简体   繁体   English

使用PHP在多个函数中重复代码

[英]Repeated code in several functions with PHP

I am adding some code to a php class which has several functions with repeated code that I'd like to refactor. 我将一些代码添加到一个php类,它有几个函数,重复代码,我想重构。 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. 如果为true,则在每个函数中执行唯一代码。 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. 使用抽象类和模板抽象方法获取唯一代码,但我不确定如何传递$ 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 它们是Symfony2表单处理程序,用于为对象添加上载的图像

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

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

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? 如果args对于所有函数都是相同的,并且在uniqueCode操作之前对它们执行某些操作,那么创建一个将args作为参数并将检查作为方法的对象会更好吗?

In other words the uniquecode seems to be the core of your function. 换句话说, uniquecode似乎是您功能的核心。 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. 简化if / else结构。 Sometimes you can avoid the use of the else part entirely. 有时你可以完全避免使用else部分。 For instance you can check for $request->getMethod() and return immediately: 例如,您可以检查$request->getMethod()并立即返回:

if ($request->getMethod() != 'POST') {
    return false;
}

//Rest of the code

The count($errorList) validation seems to depend only on data['file'] . count($errorList)验证似乎仅依赖于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. 试图取消所有if语句。 Also this will makes it possible for you to change the return statements and start throwing Exceptions. 此外,这将使您可以更改return语句并开始抛出异常。

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. 我个人会避免任何涉及回调的解决方案,但这只是一种品味问题。

Nice question. 好问题。

Based on the two examples, it seems like both Post and Event classes should implement a sort of "ImageSource" interface. 基于这两个例子,似乎Post和Event类都应该实现一种“ImageSource”接口。 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. 如果其他情况类似,并且还假设可以轻松更改Event,Post和其他类,我认为代码应该是这样的。 Let's call the common function "handleImageSource": 我们调用常用函数“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. 然后,实现ImageSource接口的每个类都应该有这样的方法。 For example in the Event class: 例如在Event类中:

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. 在其他情况下,或者如果您只想使用“handleToXxxx”方法重构类,我会在每次调用之前使用不同的代码创建一个匿名函数。 For example, again with the Event class: 例如,再次使用Event类:

$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: 然后,在“$ theHandleObj”类中:

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 :) 希望这可以帮助 :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM