Defer: из Go в PHP

Автор: admin от 7-01-2018, 17:40, посмотрело: 55

В языке Go есть полезная конструкция defer. Обычно она используется для освобождения ресурсов и работает следующим образом: в качестве аргумента defer передается функция, которая помещается в список функций. Этот список функций выполняется при выходе из объемлющей функции.



У defer есть несколько очевидных и не очень достоинств:




  • улучшает понимание кода — при создании ресурса сразу виден код, ответственный за его освобождение. Не нужно искать try {} finally {} или все точки выхода из функции

  • позволяет избежать частых ошибок, связанных с освобождением ресурсов, например, с необработанными исключениями, или в случае открытия нескольких ресурсов.



К примеру, такой код:



class Utils
{

    public function copyFile(string $sourceName, string $destName): void
    {
        $readHandle = fopen($sourceName, "r");
        if ($readHandle === false) {
            throw new Exception();
        }

        $writeHandle = fopen($destName, "w");
        if ($writeHandle === false) {
            fclose($readHandle);
            throw new Exception();
        }

        while (($buffer = fgets($readHandle)) !== false) {
            $wasFailure = fwrite($writeHandle, $buffer);
            if ($wasFailure) {
                fclose($readHandle);
                fclose($writeHandle);
                throw new Exception();
            }
        }

        if (!feof($readHandle)) {
            fclose($readHandle);
            fclose($writeHandle);
            throw new Exception();
        }
        fclose($readHandle);
        fclose($writeHandle);
    }
}


Можно было бы превратить в такой:



class Utils
{

    public function copyFile(string $sourceName, string $destName): void
    {
        $readHandle = fopen($sourceName, "r");
        if ($readHandle === false) {
            throw new Exception();
        }
        defer fclose($readHandle);

        $writeHandle = fopen($destName, "w");
        if ($writeHandle === false) {
            throw new Exception();
        }
        defer fclose($writeHandle);

        while (($buffer = fgets($readHandle)) !== false) {
            $wasFailure = fwrite($writeHandle, $buffer);
            if ($wasFailure) {
                throw new Exception();
            }
        }

        if (!feof($readHandle)) {
            throw new Exception();
        }
    }
}


Во втором случае работать с закрытием файлов гораздо проще — каждый файл нужно закрыть только один раз. Снижается вероятность, что кто-то забудет закрыть файл, особенно, если их будет не 2, а больше.



Но, к сожалению, в PHP нет defer. Зато можно написать свою реализацию. Она выглядит следующим образом:



class DeferredContext
{
    protected $deferredActions = [];

    public function defer(callable $deferAction)
    {
        $thisdeferredActions[] = $deferAction;
    }

    public function executeDeferredActions()
    {
        $actionsCount = count($thisdeferredActions);
        if ($actionsCount > 0) {
            for ($i = $actionsCount - 1; $i >= 0; $i--) {
                $action = $thisdeferredActions[$i];
                try {
                    $action();
                } catch (Exception $e) {

                }
                unset($thisdeferredActions[$i]);
            }
        }

        $thisdeferredActions = [];
    }
}

trait DeferredTrait
{

    private function deferred(callable $callback) {
        $context = new DeferredContext();
        try {
            $callback($context);
        } finally {
            $contextexecuteDeferredActions();
        }
    }
}


DeferredContext — класс, в котором накапливаются функции-обработчики. При выходе из функции необходимо вызвать метод executeDeferredActions(), который выполнит все обработчики. Для того, чтобы не создавать вручную DeferredContext, можно использовать трейт DeferredTrait, который инкапсулирует в себе логику работы с DeferredContext.



С использованием данного подхода код из примера выше будет выглядеть так:



class Utils
{
    use DeferredTrait;

    public function copyFile(string $sourceName, string $destName): void
    {
        $thisdeferred(function(DeferredContext $context) use ($destName, $sourceName) {
            $readHandle = fopen($sourceName, "r");
            if ($readHandle === false) {
                throw new Exception();
            }
            $contextdefer(function() use ($readHandle) { fclose($readHandle); });

            $writeHandle = fopen($destName, "w");
            if ($writeHandle === false) {
                throw new Exception();
            }
            $contextdefer(function() use ($writeHandle) { fclose($writeHandle); });

            while (($buffer = fgets($readHandle)) !== false) {
                $wasFailure = fwrite($writeHandle, $buffer);
                if ($wasFailure) {
                    throw new Exception();
                }
            }

            if (!feof($readHandle)) {
                throw new Exception();
            }
        });

    }
}


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



Источник: Хабрахабр

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *