Практическое использование PHP Reflection

в 10:29, , рубрики: php, reflection, метки: ,

Захотелось мне прописывать экшны и параметры с проверкой в моем контроллере примерно таким образом:

class Controller {     /**      * Тестовое действие      * @a /^[0-9]+$/i      * @b /^[0-9a-z]+$/i      */     function testAction ( $a, $b = 'something' ) {         echo 'a: '.$a.', b: '.$b;     } } 

Идею прописывать необходимые параметры передаваемые в URL прямо в аргументах функции я подглядел в Symfony2. Там же использовались комментарии PHPdoc для определения маршрутов. Выглядело это как чудо и я решил разобраться магия это или наука.

Disclaimer!
Всё написанное не претендует на технологическую новизну, а является демонстрацией возможностей PHP Reflection API. Преобразователь ЧПУ и роутинг на контроллер опущен, т.к. не принципиальны для примера.

Итак, задача: если переменная «a» не передана, или переданы «лишние» переменные — выдается ошибка, если переменная «b» не указана, подставляется значение по умолчанию. Обе переменные проверяются по регулярным выражениям прописанным в PHPDoc.

В итоге функция проверки получилась следующая:

function CheckURLValid ( $class, $method, $values_arr = array() ) {     $class  = new ReflectionClass( $class );     $method = $class->getMethod( $method );     $param  = $method->getParameters();     $doc    = $method->getDocComment();      //Разбираем PHPdoc     preg_match_all( '/@([a-z0-9_-]+)([^n]+)/is', $doc, $arr );     $reg_arr = array_combine($arr[1], $arr[2]);      //Проходим по аргументам функции     $params_arr = array();     foreach ( $param as $p ) {         $key        = $p->getName();         $value      = isset ( $values_arr[$key] ) ? $values_arr[$key] : false;         $regular    = isset ( $reg_arr[$key] ) ? trim($reg_arr[$key]) : false;         $default    = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : NULL;          //Если есть регулярка - проверяем         if ( isset ( $values_arr[$key] ) ) {             if ( $regular && !preg_match( $regular, $values_arr[$key] ) )                 throw new Exception( 'Параметр "'.$key.'" указан неверно!' );          //Если параметр обязательный и он не указан         } elseif ( !$p->isOptional() )             throw new Exception( 'Указаны не все обязательные параметры!' );          //Добавляем значение в общий массив         $params_arr[$key] = $value ? $value : $default;     }      //Проверяем наличие лишних параметров     if ( count(array_diff_key( $values_arr, $params_arr )) )         throw new Exception ( 'Указаны лишние параметры!' );      return $params_arr; } 

Пример использования:

try {     $arr = CheckURLValid( 'Controller', 'testAction', $_GET );     call_user_func_array( array('Controller', 'testAction'), $arr ); } catch ( Exception $e ) {     echo $e->getMessage(); } 

Можно погонять различные вариации типа:
/test.php
/test.php?a=abc
/test.php?a=12
/test.php?a=12&b=another
/test.php?a=12&c=13

Можно забрать одним файлом.

P.S. Хотя задачей было просто показать возможности Reflection, стоит обратить внимание, что приведенный пример использования обладает некоторыми минусами (подробнее в комментах):
— Невозможность обфусцирования
— Неочевидность и идеологическая кривость архитектуры
— Возможные сложности при поддержкеразработке в коллективе или сторонними разработчиками.

С другой стороны фреймворки Yii и Symfony2 используют подобные решения, правда для других целей, так что видимо не всё так плохо.

Вывод: Думайте своей головой!

Автор: azverin


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js