Pull to refresh

Comments 54

@ — друг программиста теперь и в PHP
В лучших традициях костыльно-ориентированного программирования PHP все ошибки, возникающие в декораторах будут подавляться по-умолчанию.
@ в объявлении декоратора не доходит до самого PHP. И никакого влияния на реакцию на ошибки они не оказывают.
Это просто частичное совпадение синтаксиса.
А как подавить все ошибки, возникающие в декораторе при его исполнении? @@?
Если без error_handler никак, то так же как и раньше — перед вызываемой функцией, которая может сгенерить ошибку.
Ну да, этот как вариант error_handler'а.
Хотя можно извратиться так :)
<?php
function silence($func) {
    return function() use($func) {
        $current_level = error_reporting(0);
        $res = call_user_func_array($func, func_get_args());
        error_reporting($current_level);
        return $res;
    };
}

function replacer($func, $name) {
    return function() use($func, $name) {
        return call_user_func_array($name, func_get_args());
    };
}

@silence
@replacer('fopen')
function xfopen(){}

xfopen('/wrong/path', 'rb');


Возможно, приду в последствие к чему-нибудь вида
@Decorate('fopen', 'silence')
или
Decorate('fopen', 'silence');

За что ж вы так с @ то…

А почему бы и нет? Его всё-равно никто не использует.
Его нельзя «не использовать», потому что в php нет другого способа вручную обработать ошибки при вызове ряда стандартных функций. Например, если вы вызовете fopen без собаки, то получите warning для несуществующего файла (а вы, возможно, хотели эту ситуацию обработаьь вручную и показать пользователю внятное сообщение). На всякий случай для тех, кто сам не догадался: предварительный вызов file_exists, is_readable и т.д. проблему не решают, потому что между вызовами этих проверок и вызовом самой fopen файл может исчезнать (к примеру).
Есть два способа обработать эту ситуацию:

1) Как написали выше, собственный error_handler
2) display_errors в off, и контролировать возвращаемое значение от fopen. В случае ошибки придет false.

И к слову сказать, я считаю, что это более правильные варианты, нежели подавлять ошибку с помощью @.
1) Если вы пишете библиотеку, то error_handler мягко говоря не самый удобный вариант, вам надо: выставить обработчик перед вызовом, выполнить операцию, вернуть обратно.
2) display errors не убережет вас от warning
И правильно сделает, что не убережет. Но он не покажет сообщение пользователю, а запишет его в логи. Ситуация, когда файл пропадает после вызова file_exists и до вызова fopen явно не нормальна и ее надо отслеживать. Запись в логи в данном случае вполне уместна.
Вызов file_exists и fopen это две дисковых операции, когда можно обойтись одним fopen и принципом let it fail.
Если я контролирую возвращаемое значение fopen мне абсолютно неинтересен этот warning, он будет только забивать логи.

В питоне мы можем сделать try fopen except IOError, в php такой возможности нет, поэтому приходится подавлять ошибку и проверять код возврата.
Ну если следовать принципу let it fail и нет возможности написать собственный error_handler, то такой подход допустим. Но я в своей практике ситуации где бы это было действительно оправдано встречал крайне редко.

Кстати, а если в Вашем примере без file_exists файл не пропал, а просто у скрипта нет прав на его чтение? В этом случае warning бы очень помог быстро обнаружить ошибку.
Подход в корне другой.
То что не удалось открыть файл, это еще не повод генерировать ошибку, я могу пытаться проверить файл на чтение как раз таким образом (вместо file_exists, т.к. file_exists не означает что файл можно будет открыть).
Или я могу захотеть проверить код возврата чтобы кинуть Exception, который будет отловлен на нужном уровне в моем коде.
Или у меня хостинг клиентских файлов, и то что файл не существует, или к нему нет доступа меня не волнует, может быть это администратор специально забрал права или удалил файл; в коде есть проверка результата fopen и показывается стандартная заглушка клиенту.
Короче говоря, отсутствие файла может быть вполне нормальной ситуацией, незачем об этом писать в логи.
Это просто превращение всех ошибок в исключения. Не совсем то.
Нам-то нужно ловить именно аналог IOError (ошибки операции с файлом), а размытое понятие ErrorException ни о чем конкретном не говорит.
Функции стандартной библиотеки (разве что кроме «оберток» к методам ООП-библиотек типа mysqli_*) не бросают исключений, а сигнализируют об ошибках обычно возвратом FALSE (даже возвращающие при обычной работе 0, NULL, пустую строку и т. п. что часто приводит к конфузам из-за использования ==/!= вместо ===/!== в выражениях типа if ($result == FALSE) или if ($result != 0) ), а часть из них ещё бросают warning'и (может и другие ошибки бросают, но не припоминаю). @ как раз и предназначен (по крайней мере чаще всего для этого используется) для подавления warning'ов в таких случаях.
Ситуация, когда файл пропадает после вызова file_exists и до вызова fopen явно не нормальна и ее надо отслеживать.

Для разделяемых между процессами (а то и хостами) файлов она вполне нормальна, пускай это и не лучший способ организовывать IPC, но частый, а иной раз вообще единственно доступный.
Так, по-моему, попроще и покорректнее будет.
$current_level = error_reporting(0);
$file = fopen('/etc/passwd', 'r+');
error_reporting($current_level);

Единственное отличие в поведении вроде бы — не будет вызываться внешний error_handler
не отработает, это не эксепшен
Не в курсе был, в python все отработает и перехватится…
А по сути.
Выбрано по аналогии с python. Понятно, что выходит двоякое восприятие @. Но одной из целей написания статьи и было обсудить идею и синтаксис. Поменять @ на что-то другое, более подходящее, пока еще можно.
Как вариант для синтаксиса использовать а-ля phpdoc аннотации.
Ну, например,
<?php
function double($func) {
    return function() use($func) {
        return 2*call_user_func_array($func, func_get_args());
    };
}

/**
 * @return integer
 * @decorated_by double
 **/
function a()
{
    return 21;
}
var_dump(a());

/* Вывод:
int(42)
*/

Из плюсов — не будет ругаться IDE (разве что на неиспользуемую функцию) и даже транслятор, просто функция не будет декорироваться. Из минусов — в декораторе может быть важная логика, например, авторизации и она не сработает. А если декоратор преобразует результаты (хоть тип, хоть значение), а не просто осуществляет действия типа логирования вызовов, то могут возникать очень трудноуловимые ошибки времени «исполнения» вместо синтаксических времени «компиляции».
Вот из-за «в декораторе может быть важная логика» в какой-то момент не стал делать декораторы внутри какого-либо вида комментариев.
Если уж декоратор объявлен, то предполагается, что он тут нужен, а не просто «задел на будущее».

В общем, над этим еще думаю.
Не имеет смысла использовать комментарии ни для чего, кроме комментирования; иначе — это убивает саму идею комментариев.
А что делать если хранить и обрабатывать метаданные нужно, причем часто желательно в непосредственной близости от объекта, а синтаксис этого не позволяет.
Ответ может варьироваться в зависимости от того, что вы понимаете под метаданными.

В нашем случае правильнее было бы говорить не о метаданных, а об инструкции/операторе, потому как на лицо его влияние на логику исполнения приложения. Ну, а добавление оператора означает расширение синтаксиса.
Из минусов — в декораторе может быть важная логика, например, авторизации и она не сработает.

Никогда никому не советую запихивать важную логику в декоратры… себе дороже…
код написан под впечатление статьи «Изменяем синтаксис РНР»
в целом не плохо.
А что за статья? Что-то ни на Хабре, ни гуглится по полному названию.
Да и если речь о модификации логики лексического парсера, то нет, у меня по другому.
Мне ее нашли.
Нет, в моем случае не требуется пересобирать сам PHP: подцепляем расширение через «extension=decorators.so» и пользуемся.
ок…
просто смотрю, синтаксис странный,
да и твоя статья вышла три дня спустя…

а вообще все супер
А неймспейсы поддерживает?
Вопрос об этом?
1.php:
<?php
require(__DIR__.'/2.php');

use \Foo\Decor;

@Decor::neg
function one()
{
    return 1;
}
var_dump(one());


2.php:
<?php
namespace Foo;

class Decor
{
    public static function neg($func)
    {
        return function() use($func) {
            $v = call_user_func_array($func, func_get_args());
            return -$v;
        };
    }
}


Выводит:
int(-1)

Вопрос был об этом?

Вариант с
@\Foo\Decor::neg
пока нет, но будет.
Я правильно понял, что в экстеншене:
1. Вручную написаный лексер пропускает через себя каждое выражение
2. Находя там собаку начинает собирать колбеки
3. Когда строки с собаками кончаются, то он собирает строку выражения вида call_user_func_array(call_user_func_array(%and so on%, func_get_args()), func_get_args()), и передает ее пхпшному парсеру?))

Это блин забавное решение:) Не подумайте, что я стебу, я даже плюсанул пост, но все же, способ очень извращенный)
1) Да. Используется родной Zend'овский лексер lex_scan, поверх которого ДКА. Если в коде нет «собачек», то сразу выдается пришедшее на вход, без токенизации.

3) Не совсем такая строка. Расширение экспортирует глобальную функцию decorators_preprocessor, которой можно подсунуть исходный код с декораторами, а на выходе получить чистый php код.
Код вида:
@a(A)
@b
@c(C)
function x(X)
{
    Y
}

превращается в:
function x(X)
{ return call_user_func_array(a(b(c(function(X) {
    Y
}, C)), A), func_get_args());}


Я не придумал ничего лучше, с предпосылками:
  • просто php расширение, не затрагивающее сам PHP;
  • сохранение имени и формата вызова декорируемой функции/метода;
  • по максимуму сохранить неизменными магические константы.
Вот здесь можно выхватить сложных багов:
call_user_func_array($func, func_get_args());

Не знаю как в свежих версиях PHP но раньше были проблемы в случае передачи параметров по ссылке.
Насколько я помню func_get_args возвращала копию параметров и соответственно внутри функции по ссылке они уже не изменялись.
Копируется и в 5.5.
Не задумывался над этим вопросом (видимо из-за того, что не пользуюсь передачей по ссылке). Передача по ссылке в функцию, которая заворачивается в декоратор… Подумаю на эту тему.
А вообще интересно мнение Хабра: как поступать в подобных случаях.
Ну по сути проблема не большая, параметры по ссылке дейстивтельно используются не часто (но иногда бывает полезно при обработке больших массивов например).
Тем не менее возможно стоило бы указать это как ограничение просто для того чтобы сэкономить кому то время и нервы.
Пока расширение не имеет даже качественных тестов :)
Когда будет версия с претензией на стабильность и оттестированность, которую сам попробую в production — тогда конечно будут задокументированы все особенности.

А пока собираю дельные комментарии вроде вашего.
Теперь модно вот это называть декораторами? Проблема в том, что это слегка, точнее совсем не декоратор. При этом если поле использования декораторов это именно рантайм, нацепить к примеру слой совместимости для объектов предыдущей версии, иль еще чего-нить. А это что? Если исходит из стиля PHP, это что-то похожее на трейты для функций. Да и рантаймом тут и не пахло.
И вот еще The Java EE 6 Tutorial:
@Decorator
public abstract class CoderDecorator implements Coder {
    @Inject
    @Delegate
    @Any
    Coder coder;

    @Override
    public String codeString(String s, int tval) {
        ...
    }
}
Я бы назвал все это аспектно-ориентированным программированием ) То, что вы делаете с помощью парсера очень походит на то, что делает моя либа, написанная на PHP. Я скоро планирую завершить работы и по перехвату стандартных функций, типа fopen, mysqli_open и др. Основное отличие-работать будет везде, на любом хостинге.
Всех интересующихся приглашаю на DevConf )
АОП это то, что можно из декораторов получить, но на этом применение не заканчивается. Декораторы я планирую использовать в работе, так что самому интересно, к чему в итоге придет :)

Насчет работы на любом хостинге. Да, расширение можно поставить не везде, я написал об этом в самом начале. Но в моей работе нужно помнить о времени работы написанного кода и не применять без сильной необходимости сильно абстрактные фреймворки любых толков. Тут «нативное» расширение очень к месту: и быстро и удобно.
На данный момент я проверил поддержку при выполнении кода с декораторами через:

php -a

Ну и надеюсь, что про mod_php и php-fpm (да со включенными apc? eaccelerator, xcache и т. п.) просто не сочли нужным упоминать в силу очевидности.

А вообще интересно, хотя после знакомства с AOP декораторы python кажутся уже примитивными.
Ошибка — в первом же примере:

/decor3.php — syntax error, unexpected 'function' (T_FUNCTION) in /home/..../php/decor3.php on line 9

Версия php 5.4.15

С остальными примерами то же самое.
Можете прислать в личку выдачу?
echo decorators_preprocessor(file_get_contents('decor3.php'));
Я прошу прощения, но после работы не сразу понял статью. Это был пример без указанного расширения, на чистом PHP. Не нашел, как удалить.
А по поводу использования сразу могу сказать, что использовать не буду. По простой причине — мне необходимо отдавать код, который будет работать в том числе и на хостингах. Такова жизнь. А там и так хватает заморочек.
Ставлю плюс. Виртуально. Кармы не хватает.
Sign up to leave a comment.

Articles

Change theme settings