Reputation: 1222
I am adding some code to a php class which has several functions with repeated code that I'd like to refactor. The functions look like the following:
public function similarName($arg1, $arg2, $differentObject)
{
// $arg1 and $arg2 are common to all functions
if ($firstCheck) {
// some code
if($secondCheck) {
// some code
if ($thirdCheck) {
// unique code with $differentObject
}
}
}
// return statement
}
I need to add now a new function following exactly the same, but the unique code.
The following are the things that came to my mind:
What is the right pattern for this?
UPDATE:
The following are two of those functions with the real code. They are Symfony2 form handlers which add uploaded image for the object
public function handleToPost(FormInterface $form, Request $request, Post $post)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
if (!$post->getImage()) {
$image = new ImagePost();
$image->setPost($post);
$image->setFile($file);
$this->imageManager->saveImage($image);
} else {
$image = $post->getImage();
$image->setFile($file);
}
$this->imageManager->createImage($image);
} else {
return false;
}
return true;
}
}
return false;
}
public function handleToEvent(FormInterface $form, Request $request, Event $event)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if ($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
if (!$event->getImage()) {
$image = new ImageEvent();
$image->setEvent($event);
$image->setFile($file);
$this->imageManager->saveImage($image);
} else {
$image = $event->getImage();
$image->setFile($file);
}
$this->imageManager->createImage($image);
} else {
return false;
}
return true;
} else {
return true;
}
}
return false;
}
Upvotes: 1
Views: 242
Reputation: 57
It is hard to advise you without knowing exactly what your data represents.
If the args are the same for all function and you do some operations on them before the uniqueCode
operations wouldn't it be better to create an object where you would have your args as parameters and your checks as methods?
In other words the uniquecode
seems to be the core of your function. Leave it visible and readable and refactor the validation part.
Something like:
class ValidateArgs {
private $arg1;
private $arg2;
public function __construct($arg1, $arg2) {
$this->arg1 = $arg1;
$this->arg2 = $arg2;
}
public function check() {
//Check
$check = $this->arg1 && $this->arg2;
return $check;
}
}
And then you would call it like:
public function similarName($arg1, $arg2, $differentObject)
{
$validation = new ValidateArgs($arg1, $arg2);
if($validation->check()) {
// unique code goes here
return $result_of_unique_code;
}
}
UPDATE:
After seeing your code example I believe you need multiple iterations to successfully refactor it. Go slowly one step at a time trying to get the code a little bit cleaner on each step.
Here are a couple of suggestions:
Simplify the if/else structure. Sometimes you can avoid the use of the else
part entirely. For instance you can check for $request->getMethod()
and return immediately:
if ($request->getMethod() != 'POST') {
return false;
}
//Rest of the code
The count($errorList)
validation seems to depend only on data['file']
. I guess you could create a function with all that logic. Something like this:
public function constrainValidations($data) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($data['file'], $imageConstraint);
if (count($errorList) == 0) {
return true;
} else {
return false;
}
}
Then your original code would start to look a little bit more clean and readable:
if ($request->getMethod() != 'POST') {
return false;
}
if (!$this->constrainValidations($data)) {
return false;
}
//Unique code goes here
return true;
Keep doing it step by step. Trying to unnest all the if statements.
Also this will makes it possible for you to change the return
statements and start throwing Exceptions.
Then you can possibly start to think on the object approach for extra readability.
I would personally avoid any solution that involves a callback, but thats a matter of taste.
Upvotes: 1
Reputation: 1642
Nice question.
Based on the two examples, it seems like both Post and Event classes should implement a sort of "ImageSource" interface. If other cases are similar, and assuming also that the Event, Post and other classes can be easily changed, in my opinion the code should be something like this. Let's call the common function "handleImageSource":
public function handleImageSource(FormInterface $form, Request $request, ImageSource $imgsrc)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if ($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
$image = $imgsrc->createImageFromFile($file, $this->imageManager);
} else {
return false;
}
return true;
} else {
return true;
}
}
return false;
}
Then, every class which implements the ImageSource interface should have a method like this. For example in the Event class:
public function createImageFromFile($file, $imageManager)
{
if (!($image = $this->getImage()) ) {
$image = new ImageEvent();//|| new ImagePost() || etc...
$image->setEvent($this);//|| setPost() || etc...
$image->setFile($file);
$imageManager->saveImage($image);
} else {
$image->setFile($file);
}
$imageManager->createImage($image);
return $image;
}
In other cases, or if you only want to refactor the class with the "handleToXxxx" methods, I'd create an anonymous function with the different code, just before each call. For example, again with the Event class:
$image_source = function($file, $imageManager) use ($Event){
if (!($image = $Event->getImage()) ) {
$image = new ImageEvent();//|| new ImagePost() || etc...
$image->setEvent($this);//|| setPost() || etc...
$image->setFile($file);
$imageManager->saveImage($image);
} else {
$image->setFile($file);
}
$imageManager->createImage($image);
return $image;
};
//Then call to the function
$theHandleObj->handleImageSource($form, $request, $image_source);
Then, in the "$theHandleObj" class:
public function handleImageSource(FormInterface $form, Request $request, callable $imgsrc)
{
if ($request->getMethod() == 'POST') {
$form->bind($request);
$data = $form->getData();
$file = $data['file'];
if ($data['file']) {
$imageConstraint = new \Symfony\Component\Validator\Constraints\Image();
$imageConstraint->maxSizeMessage = Image::ERROR_MESSAGE;
$imageConstraint->maxSize = Image::MAX_SIZE;
$errorList = $this->validator->validateValue($file, $imageConstraint);
if (count($errorList) == 0) {
$image = $imgsrc($file, $this->imageManager);
} else {
return false;
}
return true;
} else {
return true;
}
}
return false;
}
Hope this helps :)
Upvotes: 1
Reputation: 4549
The answer may be to move the "unique code" to the different object class, if it depends only on the object type.
If it depends on factors other than the object type, then indeed a good approach is a "callback"
private function commonFunction($arg1, $arg2, $differentObject, $uniqueCallback)
{
// $arg1 and $arg2 are common to all functions
if ($firstCheck) {
// some code
if($secondCheck) {
// some code
if ($thirdCheck) {
$uniqueCallback($differentObject);
}
}
}
// return statement
}
public function similarFunction($arg1, $arg2, $differentObject)
{
$this->commonFunction($arg1, $arg2, $differentObject,
function($differentObject) {
// uniqueCode
});
}
Upvotes: 1