Захотелось мне прописывать экшны и параметры с проверкой в моем контроллере примерно таким образом:
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