Comments 48
Начали за здравие, закончили за упокой. В конце, такое чувство, что вы торопились, быстрей бы написать статью, и в итоге, последние примеры проглотились.
Добавьте как правильно вызывать, то что вы описывали, чтобы не нужно было после такой обьемной статьи, бегать по ссылками и искать, как будет выглядеть измененный метов в реальности.
И все будет крайне отлично )
Добавьте как правильно вызывать, то что вы описывали, чтобы не нужно было после такой обьемной статьи, бегать по ссылками и искать, как будет выглядеть измененный метов в реальности.
И все будет крайне отлично )
+3
Круто- использовал АОП в Spring`e, но даже не подозревал, что есть реализация для php.
0
По-сути, реализация этой библиотеки и представляет собой портированную версию Spring в PHP. Изучение Spring и AspectJ дало мне много полезной информации и пищи к размышлению. Я сейчас работаю над стандартизацией интерфейсов для АОП в PHP, они будут совместимы с аналогичными в Java: aopalliance/php. Возможно, удастся добиться совместимости между разными фреймворками и библиотеками в PHP как в Java.
+2
UFO just landed and posted this here
Сами аспекты описываются классами, как в Spring-е и используются аннотации для задания поинткатов, однако есть возможность создавать Advisor-ы сразу в коде. Каждый аспект разбивается на серию независимых советников, которые регистрируются отдельно.
static $memoryCache — так и задумывалось, потому что один аспект связывается со всеми классами и более того, аспект — это синглтон, он регистрироваться должен только один раз.
static $memoryCache — так и задумывалось, потому что один аспект связывается со всеми классами и более того, аспект — это синглтон, он регистрироваться должен только один раз.
0
А можно пример с кэшированием, когда в качестве ключа используется не имя метода, а один из параметров? Например, getUser($id) очевидно будет кэшировать запись с ключом = $id, в не «getUser»
+1
Да, конечно, нужно просто при формировании ключа для кэширования добавить еще сами параметры метода, которые доступны через вызов
$invocation->getArguments()
:// ...
$key = $class . ':' . $invocation->getMethod()->name . join(':', $invocation->getArguments());
// ...
+3
… не забыв хотя бы отсортировать их содержимое.
Т.к. getUser($id, array $properties) сразу поломает вам всю модель.
И сразу возникает вопрос контроля за ключами подобного кеша, которые иногда необходимо сбрасывать.
Т.к. getUser($id, array $properties) сразу поломает вам всю модель.
И сразу возникает вопрос контроля за ключами подобного кеша, которые иногда необходимо сбрасывать.
0
Ага, как известно есть две основных проблемы в IT: инвалидация кеша и именование переменных. Тут уже нужно думать в каждом конкретном случае, как делать инвалидацию.
В принципе, это возможно с помощью аспектов и метаинформации. Например, для метода
В принципе, это возможно с помощью аспектов и метаинформации. Например, для метода
update($id, $value)
можно использовать аннотацию @CacheInvalidator(get($id)), по которой совет поймет, что ему нужно сбрасывать кэш для метода get($id)
с нужным значением $id.0
Возможно, я покажусь тупым, но всё же спрошу.
Всё это мне кажется интересным, но примеры не до конца понятные.
Изначальные утверждения о простоте для меня обернулись тем, что я даже примеры до конца не понял. Вы вызываете методы своей библиотеки, но что они делают мне до конца не понятно.
И так вопрос: почему не сделать предельно простой класс в одним-двумя методами, в которых пустое либо в одну строчку тело, к которому добавляется логирование, потом добавить примитивный класс логирования и показать, как это работает, на пальцах, чтобы не было ни одной строчки, которую можно выбросить?
Всё это мне кажется интересным, но примеры не до конца понятные.
Изначальные утверждения о простоте для меня обернулись тем, что я даже примеры до конца не понял. Вы вызываете методы своей библиотеки, но что они делают мне до конца не понятно.
И так вопрос: почему не сделать предельно простой класс в одним-двумя методами, в которых пустое либо в одну строчку тело, к которому добавляется логирование, потом добавить примитивный класс логирования и показать, как это работает, на пальцах, чтобы не было ни одной строчки, которую можно выбросить?
0
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
0
Ожидал этого вопроса. Вопрос на самом деле очень сложный в технической реализации и объяснении.
Но попробую объяснить. Когда перехватываются динамические методы — там все просто, мы всегда знаем объект благодаря
Со статическими методами все намного сложнее — нужно переопределить статический метод, после этого вызвать оригинальный метод через Reflection. А в PHP сейчас есть сложность с тем, чтобы указать scope при вызове статического метода через рефлексию. Вот поэтому и ломается LSB. Только в 5.4 можно создать замыкание в дочернем классе, которое форвардит запрос к родительскому методу и сменить ему scope на корректный через метод
Но попробую объяснить. Когда перехватываются динамические методы — там все просто, мы всегда знаем объект благодаря
$this
. Поэтому нам не нужно указывать scope. Со статическими методами все намного сложнее — нужно переопределить статический метод, после этого вызвать оригинальный метод через Reflection. А в PHP сейчас есть сложность с тем, чтобы указать scope при вызове статического метода через рефлексию. Вот поэтому и ломается LSB. Только в 5.4 можно создать замыкание в дочернем классе, которое форвардит запрос к родительскому методу и сменить ему scope на корректный через метод
bindTo()
+3
Посмотрел на 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 с использованием трейтов.
0
Кажется красивее будет Traits использовать из php 5.4
+1
Как я понял, навешивание аспектов происходит через Reflection? Можете сказать, какой оверхед появляется за счет этого?
0
Само вплетение аспектов проводится ровно один раз через 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мс
+1
Уже где-то использовали это решение в продакшене?
0
В настоящий момент — еще нет, но само решение к продакшену готово.
Поэтому сразу предупреждение для всех — разворачивайте код с аспектами параллельно, а не вместо основного кода.
Фишка АОП в том, что вы не меняете код приложения, поэтому можно написать код и включать аспекты только при наличии волшебного параметра в запросе — это делается запросто. Нет параметра в запросе — не используем аспекты вообще, никаких изменений в логике приложения. Есть наш заветный ключик — переключаемся именно для нашего запроса на код с аспектами. После того, как проверите, что все ок — убираем наш ключик и переходим на аспекты.
Поэтому сразу предупреждение для всех — разворачивайте код с аспектами параллельно, а не вместо основного кода.
Фишка АОП в том, что вы не меняете код приложения, поэтому можно написать код и включать аспекты только при наличии волшебного параметра в запросе — это делается запросто. Нет параметра в запросе — не используем аспекты вообще, никаких изменений в логике приложения. Есть наш заветный ключик — переключаемся именно для нашего запроса на код с аспектами. После того, как проверите, что все ок — убираем наш ключик и переходим на аспекты.
0
медленнее обычного кода в 10 раз, ну это как то круто, чтобы использовать в реальных проектах, не думаю, что кто-то может себе такое позволить использовать.
Где реально это может пригодиться?
Где реально это может пригодиться?
0
Это практически никакой оверхед. Покажите мне код, который за свой цикл Request-Process-Response исполнит 10к советов.
+2
Уточнение: не в 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 раз — выстрел в себе в ногу.
Все нужно использовать с умом и правильно, как и любой другой инструмент.
+1
nikita2206 меня опередил с изящным простым ответом )
0
Напоминает попытку реализовать декораторы.
Например как в 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.
+2
к сожалению декораторы создают ещё один объект(класс или функцию) а то и не один :(
с другой стороны не такой уж оверхед. один раз делается же.
но в php нет таких удобных декораторов :)
с другой стороны не такой уж оверхед. один раз делается же.
но в php нет таких удобных декораторов :)
0
А вы смотрели ли на JMSAopBundle?
0
Ну это Symfony-coupled код, а Go не зависит от платформы
0
Да, помимо завязки на контейнер симфони и ее архитектуру, есть еще несколько отличий. Если попробовать перехватить статический метод — JMSAopBundle отдыхает, перехватить метод в финальном классе — опять отдыхает. Пишем явно
Однако создание декораторов и подмена их в классе весьма неплохое решение и для работы в Symfony2 с АОП я его рекомендую больше, чем Go! Но вот не все он умеет, далеко не все…
(new Class())->methodName()
— опять отдыхает. У меня будет еще Introduction и перехват финальных методов, чем JMSAopBundle еще не скоро обзаведется.Однако создание декораторов и подмена их в классе весьма неплохое решение и для работы в Symfony2 с АОП я его рекомендую больше, чем Go! Но вот не все он умеет, далеко не все…
0
Утром на Хабре — вечером в PHP Weekly. Только что пришла рассылка. :)
И поздравляю с топ1 по Most Starred Today в PHP на гитхабе.
И поздравляю с топ1 по Most Starred Today в PHP на гитхабе.
0
Если попытаться завернуть кэширующий прокси поверх логирующего, а тот, в свою очередь, поверх основного класса, то скорость упадет на порядок.Странно, потому как на моей практике профилирования кешируюших/логируюших прокси классов, время вызова этих __call & __get в среднем на порядки меньше вызова самих кеширующих/логирующих методов. Ну т.е. издержки от их использования составляют 0.1-0.5%.
0
Все верно подмечено, что вызов даже магического метода будет быстрее если он отдаст данные из кэша. Это же справедливо и для аспектов, только работает еще быстрее, потому что там нет вообще этих магических методов, только прямые вызовы.
На самом деле гораздо больше неприятностей доставляет тот факт, что использование прокси-классов не дает возможности использовать в методах указание ожидаемых интерфейсов и классов.
На самом деле гораздо больше неприятностей доставляет тот факт, что использование прокси-классов не дает возможности использовать в методах указание ожидаемых интерфейсов и классов.
0
Напоминает триггеры в СУБД
0
статья интересная, спасибо. но мне интересно, нельзя ли избавиться вот от этого
вроде бы это как раз то, против чего и направлена мысль, озвученная в посте?
$obj = $invocation->getThis();
$class = is_object($obj) ? get_class($obj) : $obj;
$key = $class . ':' . $invocation->getMethod()->name;
вроде бы это как раз то, против чего и направлена мысль, озвученная в посте?
0
Да, выглядит некрасиво. Но даже если это не реализовано автором, всегда можно
самостоятельно завернуть
Ну или вернуть 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/отдельный класс с тремя публичными полями…
+2
Буквально в первом абзаце упоминается термин АОП, а его расшифровка появляется только под конец. Немного нелогично. А так в целом статья на хорошо!
0
В теории красиво, а есть какой-нибудь крупный проект, выполненный по этой методологии, который можно было бы выложить в открытый доступ?
0
Нет, в открытом доступе пока ничего нет. Но есть примеры в самой библиотеке, а также у меня на гитхабе есть пару тестовых приложений для ZF2 и SF2, которые работают с АОП. Ссылка на ZF2 есть в списке ссылок. Это как раз сделано для практики и оценки скорости работы.
0
Уважаемое хабрасообщество, что еще вам было бы интересно узнать по АОП в PHP? Также интересно получить обратную связь от тех, кто попробовал установить библиотеку и поработал с аспектами.
Я знаю, что есть небольшая парочка умельцев, которые не побоялись засучить рукава и поставить библиотеку на своем локальном компьютере )
Я знаю, что есть небольшая парочка умельцев, которые не побоялись засучить рукава и поставить библиотеку на своем локальном компьютере )
0
АОП это круто. Но создание аспектов выглядит пока что страшновато.
github.com/lisachenko/go-aop-php#5-create-an-aspect
Планируется ли возможность добавления в виде анонимных функций? Все таки это php а не java.
github.com/lisachenko/go-aop-php#5-create-an-aspect
Планируется ли возможность добавления в виде анонимных функций? Все таки это php а не java.
0
Думаю, что код большого приложения без аспектов выглядит еще более страшновато )
А теперь по-делу: задавать советы в виде анонимных функции — нет проблем. Внутри библиотека так и работает. Каждый аспект разбивается на серию советников (Advisor), которая регистрируется в контейнере. Советник по своей природе — это пара, состоящая из регулярного выражения для точки кода и замыкания (совета), полученного из метода аспекта. Поэтому можно легко регистрировать в ядре свои советники напрямую, API для этого уже есть.
Это фундамент на будущее, чтобы можно было загружать аспекты откуда угодно: из xml, из yml, с помощью CompilerPass-ов и другого. Однако использование аспектов — более предпочтительно, потому что позволяет структурировать код АОП в логические блоки, как классы в ООП. Тогда как советник в АОП сопоставим с простой функцией в ООП.
А теперь по-делу: задавать советы в виде анонимных функции — нет проблем. Внутри библиотека так и работает. Каждый аспект разбивается на серию советников (Advisor), которая регистрируется в контейнере. Советник по своей природе — это пара, состоящая из регулярного выражения для точки кода и замыкания (совета), полученного из метода аспекта. Поэтому можно легко регистрировать в ядре свои советники напрямую, API для этого уже есть.
Это фундамент на будущее, чтобы можно было загружать аспекты откуда угодно: из xml, из yml, с помощью CompilerPass-ов и другого. Однако использование аспектов — более предпочтительно, потому что позволяет структурировать код АОП в логические блоки, как классы в ООП. Тогда как советник в АОП сопоставим с простой функцией в ООП.
0
Sign up to leave a comment.
Избавляемся от дублирования сквозного кода в PHP: рефакторинг кода с АОП