Pull to refresh

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

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

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


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

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


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

Модуль 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

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


Рассмотрим применение фреймворка на классическом примере использования АОП — логирование.
Допустим имеем класс:
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;
	}
	...
}

Полный код похожего примера: github.com/kooler/PAF/blob/master/Framework/example.php

Заключение


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

Articles