1

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:

  • Use a callback function as a parameter, not sure how to.
  • Use a single function with the common code, and return a boolean value. If true then execute unique code in every single function. This seems inelegant to me.
  • Using an abstract class and a template abstract method for the unique code, but I am not sure how to pass the $differentObject.
  • I am even wondering if this is a Decorator pattern issue, but I never used it this way.

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; } 
2
  • I know you want to simplify, I just wished the snippet code was more meaningful Commented Jul 6, 2015 at 16:42
  • Editing the question with real code... Commented Jul 6, 2015 at 16:45

3 Answers 3

1

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 :)

Sign up to request clarification or add additional context in comments.

2 Comments

Pretty cool, yes! i like it more the first approach, it seems more readable to me as lambda functions are sometimes difficult to debug. Although, I am thinking that the imgsrc->createImageFromFile needs the $image_manager object injected, so how about as I said earlier about using inheritance and a factory? It is pretty much the same really, but I could inject the object to the main class instead :) Thanks @juanra
As you said, the imageManager param is the odd part, it's not very elegant passing a local object ($this->obj) to a method of another class. I guess the imageManager should be accessed from the other class in other way, but the best way to do it depends on how the imageManager works...
1

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 }); } 

4 Comments

Thanks Daniel. It does depend on the object type. I guess callback is a good solution, yes, and your code is really great. How about encapsulating the unique code in different classes extending this one? Any idea how to? Please check the updated question.
If it depends purely on the object type, then the call should be $differentObject->uniqueCode(), and the various subclasses of differentObject should implement it differently. Kind of. It depends on whether or not differentObject's "concern" should include the "uniqueCode". This is getting a bit advanced for a non-concrete example.
Yes I understand. But that is not possible, there are some dependencies that I don't want to pass to the different objects, that is why I am asking if it makes sense to use different classes which extend this one for every type of concrete different objects. I am even thinking on a factory for creating these new objects. How does this sound?
Yes, that sounds like it might be an approach I would take. (Which of course means its flawless ;-) )
1

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.

3 Comments

Thanks. If you take a look at the original code there are several return statements that I have to be aware of, so I can only return the result of the 'unique code' piece of code. Imagine even that I could have another piece of 'similar code' after that, can you imagine a way to use your approach?
Before I update my answer I have noticed that in your example you have one function that returns false when $data['file'] is false and the other one returns true when $data['file'] is false. I am worried this might be a bug...
Ir is indeed! Thanks!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.