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


Всем привет!

Закончили в этом месяце первый набор курса «Backend разработчик на PHP» и трудоустраиваем их вовсю (ну насколько это возможно в пору отпусков). Курс пополнился ещё одним преподавателем — Евгением Волосатовым, которого многие, наверное, знают. Ну, а мы традиционно делимся интересными вещами.

Поехали.

В любом приложении есть части кода, “пересекающие” несколько частей архитектуры одновременно.

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



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


Аспектно-ориентированное программирование (АОП) — парадигма программирования, сфокусированная на организации и модульности сквозной функциональности. Кейсы применения — ACL, логирование, обработка ошибок, кэширование.

Встроенный (внутренние) предположения PHP (когда вы определяете функцию/константу/класс, она остается определенной навсегда) делают парадигму АОП сложной для имплементации.

Li3 был первым, решившим проблему сквозной функциональности с помощью механизма фильтрации, позволяющего фильтровать логику метода через замыкания. Чтобы сделать метод фильтруемым, имплементация Li3 требует мануального добавления шаблонного кода. С такими ограничениями, техники АОП ограничиваются методами фильтрации.

Другие хорошо известные реализации АОП на PHP:

  • AOP (расширение PECL)
  • Go!

Расширение PECL AOP — интересный, но в то же время рискованный подход, так как поддержка PECL расширений не распространена. Другой вариант — библиотеки Go!, представляющие собой реализацию АОП, исправляющей PHP код на лету, что делает возможным использование методов АОП.

Есть и другие реализации, но большинство из них строятся на базе прокси (насколько мне известно), а у такого подхода множество ограничений.

Новый парень на районе


Автоматическая генерация кода давно есть в PHP и используется во многих библиотеках, например ProxyManager. А благодаря принятию Composer, Go! показывает, что правка кода на лету возможна.

Если генерацию кода еще можно посчитать простой, то исправление кода несколько сложнее. Во-первых, в PHP нет встроенного парсера кода, а во-вторых, очень мало библиотек, решающих проблемы парсинга PHP кода. Самая известная — библиотека PHP-Parser. PHP-Parser — отличный инструмент, но даже он игнорирует форматирование пробелов в сгенерированных абстрактных синтаксических деревьях. Что затрудняет исправление кода. Действительно, код, который нужно исправить, является настоящим исполняемым кодом. Поэтому, если хотите, чтобы обратная трассировка была точна при ошибках, нужно учитывать номера строк в исправленном файле.

Для этой задачи мы используем патчер JIT кода Kahlan. Kahlan — новый тестовый фреймворк Unit & BDD, благодаря JIT техникам правок, позволяющий stub’ить и monkey patch’ить код прямо в Ruby или JavaScript. Под капотом обнаружим, что данная библиотека основана на рудиментарном парсере PHP. Но, тем не менее, он достаточно быстрый и стабильный, чтобы нам подойти.

Библиотека фильтров доступна на github.com/crysalead/filter и может быть использована следующим образом.

Во-первых, патчер JIT кода должен быть инициализирован как можно быстрее (например, сразу после включения композера autoloade):

include __DIR__ . '/../vendor/autoload.php';

use Lead\Filter\Filters;

Filters::patch(true);

Заметим, что правка кода возможна только для классов, загруженных autoload’ером Composer. Если класс добавлен при помощи require или include выражения, он уже загружен перед вызовом Filters::patch(true), и поэтому не будет исправлен.

По умолчанию, весь исправленный код будет храниться по адресу /tmp/jit, но вы всегда можно изменить его на свой:

Filters::patch(true, ['cachePath' => 'my/cache/path/jit']);

Кэшированные файлы будут восстановливаться автоматически каждый раз при изменении PHP файла.

Внимание! Filters::patch(true) — самый простой способ настройки патчера, имейте в виду, что весь ваш код будет исправлен. Чтобы завернуть все методы вашей базы кода в фильтр-замыкание, может потребоваться много времени.

К счастью, если сквозной функционал играет решающую роль в хорошо продуманном коде, потребуется только пара методов в проекте. Поэтому предпочтительным подходом будет исправление только тех методов, что будут отфильтрованы:

Filters::patch([
 'A\ClassName',
 'An\Example\ClassName::foo',
 'A\Second\Example\ClassName' => ['foo', 'bar'],
], [
    'cachePath' => 'my/cache/path/jit',
]);

Таким образом, вы можете выбрать исправление всех методов определенного класса, только одного метода или нескольких из них.

API Фильтр


Теперь, когда JIT патчер включен, создадим фильтр логирования:

use Chaos\Filter\Filters;
use Chaos\Database\Database;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('database');
$logger->pushHandler(new StreamHandler('my/log/path/db.log', Logger::WARNING));

Filters::apply(Database::class, 'query', function($next, $sql, $data, $options) use ($logger) {
    $logger->info('Ran SQL query: ' . $sql);
    return $next($sql, $data, $options);
});

В приведенном выше примере создается фильтр регистрации SQL запросов для библиотеки базы данных Chaos. Больше информации о API фильтре можно узнать на github.com/crysalead/filter.

Заключение


АОП, по моему мнению, настоящий ответ для сквозного функционала. Все прочие абстракции одинаково излишни и в большинстве случае ограничены определенными рамками. Я не теряю надежду, что однажды PHP предоставит встроенный API для аспектно-ориентированного программирования. Но сейчас, я думаю, патчинг JIT кода — лучший вариант, где преимущества значительно перевешивают оверхэд CPU, которым можно практически пренебречь, когда патчинг JIT кода не применяется глобально.

THE END

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




К сожалению, не доступен сервер mySQL