Статьи

Исследуйте аспектно-ориентированное программирование с CodeIgniter, часть 3

В предыдущих частях серии мы узнали о концепциях AOP и необходимости использования AOP в крупномасштабных проектах, и я представил хуки CodeIgniter как удобный механизм для создания функциональности AOP с нуля. В этой части я покажу вам, как использовать XML и методы, основанные на комментариях, для создания пользовательских функциональных возможностей AOP, когда отдельная инфраструктура AOP недоступна.

Конфигурация XML

Давайте начнем с метода XML. Во-первых, нам нужен специальный XML-файл с деталями конфигурации AOP. Я собираюсь определить конкретные теги для правил АОП, как показано в коде ниже.

<?xml version="1.0" encoding="UTF-8" ?> <aopConfig> <aopAspect id="LoggingAspect" > <aopPointcut expression="execution[*.*]" > <aopBefore ref-class="LoggingAspect" method="startLog"/> <aopAfter ref-class="LoggingAspect" method="endLog"/> </aopPointcut> </aopAspect> <aopAspect id="ProfilingAspect" > <aopPointcut expression="execution[*.*]" > <aopBefore ref-class="ProfilingAspect" method="startProfiling"/> <aopAfter ref-class="ProfilingAspect" method="endProfiling"/> </aopPointcut> </aopAspect> </aopConfig> 

Каждая структура АОП будет иметь свои собственные уникальные теги. Здесь я определил <aopAspect> для каждого типа аспекта и <aopPointcut> чтобы уменьшить количество точек соединения для соответствия определенным критериям. В <aopPointcut> я использовал атрибут выражения, который будет использоваться для сопоставления точек соединения.

Я определил два тега для до и после консультации; всякий раз, когда метод соответствует выражению pointcut, он будет искать совет до или после и вызывать методы, предоставленные в классе Aspect используя значение атрибута ref-class .

В приведенном выше коде выражение [*.*] Означает, что рекомендация будет выполнена для всех методов всех контроллеров. Если бы мы хотели применить некоторые советы ко всем методам удаления. выражение pointcut будет [*.delete*] . Если бы мы хотели применить совет к определенному типу контроллеров, это было бы [*Service.*] . Вы можете определить любую пользовательскую структуру для соответствия классам и методам. Вышесказанное — самый простой способ сделать это.

Теперь давайте посмотрим, как мы можем использовать этот файл конфигурации для применения советов в CodeIgniter. В предыдущей части я объяснил, как определять pre_controller и post_controller для вызова пользовательских классов. Ниже показана реализация этих классов.

 <?php class AOPCodeigniter { private $CI; public function __construct() $this->CI = &get_instance(); } public function applyBeforeAspects() { $uriSegments = $this->CI->uri->segment_array(); $controller = $uriSegments[1]; $function = $uriSegments[2]; $doc = new DOMDocument(); $doc->load("aop.xml"); $aopAspects = $doc->getElementsByTagName("aopPointcut"); foreach ($aopAspects as $aspect) { $expr = $aspect->getAttribute("expression"); preg_match('/[(.*?)]/s', $expr, $match); if (isset($match[1])) { $exprComponents = explode(".", $match[1]); $controllerExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[0]) . "$/"; $functionExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[1]) . "$/"; preg_match($controllerExpr, $controller, $controllerMatch); preg_match($functionExpr, $function, $functionMatch); if (count($controllerMatch) > 0 && count($functionMatch) > 0) { $beforeAspects = $aspect->getElementsByTagName("aopBefore"); foreach ($beforeAspects as $beforeAspect) { $refClass = $beforeAspect->getAttribute("ref-class"); $refMethod = $beforeAspect->getAttribute("method"); $classObject = new $refClass(); $classObject->$refMethod(); } } } } } } 

Весь код для предварительных рекомендаций применяется внутри applyBeforeAspects() .

Изначально мы получаем сегменты URI, используя класс URI CodeIgniter. Первый параметр — это контроллер, а следующий параметр — имя метода для текущего запроса. Затем мы загружаем наш файл конфигурации AOP с помощью DOMDocument() и получаем <aopPointcut> точки с помощью <aopPointcut> .

Зацикливаясь на каждом pointcut, мы получаем атрибут expression . Затем мы получаем выражение в скобках с помощью регулярных выражений и подготавливаем имя контроллера и имя метода, взорвав нашу совпавшую строку. Я использовал базовое регулярное выражение для сопоставления контроллеров и методов, но в реальной среде AOP это было бы намного сложнее.

Затем мы пытаемся сопоставить имя контроллера и имя метода с созданными нами регулярными выражениями. Если метод и контроллер совпадают, у нас есть подсказка, где мы должны применить советы. Поскольку в этом методе мы применяем <aopBefore> рекомендации, мы получаем теги <aopBefore> для текущего pointcut. Циклически просматривая каждый тег before, мы применяем совет, вызывая метод ref-class .

Это основной способ создания функциональности АОП с CodeIgniter. То же самое относится и к последующему совету.

Наш класс LoggingAspect будет LoggingAspect следующим образом:

 <?php class LoggingAspect { function startLog() { echo "Started Logging"; } function endLog(){ } } 

Теперь у нас есть представление о том, как работает метод XML, поэтому давайте перейдем к технике, основанной на комментариях.

Конфигурация на основе комментариев документа

В этой технике у нас нет никаких файлов конфигурации, как у нас с подходом XML. Вместо этого все советы находятся внутри классов аспектов. Мы определяем комментарии в соответствии с предопределенной структурой, а затем можем манипулировать комментариями, используя классы отражения PHP, чтобы применить рекомендации.

 <?php /** * * @Aspect */ class LoggingAspect { /** * @Before:execution[*.*] * */ function startLog() { echo "Started Logging"; } /** * @After:execution[*.*] * */ function endLog() { } } 

Класс LoggingAspect изменился и теперь является классом Aspect использующим комментарий @Aspect . Во время выполнения инфраструктура AOP находит классы аспектов и ищет рекомендации, которые соответствуют определенным точечным вызовам. Совет до и после определяется с помощью @Before и @After и указанного параметра выполнения.

Давайте перейдем к реализации функции applyBeforeAspects() для этого подхода.

 <?php public function applyBeforeAspects() { $uriSegments = $this->CI->uri->segment_array(); $controller = $uriSegments[1]; $function = $uriSegments[2]; $aspectClasses = array("LoggingAspect"); foreach ($aspectClasses as $aspectClass) { $ref = new ReflectionClass($aspectClass); $methods = $ref->getMethods(); foreach ($methods as $method) { $methodName = $method->name; $methodClass = $method->class; $reflectionMethod = new ReflectionMethod($methodClass, $methodName); $refMethodComment = $reflectionMethod->getDocComment(); preg_match('/@Before:execution[(.*?)]/s', $refMethodComment, $match); if (isset($match[1])) { $exprComponents = explode(".", $match[1]); $controllerExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[0]) . "$/"; $functionExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[1]) . "$/"; preg_match($controllerExpr, $controller, $controllerMatch); preg_match($functionExpr, $function, $functionMatch); if (count($controllerMatch) > 0 && count($functionMatch) > 0) { $classObject = new $methodClass(); $classObject->$methodName(); } } } } } 

Для простоты я жестко запрограммировал класс аспектов в массиве, но в идеале все классы аспектов будут извлечены во время выполнения инфраструктурой AOP.

Мы получаем методы каждого аспектного класса, используя отражение. Затем, просматривая каждый метод, мы извлекаем комментарий к документу, определенный перед методом. Мы извлекаем выражение pointcut, сопоставляя комментарий с определенным регулярным выражением. Затем мы продолжаем тот же процесс, что и с методом XML для оставшейся логики.

Резюме

В ходе этой серии вы узнали о концепциях АОП и о том, как определять ситуации, в которых следует применять АОП. И хотя я надеюсь, что вы лучше понимаете, как работает AOP сейчас, имейте в виду, что CodeIgniter не является платформой AOP, поэтому использование хуков не будет самым оптимальным способом применения AOP в ваших проектах. Скорее всего, CodeIgniter использовался в качестве инструмента на протяжении всей серии, чтобы предоставлять знания об АОП с нуля, чтобы вы могли легко адаптироваться к любой новой среде АОП. Я рекомендую вам использовать хорошо известную платформу AOP для крупномасштабных проектов, поскольку она может сделать ваше приложение более сложным, если не использовать его с умом.

Изображение через Fotolia