Декораторы в PHP

от автора

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Никто ещё не голосовал. Воздержавшихся нет.

ссылка на оригинал статьи http://habrahabr.ru/post/180827/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *