Решил поделиться своим видением и наработками по реализации python-style декораторов в PHP.
В качестве завлекалочки небольшой пример использования на изображении справа. Выводит (после реализации логики самих декораторов):
Log: calling b() int(42)
Реализация выполнена в виде C расширения и не требует пересборки самого PHP. Но не заведется на хостингах, где нельзя загрузить свою so’шку.
На данный момент код находится в стадии беты (весь нужный функционал написан, но баги и утечки памяти наверняка есть 🙂 ). Так что as is. Ну а если есть желание помочь в развитии, то буду рад принять коммиты на github.
Простой пример использования:
<?php function double($func) { return function() use($func) { return 2*call_user_func_array($func, func_get_args()); }; } @double function a() { return 21; } var_dump(a()); /* Вывод: int(42) */
Декораторы всегда являются функциями, возвращающими функции. Внешняя функция принимает первым параметром подменяемую функцию. В отличие от python, декоратор с параметрами не описывается как функция, возвращающая функцию, которая возвращает функцию… Дополнительные параметры просто передаются после замещаемой функции:
<?php function add($func, $v=0) { return function() use($func, $v) { return $v+call_user_func_array($func, func_get_args()); }; } @add(1) function a() { return 1; } var_dump(a()); /* Вывод: int(2) */
Декораторы можно комбинировать:
<?php function dec($func, $p='[]') { return function() use($func, $p) { $s = call_user_func_array($func, func_get_args()); return $p[0].$s.$p[1]; }; } @dec function a() { return 'I'; } var_dump(a()); @dec('{}') function b() { return 'am'; } var_dump(b()); @dec @dec('()') @dec('{}') function c() { return 'here'; } var_dump(c()); /* Вывод: string(3) "[I]" string(4) "{am}" string(10) "[({here})]" */
При этом они выполняются в порядке обратном указанию:
@A
@B
@C
function X
превращается в
A(B(C(X(…))))
Количество параметров и их типы являются произвольными, а ленивость вычислений вообще развязывает руки:
<?php class Logger { const INFO = 'INFO'; public static function log($func, $text='', $level=self::INFO, $prefix='') { return function() use($func, $text, $level, $prefix) { printf("%s%s: %s\n", $prefix, $level, $text); return call_user_func_array($func, func_get_args()); }; } } @Logger::log('calling a()', Logger::INFO, date('Y-m-d H:i:s').': ') function a() { return 'Hello'; } var_dump(a()); /* Вывод: 2013-05-24 14:22:23: INFO: calling a() string(5) "Hello" */
В качестве имен декораторов должны выступать функции и статические методы, причем объявленные на момент вызова, а не при описании декоратора. Да и вообще можно поэкспериментировать:
<?php class Arr { public static function map($func, $cb) { return function() use($func, $cb) { $v = call_user_func_array($func, func_get_args()); return array_map($cb, $v); }; } } class Foo { /* инвертируем знаки чисел в массиве */ @Arr::map(function($i){return -$i;}) /** * Комментарии между описанием декораторов и телом функций * по большей части поддерживаются * * @return array */ public function bar() { return range(1, 3); } } $foo = new Foo(); print_r($foo->bar()); /* Вывод: Array ( [0] => -1 [1] => -2 [2] => -3 ) */
Ну, я уверен, тут каждый сможет придумать что-то поинтересней в контексте своих задач.
Технические вопросы
На данный момент я проверил поддержку при выполнении кода с декораторами через:
- cat file.php|php
- php file.php
- require/include
- eval
Возможно, что-то еще упущено.
Из известных багов/фич (фичи можно обсудить; баги я скоро исправлю):
- Если у декоратора указываются параметры, то открывающая ‘(‘ должна быть на той же строке, что и имя декоратора;
- Вследствие модификации кода __FUNCTION__ и __METHOD__ теряют свою актуальность. Можно исправить подменой констант на строки с итоговыми значениями, но не уверен в правильности такого решения;
- __LINE__ должен всегда совпадать, хотя случай многострочного описания параметров декораторов еще не проработан;
- При ошибках синтаксиса описания декораторов вызывается исключение базового класса Exception с некорректными именем файла и номером строки;
- Комментарии в коде на github на русском, т.к. моего уровня письменного английского не достаточно, чтобы не было стыдно за написанное. Надеюсь, временно, да и если кто пришлет коммит с хорошим переводом — будет классно!
- Приличные IDE ругаются на непонятный синтаксис. Есть ли возможность обучить PHPStorm хотя бы не ругаться?
ссылка на оригинал статьи http://habrahabr.ru/post/180827/
Добавить комментарий