Comments 48
Начали за здравие, закончили за упокой. В конце, такое чувство, что вы торопились, быстрей бы написать статью, и в итоге, последние примеры проглотились.
Добавьте как правильно вызывать, то что вы описывали, чтобы не нужно было после такой обьемной статьи, бегать по ссылками и искать, как будет выглядеть измененный метов в реальности.
И все будет крайне отлично )
Добавьте как правильно вызывать, то что вы описывали, чтобы не нужно было после такой обьемной статьи, бегать по ссылками и искать, как будет выглядеть измененный метов в реальности.
И все будет крайне отлично )
Круто- использовал АОП в Spring`e, но даже не подозревал, что есть реализация для php.
По-сути, реализация этой библиотеки и представляет собой портированную версию Spring в PHP. Изучение Spring и AspectJ дало мне много полезной информации и пищи к размышлению. Я сейчас работаю над стандартизацией интерфейсов для АОП в PHP, они будут совместимы с аналогичными в Java: aopalliance/php. Возможно, удастся добиться совместимости между разными фреймворками и библиотеками в PHP как в Java.
Сами аспекты описываются классами, как в Spring-е и используются аннотации для задания поинткатов, однако есть возможность создавать Advisor-ы сразу в коде. Каждый аспект разбивается на серию независимых советников, которые регистрируются отдельно.
static $memoryCache — так и задумывалось, потому что один аспект связывается со всеми классами и более того, аспект — это синглтон, он регистрироваться должен только один раз.
static $memoryCache — так и задумывалось, потому что один аспект связывается со всеми классами и более того, аспект — это синглтон, он регистрироваться должен только один раз.
А можно пример с кэшированием, когда в качестве ключа используется не имя метода, а один из параметров? Например, getUser($id) очевидно будет кэшировать запись с ключом = $id, в не «getUser»
Да, конечно, нужно просто при формировании ключа для кэширования добавить еще сами параметры метода, которые доступны через вызов
$invocation->getArguments()
:// ...
$key = $class . ':' . $invocation->getMethod()->name . join(':', $invocation->getArguments());
// ...
… не забыв хотя бы отсортировать их содержимое.
Т.к. getUser($id, array $properties) сразу поломает вам всю модель.
И сразу возникает вопрос контроля за ключами подобного кеша, которые иногда необходимо сбрасывать.
Т.к. getUser($id, array $properties) сразу поломает вам всю модель.
И сразу возникает вопрос контроля за ключами подобного кеша, которые иногда необходимо сбрасывать.
Ага, как известно есть две основных проблемы в IT: инвалидация кеша и именование переменных. Тут уже нужно думать в каждом конкретном случае, как делать инвалидацию.
В принципе, это возможно с помощью аспектов и метаинформации. Например, для метода
В принципе, это возможно с помощью аспектов и метаинформации. Например, для метода
update($id, $value)
можно использовать аннотацию @CacheInvalidator(get($id)), по которой совет поймет, что ему нужно сбрасывать кэш для метода get($id)
с нужным значением $id.Возможно, я покажусь тупым, но всё же спрошу.
Всё это мне кажется интересным, но примеры не до конца понятные.
Изначальные утверждения о простоте для меня обернулись тем, что я даже примеры до конца не понял. Вы вызываете методы своей библиотеки, но что они делают мне до конца не понятно.
И так вопрос: почему не сделать предельно простой класс в одним-двумя методами, в которых пустое либо в одну строчку тело, к которому добавляется логирование, потом добавить примитивный класс логирования и показать, как это работает, на пальцах, чтобы не было ни одной строчки, которую можно выбросить?
Всё это мне кажется интересным, но примеры не до конца понятные.
Изначальные утверждения о простоте для меня обернулись тем, что я даже примеры до конца не понял. Вы вызываете методы своей библиотеки, но что они делают мне до конца не понятно.
И так вопрос: почему не сделать предельно простой класс в одним-двумя методами, в которых пустое либо в одну строчку тело, к которому добавляется логирование, потом добавить примитивный класс логирования и показать, как это работает, на пальцах, чтобы не было ни одной строчки, которую можно выбросить?
Note that PHP versions before 5.4.0 will not work completely, if you try to use aspects for code that uses Late Static Binding (LSB) feature.
В чем дело? LSB в PHP поддерживается с версии 5.3
Ожидал этого вопроса. Вопрос на самом деле очень сложный в технической реализации и объяснении.
Но попробую объяснить. Когда перехватываются динамические методы — там все просто, мы всегда знаем объект благодаря
Со статическими методами все намного сложнее — нужно переопределить статический метод, после этого вызвать оригинальный метод через Reflection. А в PHP сейчас есть сложность с тем, чтобы указать scope при вызове статического метода через рефлексию. Вот поэтому и ломается LSB. Только в 5.4 можно создать замыкание в дочернем классе, которое форвардит запрос к родительскому методу и сменить ему scope на корректный через метод
Но попробую объяснить. Когда перехватываются динамические методы — там все просто, мы всегда знаем объект благодаря
$this
. Поэтому нам не нужно указывать scope. Со статическими методами все намного сложнее — нужно переопределить статический метод, после этого вызвать оригинальный метод через Reflection. А в PHP сейчас есть сложность с тем, чтобы указать scope при вызове статического метода через рефлексию. Вот поэтому и ломается LSB. Только в 5.4 можно создать замыкание в дочернем классе, которое форвардит запрос к родительскому методу и сменить ему scope на корректный через метод
bindTo()
Посмотрел на AspectPHP, странно, что он мне не попался раньше.
Да, идея аналогичная — делать load-time рефлексию кода, но проанализировав ее, я понял, что скорость работы конечного кода (с вплетенными аспектами) тут явно не учитывалась, тогда как Go! и создавался вокруг этой цели. Отсюда я могу сделать предположение, что попытка использовать в боевом режиме AspectPHP на большом количестве классов — будет обречена на провал из-за низкой скорости конечного кода.
Помимо этого, AspectPHP не учитывает необходимость корректной работы с опкод-кэшером, поэтому будут проблемы с этим в боевом режиме. Ну и еще Go! умеет перехватывать обращения к публичным и защищенным свойствам объектов (с ограничениями, правда), а также умеет работать с метаинформацией классов в load-time благодаря интеграции с Doctrine2. Скоро еще будет Introduction для PHP 5.4 с использованием трейтов.
Да, идея аналогичная — делать load-time рефлексию кода, но проанализировав ее, я понял, что скорость работы конечного кода (с вплетенными аспектами) тут явно не учитывалась, тогда как Go! и создавался вокруг этой цели. Отсюда я могу сделать предположение, что попытка использовать в боевом режиме AspectPHP на большом количестве классов — будет обречена на провал из-за низкой скорости конечного кода.
Помимо этого, AspectPHP не учитывает необходимость корректной работы с опкод-кэшером, поэтому будут проблемы с этим в боевом режиме. Ну и еще Go! умеет перехватывать обращения к публичным и защищенным свойствам объектов (с ограничениями, правда), а также умеет работать с метаинформацией классов в load-time благодаря интеграции с Doctrine2. Скоро еще будет Introduction для PHP 5.4 с использованием трейтов.
Кажется красивее будет Traits использовать из php 5.4
Как я понял, навешивание аспектов происходит через Reflection? Можете сказать, какой оверхед появляется за счет этого?
Само вплетение аспектов проводится ровно один раз через load-time рефлексию, оверхед не измерял, потому что он не имеет особого значения в режиме нормальной работы приложения. Код с вплетенными аспектами кладется в кэш и дальше вызываются сразу нужные методы с нужными обработчиками. Никакой runtime-проверки на различные условия не выполняется. Все советы (Closure) сериализуются в самом коде класса и дальше могут быть закэшированы опкод-кэшером.
Выполнение методов с советами максимально оптимизировано и сопоставимо со скоростью выполнения метода при добавлении аналогичного совета для метода на уровне С, к примеру, с помощью экстеншена PHP-AOP.
Замерил скорость выполнения кода с советами в Go! и экстеншена PHP-AOP. Тест: 10000 итераций, один совет, в котором мы выводим на экран перед вызовом метода класса его имя, имя метода и аргументы. Результат:
Экстеншен: 428-604 мс
Моя либа: 502-710 мс
Без советов: 50-60мс
Выполнение методов с советами максимально оптимизировано и сопоставимо со скоростью выполнения метода при добавлении аналогичного совета для метода на уровне С, к примеру, с помощью экстеншена PHP-AOP.
Замерил скорость выполнения кода с советами в Go! и экстеншена PHP-AOP. Тест: 10000 итераций, один совет, в котором мы выводим на экран перед вызовом метода класса его имя, имя метода и аргументы. Результат:
Экстеншен: 428-604 мс
Моя либа: 502-710 мс
Без советов: 50-60мс
Уже где-то использовали это решение в продакшене?
В настоящий момент — еще нет, но само решение к продакшену готово.
Поэтому сразу предупреждение для всех — разворачивайте код с аспектами параллельно, а не вместо основного кода.
Фишка АОП в том, что вы не меняете код приложения, поэтому можно написать код и включать аспекты только при наличии волшебного параметра в запросе — это делается запросто. Нет параметра в запросе — не используем аспекты вообще, никаких изменений в логике приложения. Есть наш заветный ключик — переключаемся именно для нашего запроса на код с аспектами. После того, как проверите, что все ок — убираем наш ключик и переходим на аспекты.
Поэтому сразу предупреждение для всех — разворачивайте код с аспектами параллельно, а не вместо основного кода.
Фишка АОП в том, что вы не меняете код приложения, поэтому можно написать код и включать аспекты только при наличии волшебного параметра в запросе — это делается запросто. Нет параметра в запросе — не используем аспекты вообще, никаких изменений в логике приложения. Есть наш заветный ключик — переключаемся именно для нашего запроса на код с аспектами. После того, как проверите, что все ок — убираем наш ключик и переходим на аспекты.
медленнее обычного кода в 10 раз, ну это как то круто, чтобы использовать в реальных проектах, не думаю, что кто-то может себе такое позволить использовать.
Где реально это может пригодиться?
Где реально это может пригодиться?
Это практически никакой оверхед. Покажите мне код, который за свой цикл Request-Process-Response исполнит 10к советов.
Уточнение: не в 10 раз, потому что если бы не было вынесенного кода в совете, то он бы был в самом методе, точнее, повсюду в методах в разных формах и вариациях. По-факту, при одинаковом функционале в коде совета и в коде метода получается пропорция 4:1-5:1, что весьма неплохо.
Тут огорчу любителей паттерна «Прокси» — у них этот же код работает еще медленней, чем код с аспектом за счет использования __call(), func_get_args() и call_user_func_array(). Но об этом никто не вспоминает при сравнении с АОП, а ведь надо сравнивать с альтернативой аналогичного кода.
Идем дальше, берем реальные цифры. Вызов одного метода без аспекта, но с такой же логикой — 10 мкс, с аспектом — 50 мкс. В случае с хайлоадом это очень и очень важно, а если вы работаете над бизнес-приложениями, как я, то для вас не будут новинкой методы, работающие и несколько минут, но для которых очень важно иметь читаемый и удобный код, который постоянно нужно править.
И последний довод — аспекты используются для тех методов, которые вызываются редко, но они очень важны, оверхед в пару десятков мкс на них даже не заметен. И понятно, что указывать все методы всех классов и логировать все, получая замедление в 5 раз — выстрел в себе в ногу.
Все нужно использовать с умом и правильно, как и любой другой инструмент.
Тут огорчу любителей паттерна «Прокси» — у них этот же код работает еще медленней, чем код с аспектом за счет использования __call(), func_get_args() и call_user_func_array(). Но об этом никто не вспоминает при сравнении с АОП, а ведь надо сравнивать с альтернативой аналогичного кода.
Идем дальше, берем реальные цифры. Вызов одного метода без аспекта, но с такой же логикой — 10 мкс, с аспектом — 50 мкс. В случае с хайлоадом это очень и очень важно, а если вы работаете над бизнес-приложениями, как я, то для вас не будут новинкой методы, работающие и несколько минут, но для которых очень важно иметь читаемый и удобный код, который постоянно нужно править.
И последний довод — аспекты используются для тех методов, которые вызываются редко, но они очень важны, оверхед в пару десятков мкс на них даже не заметен. И понятно, что указывать все методы всех классов и логировать все, получая замедление в 5 раз — выстрел в себе в ногу.
Все нужно использовать с умом и правильно, как и любой другой инструмент.
nikita2206 меня опередил с изящным простым ответом )
Напоминает попытку реализовать декораторы.
Например как в python
Выдает:
А с простым «кеширующим» декоратором:
Выдает:
def get(key):
print 'get'
return 'get(%s)' % key
print get(42)
print get(42)
Выдает:
get
get(42)
get
get(42)
А с простым «кеширующим» декоратором:
def cache(func):
_dict = {}
def _cache(*args, **kawrgs):
if func not in _dict:
_dict[func] = func(*args, **kawrgs)
return _dict[func]
return _cache
@cache
def get(key):
print 'get'
return 'get(%s)' % key
print get(42)
print get(42)
Выдает:
get
get(42)
get(42)
Canonical uses of function decorators are for creating class methods or static methods, adding function attributes, tracing, setting pre- and postconditions, and synchronisation, but can be used for far more besides, including tail recursion elimination, memoization and even improving the writing of decorators.
к сожалению декораторы создают ещё один объект(класс или функцию) а то и не один :(
с другой стороны не такой уж оверхед. один раз делается же.
но в php нет таких удобных декораторов :)
с другой стороны не такой уж оверхед. один раз делается же.
но в php нет таких удобных декораторов :)
А вы смотрели ли на JMSAopBundle?
Ну это Symfony-coupled код, а Go не зависит от платформы
Да, помимо завязки на контейнер симфони и ее архитектуру, есть еще несколько отличий. Если попробовать перехватить статический метод — JMSAopBundle отдыхает, перехватить метод в финальном классе — опять отдыхает. Пишем явно
Однако создание декораторов и подмена их в классе весьма неплохое решение и для работы в Symfony2 с АОП я его рекомендую больше, чем Go! Но вот не все он умеет, далеко не все…
(new Class())->methodName()
— опять отдыхает. У меня будет еще Introduction и перехват финальных методов, чем JMSAopBundle еще не скоро обзаведется.Однако создание декораторов и подмена их в классе весьма неплохое решение и для работы в Symfony2 с АОП я его рекомендую больше, чем Go! Но вот не все он умеет, далеко не все…
Утром на Хабре — вечером в PHP Weekly. Только что пришла рассылка. :)
И поздравляю с топ1 по Most Starred Today в PHP на гитхабе.
И поздравляю с топ1 по Most Starred Today в PHP на гитхабе.
Если попытаться завернуть кэширующий прокси поверх логирующего, а тот, в свою очередь, поверх основного класса, то скорость упадет на порядок.Странно, потому как на моей практике профилирования кешируюших/логируюших прокси классов, время вызова этих __call & __get в среднем на порядки меньше вызова самих кеширующих/логирующих методов. Ну т.е. издержки от их использования составляют 0.1-0.5%.
Все верно подмечено, что вызов даже магического метода будет быстрее если он отдаст данные из кэша. Это же справедливо и для аспектов, только работает еще быстрее, потому что там нет вообще этих магических методов, только прямые вызовы.
На самом деле гораздо больше неприятностей доставляет тот факт, что использование прокси-классов не дает возможности использовать в методах указание ожидаемых интерфейсов и классов.
На самом деле гораздо больше неприятностей доставляет тот факт, что использование прокси-классов не дает возможности использовать в методах указание ожидаемых интерфейсов и классов.
Напоминает триггеры в СУБД
статья интересная, спасибо. но мне интересно, нельзя ли избавиться вот от этого
вроде бы это как раз то, против чего и направлена мысль, озвученная в посте?
$obj = $invocation->getThis();
$class = is_object($obj) ? get_class($obj) : $obj;
$key = $class . ':' . $invocation->getMethod()->name;
вроде бы это как раз то, против чего и направлена мысль, озвученная в посте?
Да, выглядит некрасиво. Но даже если это не реализовано автором, всегда можно
самостоятельно завернуть
Ну или вернуть stdClass/отдельный класс с тремя публичными полями…
class MyInvocation extends InvocationClass
{
public function foo()
{
$obj = $this->getThis();
$class = is_object($obj) ? get_class($obj) : $obj;
$key = $class . ':' . $this->getMethod()->name;
return array($obj, $class, $key);
}
}
...
/**
* @var MyInvocation $invocation
*/
list($obj, $class, $key) = $invocation->foo();
Ну или вернуть stdClass/отдельный класс с тремя публичными полями…
Буквально в первом абзаце упоминается термин АОП, а его расшифровка появляется только под конец. Немного нелогично. А так в целом статья на хорошо!
В теории красиво, а есть какой-нибудь крупный проект, выполненный по этой методологии, который можно было бы выложить в открытый доступ?
Нет, в открытом доступе пока ничего нет. Но есть примеры в самой библиотеке, а также у меня на гитхабе есть пару тестовых приложений для ZF2 и SF2, которые работают с АОП. Ссылка на ZF2 есть в списке ссылок. Это как раз сделано для практики и оценки скорости работы.
Уважаемое хабрасообщество, что еще вам было бы интересно узнать по АОП в PHP? Также интересно получить обратную связь от тех, кто попробовал установить библиотеку и поработал с аспектами.
Я знаю, что есть небольшая парочка умельцев, которые не побоялись засучить рукава и поставить библиотеку на своем локальном компьютере )
Я знаю, что есть небольшая парочка умельцев, которые не побоялись засучить рукава и поставить библиотеку на своем локальном компьютере )
АОП это круто. Но создание аспектов выглядит пока что страшновато.
github.com/lisachenko/go-aop-php#5-create-an-aspect
Планируется ли возможность добавления в виде анонимных функций? Все таки это php а не java.
github.com/lisachenko/go-aop-php#5-create-an-aspect
Планируется ли возможность добавления в виде анонимных функций? Все таки это php а не java.
Думаю, что код большого приложения без аспектов выглядит еще более страшновато )
А теперь по-делу: задавать советы в виде анонимных функции — нет проблем. Внутри библиотека так и работает. Каждый аспект разбивается на серию советников (Advisor), которая регистрируется в контейнере. Советник по своей природе — это пара, состоящая из регулярного выражения для точки кода и замыкания (совета), полученного из метода аспекта. Поэтому можно легко регистрировать в ядре свои советники напрямую, API для этого уже есть.
Это фундамент на будущее, чтобы можно было загружать аспекты откуда угодно: из xml, из yml, с помощью CompilerPass-ов и другого. Однако использование аспектов — более предпочтительно, потому что позволяет структурировать код АОП в логические блоки, как классы в ООП. Тогда как советник в АОП сопоставим с простой функцией в ООП.
А теперь по-делу: задавать советы в виде анонимных функции — нет проблем. Внутри библиотека так и работает. Каждый аспект разбивается на серию советников (Advisor), которая регистрируется в контейнере. Советник по своей природе — это пара, состоящая из регулярного выражения для точки кода и замыкания (совета), полученного из метода аспекта. Поэтому можно легко регистрировать в ядре свои советники напрямую, API для этого уже есть.
Это фундамент на будущее, чтобы можно было загружать аспекты откуда угодно: из xml, из yml, с помощью CompilerPass-ов и другого. Однако использование аспектов — более предпочтительно, потому что позволяет структурировать код АОП в логические блоки, как классы в ООП. Тогда как советник в АОП сопоставим с простой функцией в ООП.
Sign up to leave a comment.
Избавляемся от дублирования сквозного кода в PHP: рефакторинг кода с АОП