Подтвердить что ты не робот

Как я могу обойти отсутствие блока finally в PHP?

PHP до версии 5.5 не имеет окончательного блока, т.е. в то время как на самых разумных языках вы можете:

try {
   //do something
} catch(Exception ex) {
   //handle an error
} finally {
   //clean up after yourself
}

PHP не имеет понятия о конечном блоке.

У кого-нибудь есть опыт решения этой довольно раздражающей дыры на языке?

4b9b3361

Ответ 1

Решение, №. Раздражает громоздкое обходное решение, да:

$stored_exc = null;
try {
    // Do stuff
} catch (Exception $exc) {
    $stored_exc = $exc;
    // Handle an error
}
// "Finally" here, clean up after yourself
if ($stored_exc) {
    throw($stored_exc);
}

Yucky, но должен работать.

Обратите внимание: PHP 5.5 наконец (гм, извините) добавил блок finally: https://wiki.php.net/rfc/finally (и это заняло всего несколько лет... доступно в 5.5 RC почти четыре года до даты, когда я опубликовал этот ответ...)

Ответ 2

Идиома RAII предлагает уровень кода для блока finally. Создайте класс, который содержит вызываемые вызовы. В destuctor вызовите вызываемые вызовы.

class Finally {
    # could instead hold a single block
    public $blocks = array();

    function __construct($block) {
        if (is_callable($block)) {
            $this->blocks = func_get_args();
        } elseif (is_array($block)) {
            $this->blocks = $block;
        } else {
            # TODO: handle type error
        }
    }

    function __destruct() {
        foreach ($this->blocks as $block) {
            if (is_callable($block)) {
                call_user_func($block);
            } else {
                # TODO: handle type error.
            }
        }
    }
}

Координация

Обратите внимание, что PHP не имеет области блока для переменных, поэтому finally не будет запускаться до тех пор, пока функция не выйдет или (в глобальной области) последовательность выключения. Например, следующее:

try {
    echo "Creating global Finally.\n";
    $finally = new Finally(function () {
        echo "Global Finally finally run.\n";
    });
    throw new Exception;
} catch (Exception $exc) {}

class Foo {
    function useTry() {
        try {
            $finally = new Finally(function () {
                echo "Finally for method run.\n"; 
            });
            throw new Exception;
        } catch (Exception $exc) {}
        echo __METHOD__, " done.\n";
    }
}

$foo = new Foo;
$foo->useTry();

echo "A whole bunch more work done by the script.\n";

приведет к выводу:

Creating global Finally.
Foo::useTry done.
Finally for method run.
A whole bunch more work done by the script.
Global Finally finally run.

$это

Закрытия PHP 5.3 не могут получить доступ к $this (исправлено в 5.4), поэтому вам понадобится дополнительная переменная для доступа к экземплярам экземпляра в некоторых окончательных блоках.

class Foo {
    function useThis() {
        $self = $this;
        $finally = new Finally(
            # if $self is used by reference, it can be set after creating the closure
            function () use ($self) {
               $self->frob();
            },
            # $this not used in a closure, so no need for $self
            array($this, 'wibble')
        );
        /*...*/
    }

    function frob() {/*...*/}
    function wibble() {/*...*/}
}

Частные и защищенные поля

Вероятно, самая большая проблема с этим подходом в PHP 5.3 - это, наконец, закрытие не может получить доступ к закрытым и защищенным полям объекта. Подобно доступу к $this, эта проблема разрешена в PHP 5.4. Пока что частные и защищенные свойства можно получить с помощью ссылок, так как Artefacto показывает в своем ответе вопрос по этой теме в другом месте на этом сайте.

class Foo {
    private $_property='valid';

    public function method() {
        $this->_property = 'invalid';
        $_property =& $this->_property;
        $finally = new Finally(function () use (&$_property) {
                $_property = 'valid';
        });
        /* ... */
    }
    public function reportState() {
        return $this->_property;
    }
}
$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

Частные и защищенные методы можно получить, используя отражение. Вы можете использовать тот же метод для доступа к непубличным свойствам, но ссылки более простые и легкие. В комментарии на странице руководства PHP для анонимных функций, Мартин Партел дает пример класса FullAccessWrapper, который открывает непубличные поля к общественному доступу. Я не буду воспроизводить его здесь (см. Две предыдущие ссылки для этого), но вот как вы его используете:

class Foo {
    private $_property='valid';

    public function method() {
        $this->_property = 'invalid';
        $self = new FullAccessWrapper($this);
        $finally = new Finally(function () use (&$self) {
                $self->_fixState();
        });
        /* ... */
    }
    public function reportState() {
        return $this->_property;
    }
    protected function _fixState() {
        $this->_property = 'valid';
    }
}
$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

try/finally

Для блоков

try требуется хотя бы один catch. Если вы хотите только try/finally, добавьте блок catch, который поймает не Exception (код PHP не может выбросить что-либо, не полученное из Exception), или повторно выбросить исключение пойманного. В первом случае я предлагаю поймать StdClass как идиому, означающую "ничего не поймать". В методах улавливание текущего класса также может использоваться для обозначения "ничего не поймать", но использование StdClass проще и легче найти при поиске файлов.

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (StdClass $exc) {}

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (RuntimeError $exc) {
    throw $exc
}

Ответ 3

Вот мое решение отсутствия блока finally. Он не только обеспечивает работу для блока finally, но и расширяет возможности try/catch, чтобы уловить ошибки PHP (и фатальные ошибки тоже). Мое решение выглядит так (PHP 5.3):

_try(
    //some piece of code that will be our try block
    function() {
        //this code is expected to throw exception or produce php error
    },

    //some (optional) piece of code that will be our catch block
    function($exception) {
        //the exception will be caught here
        //php errors too will come here as ErrorException
    },

    //some (optional) piece of code that will be our finally block
    function() {
        //this code will execute after the catch block and even after fatal errors
    }
);

Вы можете скачать решение с документацией и примерами из git hub - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys

Ответ 4

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

Хорошие книги не позволяют использовать блоки finally для любого другого, кроме освобождения ресурса, поскольку вы не можете быть уверены, что он выполнит, если произойдет что-то неприятное. Называть это раздражающей дырой является довольно преувеличением. Поверьте мне, черт возьми, очень хороший код написан на языках без окончательного блока.:)

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

Ответ 5

function _try(callable $try, callable $catch, callable $finally = null)
{
    if (is_null($finally))
    {
        $finally = $catch;
        $catch = null;
    }

    try
    {
        $return = $try();
    }
    catch (Exception $rethrow)
    {
        if (isset($catch))
        {
            try
            {
                $catch($rethrow);
                $rethrow = null;
            }
            catch (Exception $rethrow) { }
        }
    }

    $finally();

    if (isset($rethrow))
    {
        throw $rethrow;
    }
    return $return;
}

Вызов с использованием закрытий. Второй параметр $catch не является обязательным. Примеры:

_try(function ()
{
    // try
}, function ($ex)
{
    // catch ($ex)
}, function ()
{
    // finally
});

_try(function ()
{
    // try
}, function ()
{
    // finally
});

Правильно обрабатывает исключения везде:

  • $try: Исключение будет передано $catch. Сначала будет выполняться $catch, затем $finally. Если нет $catch, исключение будет восстановлено после запуска $finally.
  • $catch: $finally будет выполняться немедленно. Исключение будет отменено после завершения $finally.
  • $finally: Исключение будет беспрепятственно разрушать стек вызовов. Любые другие исключения, запланированные для ретронирования, будут отброшены.
  • Нет: возвращается возвращаемое значение из $try.

Ответ 6

Если кто-то все еще отслеживает этот вопрос, вам может быть интересно проверить (новый) RFC для окончательной языковой функции в PHP-вики. У автора уже есть рабочие исправления, и я уверен, что это предложение принесет пользу от отзывов других разработчиков.

Ответ 7

Я только что закончил писать более элегантный класс Try Catch finally, который может вам пригодиться. Есть некоторые недостатки, но их можно обойти.

https://gist.github.com/Zeronights/5518445