Решил поделиться своим видением и наработками по реализации 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 хотя бы не ругаться?