Повторный код в нескольких функциях с PHP

1

Я добавляю некоторый код в класс php, который имеет несколько функций с повторяющимся кодом, который я бы хотел реорганизовать. Функции выглядят следующим образом:

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
}

Мне нужно добавить новую функцию, точно такую же, но уникальный код.

Вот что мне пришло в голову:

  • Используйте функцию обратного вызова в качестве параметра, но не знаете, как это сделать.
  • Используйте одну функцию с общим кодом и возвращайте логическое значение. Если true, то выполняйте уникальный код в каждой отдельной функции. Это кажется мне неэлегантным.
  • Использование абстрактного класса и абстрактного метода шаблона для уникального кода, но я не уверен, как передать $ differentObject.
  • Мне даже интересно, является ли это проблемой шаблона Decorator, но я никогда не использовал его таким образом.

Какая правильная картина для этого?

ОБНОВИТЬ:

Ниже приведены две из этих функций с реальным кодом. Это обработчики форм 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;
}
  • 0
    Я знаю, что вы хотите упростить, я просто хотел, чтобы фрагмент кода был более значимым
  • 0
    Редактирование вопроса с реальным кодом ...
Теги:
design-patterns

3 ответа

1
Лучший ответ

Хороший вопрос.

Основываясь на двух примерах, кажется, что как классы Post, так и Event должны реализовать своего рода интерфейс "ImageSource". Если другие случаи схожи, и если предположить, что событие, сообщение и другие классы можно легко изменить, на мой взгляд, код должен быть примерно таким. Позвольте называть общую функцию "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;
}

Затем каждый класс, реализующий интерфейс ImageSource, должен иметь такой метод. Например, в классе 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;
}

В других случаях, или если вы хотите реорганизовать класс только с помощью методов "handleToXxxx", я бы создал анонимную функцию с другим кодом непосредственно перед каждым вызовом. Например, снова с классом 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);

Затем в классе "$ 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;
}

Надеюсь это поможет :)

  • 0
    Довольно круто, да! мне больше нравится первый подход, он кажется мне более читабельным, поскольку лямбда-функции иногда трудно отладить. Хотя я думаю, что imgsrc->createImageFromFile нуждается во $image_manager объекте $image_manager , так как насчёт того, как я говорил ранее об использовании наследования и фабрики? Это почти то же самое, но я мог бы вместо этого ввести объект в основной класс :) Спасибо @juanra
  • 0
    Как вы сказали, параметр imageManager - это нечетная часть, не очень элегантно передавать локальный объект ($ this-> obj) в метод другого класса. Я предполагаю, что к imageManager нужно обращаться из другого класса другим способом, но лучший способ сделать это зависит от того, как работает imageManager ...
1

Трудно вам посоветовать, не зная точно, что представляют ваши данные.

Если аргументы одинаковы для всех функций, и вы выполняете некоторые операции над ними до того, uniqueCode операции uniqueCode не будут лучше создавать объект, в котором у вас будут ваши аргументы как параметры и ваши проверки как методы?

Другими словами, uniquecode, по-видимому, является ядром вашей функции. Оставьте его видимым и читаемым и отредактируйте часть проверки.

Что-то вроде:

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

И тогда вы бы назвали это так:

public function similarName($arg1, $arg2, $differentObject) 
{
    $validation = new ValidateArgs($arg1, $arg2);
    if($validation->check()) {

        // unique code goes here

        return $result_of_unique_code;
    }
}

ОБНОВИТЬ:

После просмотра вашего примера кода я считаю, что вам нужно много итераций, чтобы успешно реорганизовать его. Пойдите медленно шаг за шагом, пытаясь получить код немного чище на каждом шаге.

Вот несколько предложений:

Упростите структуру if/else. Иногда вы можете полностью исключить использование части else. Например, вы можете проверить $request->getMethod() и немедленно вернуться:

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

//Rest of the code

Проверка count($errorList) видимому, зависит только от data['file']. Думаю, вы могли бы создать функцию со всей этой логикой. Что-то вроде этого:

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

Тогда ваш исходный код начнет выглядеть немного более чистым и читаемым:

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

if (!$this->constrainValidations($data)) {
    return false;
}
//Unique code goes here
return true;

Продолжайте делать это шаг за шагом. Попытка отключить все операторы if. Кроме того, это позволит вам изменить операторы return и начать выброс исключений.

Затем вы можете начать думать об объектном подходе для дополнительной удобочитаемости.

Я бы лично избегал любого решения, которое включает обратный вызов, но это вопрос вкуса.

  • 0
    Благодарю. Если вы посмотрите на оригинальный код, я должен знать о нескольких операторах возврата, поэтому я могу вернуть только результат фрагмента кода «уникальный код». Даже представьте, что после этого у меня может появиться еще один «похожий код», можете ли вы представить себе способ использовать свой подход?
  • 0
    Прежде чем я обновлю свой ответ, я заметил, что в вашем примере одна функция возвращает false, когда $ data ['file'] равно false, а другая возвращает true, если $ data ['file'] - false. Я беспокоюсь, что это может быть ошибка ...
Показать ещё 1 комментарий
1

Ответ может состоять в том, чтобы переместить "уникальный код" в другой класс объекта, если он зависит только от типа объекта.

Если это зависит от факторов, отличных от типа объекта, то действительно хороший подход - это "обратный вызов",

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
        });
}
  • 0
    Спасибо Даниэль. Это зависит от типа объекта. Я думаю, что обратный вызов - это хорошее решение, да, и ваш код действительно хорош. Как насчет инкапсуляции уникального кода в разных классах, расширяющих этот? Есть идеи как? Пожалуйста, проверьте обновленный вопрос.
  • 1
    Если это зависит исключительно от типа объекта, то вызов должен быть $differentObject->uniqueCode() , и различные подклассы differentObject должны реализовать его по-разному. Что-то вроде. Это зависит от того, должен ли «беспокойство» другого объекта включать «уникальный код». Это немного продвинуто для неконкретного примера.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню