Пример аспектно ориентированное программирование

PHP и Аспектно-ориентированное программирование

Довольно популярная в мире Java парадигма аспектно-ориентированного программирования (АОП) почему-то слабо освещена в разработке на PHP. В данной статье я хочу представить свой подход к написанию АОП приложений с использованием небольшого фреймворка и модуля.

Кратко о аспектно-ориентированном прогрограммировании

Аспектно-ориентированное программирование — парадигма, основанная на идее разделения функциональности для улучшения разбиения программы на модули.
Кратко, суть подхода заключается в создании функций, запускающихся при определенном событии (например вызов метода). В зависимости от определения, функции запускаются либо до выполнения события, либо после (или же и до, и после), таким образом могут брать на себя довольно разнообразные операции — от логирования до синхронизации информации об объекте с базой данных (persistence).
Условия при которых выполняются такие функции называются “точками соединения” (join points), группы условий “срезами” (point cut), а сами функции зовут “советами” (advice).
Чаще всего советы группируются в аспекты (ровно как методы в классы), таким образом аспект — это набор советов, реализирующих некоторый функционал.

Реализация на PHP

Существует два очевидных метода реализации:
1) создать функции-обертки для всех потенциальных точек соединения, из которых вызывать необходимые советы;
2) внедрить свой код в процесс запуска методов, который будет запускать необходимые советы или не запускать, если нету соответствующих точек соединения.
Главный недостаток первого метода — избыточность. Еще больше задача усложняется при решении внедрить АОП в существующую систему, которая не имеет функций-оберток.
Второй метод выглядит симпатичней, но нуждается в подключении дополнительного расширения к PHP. О нем (методе) и пойдет дальше речь.

Читайте также:  Zentec z036 qms120 программирование настройка портов вентиляция

Модуль MethodIntercept

Как ни странно, но для PHP5+ нету расширения позволяющего перехватывать вызовы методов обьектов. Все что получилось найти это Intercept, разработка которого остановилась на альфа релизе в 2005 году. Расширение умеет выполнять некоторую функцию до и/или после другой функции. С ООП в PHP5 конечно же не работает.
На базе него я написал MethodIntercept, которое кроме перехвата вызова метода также умеет передавать в функцию-перехватчик обьект, чей метод вызван и аргументы переданые методу.
Скомпилировать расширение довольно просто (пример для Linux):

git clone git@github.com:kooler/PAF.git cd PAF/MethodIntercept phpize ./configure make

В результате в папке modules появится файл intercept.so, который нужно скопировать в папку с расширениями PHP (её можно узнать выполнив php -i | grep extension_dir) и добавив в php.ini: extension=intercept.so.
Если все описаное выше прошло успешно, появится возможность использовать функцию intercept_add, которая принимает 3 параметра: класс->метод, который нужно перехватить, функцию которую нужно вызвать, метод перехвата — до или после.

PHP Aspect Framework (PAF)

Не смотря на то, что MethodIntercept позволяет внедрять свой код в процесс запуска методов, этого не достаточно для полноценной работы с АОП по ряду причин:
1. Расширение не поддерживает очередь — можно указать только одну функцию-перехватчик.
2. Перехватчик может быть только функцией и не может быть методом, соответственно группировка советов в аспекты невозможна.
3. Указывать перехватчик вызовом функции (intercept_add) не очень удобно.
Дописывать реализацию всего вышеперечисленого в MethodIntercept не целесообразно, так как его задача перехватывать вызов метода, а не обеспечивать все необходимые для АОП плюшки (возможно для этого стоит написать отдельное расширение).
Поэтому я решил написать свой мини-фреймворк, который упрощает использование АОП, а именно:
1. Позволяет определять точки соединения при помощи аннотаций
2. Берет на себя формирование очереди вызова и выполнение intercept_add
3. Позволяет создавать аспекты
4. Позволяет использовать регулярные выражения при определении точок соединения
Фреймворк называется PHP Aspect Framework (или кратко PAF): github.com/kooler/PAF

Читайте также:  Язык программирования компании apple

Классический пример АОП — логирование

Рассмотрим применение фреймворка на классическом примере использования АОП — логирование.
Допустим имеем класс:

class Backet < public function order() < //оформление корзины >public function createNew() < //создание корзины >>

Предположим, что мы хотим выводить сообщение (или писать в лог) каждый раз, когда пользователь оформляет или создает корзину. Создадим аспект Logger, который будет включать два совета: первый должен вызываться после оформление корзины, а второй после создания:

class Logger extends Aspect < /* * @After(Backet->order) */ public function backetOrderMessage($params) < echo ‘Backed has been ordered’; >/* * @After(Backet->createNew) */ public function backetCreatedMessage($params) < echo ‘Backet has been created’; >>
AspectRegistry::getInstance()->addAspect(new Logger); AspectRegistry::getInstance()->interceptAll();

Последняя должна вызываться только раз, после регистрации всех аспектов, именно она отвечает за построение очереди и выполнение функции intercept_add.
В каждый совет передается один аргумент — массив, первый элемент которого содержит обьект, чей метод был перехвачен, а второй аргументы переданыe перехваченому методу. Таким образом, если например нужно вывести имя пользователя при оформлении заказа, cовет будет выглядеть так:

class Backet < public $username; … >class Logger extends Aspect < /* * @After(Backet->order) */ public function backetOrderMessage($params) < echo ‘Backed has been ordered by user: ’.$params[0]->username; > . >

Заключение

Сейчас фреймворк на довольно ранней стадии — есть куда расти. Планирую дописать возможность подключения плагинов, реализовать синхронизацию обьектов с базой (persistance) и многое другое. Судя по популярности в Java, парадигма довольно интересная и имеет право на жизнь в PHP.
Буду благодарен за любые советы и идеи, а также нужно ли вообще АОП в PHP.

Источник

Пример использования аспектно-ориентированного программирования + GRADLE + DB + Intellij

Обложка: Пример использования аспектно-ориентированного программирования + GRADLE + DB + Intellij

Аспектно-ориентированное программирование (АОП) не так широко известно и применяется на практике гораздо реже, чем объектно-ориентированное (ООП). Однако существуют задачи, плохо поддающихся решению с помощью ООП. Например, логирование, профилирование, вопросы безопасности и других классов и объектов, не имеющих между собой непосредственной или косвенной связи и объединяемые по логическим, а не физическим аспектам.

Начинающим программистам обычно довольно сложно применять АОП из-за нехватки опыта. Но разобраться с технологией можно. Для примера возьмём реальный Java-проект существующего backend-модуля, опустим лишние детали и реализуем конкретную задачу профилирования интересующего нас участка кода. Начнём с «самого низа» и постепенно разовьём идею применения АОП.

Зачем нужно аспектно-ориентированное программирование

Прежде всего выясним, для чего нам понадобилось задействовать механизм АОП. Допустим, поставлена задача: оценить производительность таблиц с движком ReplaceMergeTree базы данных ClickHouse (движок в фоне производит попытку удаления дубликатов записей на основе упорядочения определенного набора полей). Выполнить задачу нужно на базе существующего backend-модуля, в котором уже есть готовые сервисные слои для записи в базу.

Допускаю, что сразу может захотеться дополнить сервис для записи логом, часами типа StopWatch и ещё чем-то подобным, чтобы смотреть, сколько времени уходит на запись. Но в начале стоит выяснить главную особенность данного типа движка — характер поведения удаления дубликатов. А для этого нужно периодически делать запросы в базу и смотреть, что там осталось. Возможно, в процессе понадобится что-то ещё.

Уже набегает на целый класс или сервис, который необходимо подключать. С одним-двумя. классами можно справиться. А если их десять? А если при этом надо ещё что-то менять и «подкручивать»? И что делать, если код оказался унаследованным в виде библиотеки и неизменным? Обычных принципов ООП тут не хватает. И нужно применить способ, позволяющий реализовывать сквозной функционал. Аспектно-ориентированное программирование как раз для этих целей.

Пример работы с АОП

Как только разобрались, зачем нам нужен АОП, переходим к поиску существующих инструментов. В сети находим:

  • Spring AOP. Он на базе динамического заместителя CGLIB и заместителя JDK. Само связывание динамическое. Хорошо, но подходит, если модуль создан на Spring, у нас — нет;
  • Веб-контейнер и EJB-контейнер, но у нас не Java EE;
  • AspectJ. Библиотека от Eclipse. Здесь уже статическое связывание, но пересобрать проект всегда есть возможность. Останавливаемся на этой библиотеке, как удовлетворяющей нашим требованиям.

Не путать с AssertJ! Это тоже крутая штука, заслуживающая отдельного внимания.

Смотрим на чём основан проект. Так как у нас в качестве билдера будет использоваться Gradle 4.8 (старый, но ничего страшного), то будем рассматривать на его примере. Для него есть плагин freefair — как раз для аспектов. Подключим его в наш билд, а именно, в репозиторий buildscript добавим блок:

В зависимости dependencies того же блока добавим строку:

classpath "io.freefair.gradle:aspectj-plugin:6.0.0-m2"

Отлично, теперь подключаем плагин со всеми его задачами, источниками данных, конфигурациями и так далее:

apply plugin: 'io.freefair.aspectj.post-compile-weaving'

Вот, собственно, и всё. Больше ничего добавлять нет необходимости, никаких конфигураций и источников объявлять не нужно.

Обратимся к интересующему нас участку кода:

Есть метод, записывающий коллекцию данных в таблицу, который надо отпрофилировать. Для реализации профайлера напишем класс и применим в нём необходимые аннотации и ключевые слова (рис.1). Разберём, что получилось.

На рисунке 1 представлен класс, перехватывающий вызов метода writeDomainsToCHTable. Для этого был описан так называемый PointCut-срез (строка 25). Реализация срезов позволяет перехватывать вызовы методов по сигнатурам, аннотациям, генерируемым исключениям, названиям и так далее.

Функционал очень развит, позволяет делать много интересного. Применяется для этого Expression Language (EL). Описав необходимые срезы (а их может быть более одного), нужно описать необходимые интерцепторы с нужной логикой. В данном примере описано поведение до вызова метода (аннотация @Before), перехват вызова (аннотация @Around) и после вызова (аннотация @After).

Рисунок 1. Реализация профилировщика на базе концепций АОП и библиотеки AspectJ.

До вызова метода необходимо сделать запрос в базу данных и выяснить сколько там записей, а также сбросить таймер. Во время вызова включить таймер, «прогнать» метод (строка 42) и зафиксировать время выполнения метода, а после выполнения вывести в лог отчёт (строка 48). Внутри аннотаций перехватов указывается тот срез или комплект срезов, на базе которого будут срабатывать интерцепторы.

Стоит отметить, что поведение интерцепторов очень схоже со слежением за жизненным циклом или процессом валидации (javax.validation) в EJB-контейнерах. Поэтому тут нет никакой магии.

Что получилось?

Разберём, как работает разработанный профайлер. Когда выполнение кода доходит до точки вызова метода ActivityAppendableSynchronizer::writeDomainsToCHTable происходит перехват этого вызова, так как после компиляции у нас уже есть некий класс, который фактически проксирует прокинутые срезы. Непосредственно перед вызовом испытуемого метода выполнение попадает на строку 30. Выполнившись до конца, переход осуществиться на строку 41. Выйдя из метода aroundSynch, управление передается в строку 48.

Таким образом, правильно описав срезы и определив логику в событийных точках к этим срезам, можно создавать аспекты (аннотация @Aspect), которые будут применять сквозной функционал к обозначенным срезам. Данный пример можно применять на любом реальном методе, никаких изменений не потребуется. На мой взгляд, применение принципов АОП не сложнее, чем ООП.

Возможности аспектно-ориентированного программирования позволяют реализовывать сколь угодно сложное проксирование широкого круга задач, главное желание. Подробности можно посмотреть на официальном сайте библиотеки. Спасибо.

Источник

Оцените статью