Как я могу обойти отсутствие блока finally в PHP? - PullRequest
55 голосов
/ 29 мая 2009

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

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

PHP не имеет представления о блоке finally.

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

Ответы [ 7 ]

59 голосов
/ 29 мая 2009

Решение, нет. Раздражающий громоздкий обходной путь, да:

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

Тьфу, но должно сработать.

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

9 голосов
/ 12 февраля 2012

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

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), поэтому вам понадобится дополнительная переменная для доступа к элементам экземпляра внутри некоторых блоков finally.

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 заключается в том, что finally-замыкание не может получить доступ к закрытым и защищенным полям объекта. Как и доступ к $this, эта проблема решена в PHP 5.4. Пока к частным и защищенным свойствам можно получить доступ, используя ссылки, как Артефакто показывает в своем ответе на вопрос по этой самой теме в другом месте на этом сайте.

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
}
2 голосов
/ 30 мая 2012

Вот мое решение по поводу отсутствия блока 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

1 голос
/ 14 июля 2013
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 будет возвращено.
1 голос
/ 29 мая 2009

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

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

Суть finally заключается в выполнении независимо от того, был успешен блок try или нет.

0 голосов
/ 04 мая 2013

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

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

0 голосов
/ 31 июля 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...