Для моего сферического примера в вакууме для этой статьи получились следующие цифры:
Вариант | время без опкод-кэшера/с опкод-кэшером (apc.stat=off)
Оригинальный код (без логики) 0.2-0.3мс/0.19-0.25мс
Хардкод логики в методах 0.22-0.34мс/0.20-0.25мс
Оригинальный код с вызовами callable замыканий 0.5-0.7мс/0.25-0.37мс
Оригинальный код c АОП (debug=false) 7-8мс/1,6-2.2мс
Это само время выполнения кода, сюда не входит автозагрузка классов, инициализация кэша и прочее.
Лучше, конечно, делать цикл вызовов одинаковых методов с помощью разных способов (callable, call_user_func, __call, AOP и т.д), чтобы получить более правильные цифры, они будут отличаться от единичных вызовов и разница уже будет не 6-8 раз, а меньше. АОП внутри использует кэширование invocation-ов, что позволяет запускать даже огромные проекты типа ZF2, полностью заменив ВЕСЬ код аспектным (см. примеры на гитхабе).
Более того, есть возможность сделать это еще быстрее (хотя уже сейчас скорость моей библиотеки сопоставима со скоростью работы экстеншена PHP-AOP, а иногда и превышает ее из-за технической реализации)
В конце статьи именно эта ссылка и указана. Исторически так сложилось, что в момент закладки фундамента этой библиотеки я еще не знал про язык Go, он еще не был популярен так, как сейчас.
А название было дано как внутренний мотиватор — «давай, делай!». Потому что написание этой библиотеки — сплошная борьба со словом «нельзя»: нельзя подменить класс в PHP проксей (сохранив старое название класса), нельзя перехватить метод в финальном классе (потому что нельзя сделать extends), нельзя перехватить статический метод (потому что в коде они вызываются напрямую, не используя возможные прокси), нельзя сохранить LSB и решить проблему scope при перехвате статического метода, нельзя перехватить обращение к публичному свойству объекта, нельзя сделать прокси для трейта, нельзя перехватить метод в трейте, нельзя изменить код класса так, чтобы xdebug по-прежнему работал с основным классом, нельзя динамически изменить код класса при этом сохранив возможность использовать опкод-кэшер, нельзя сериализовать замыкания и прочие технические проблемы.
Думаю, что АОП в C++ можно сделать еще следующим образом: берем таблицу методов класса, подменяем указатель на метод своим указателем, а потом просто вызываем оригинальный метод, передавая ему управление. Такие вещи можно делать даже в рантайме, будет работать очень быстро. Аналогичный подход используется для эксплуатации уязвимостей переполнения буфера в программе, так что однозначно — можно.
Если вы читаете php.internals, то обнаружите, что с вами солидарно очень много разработчиков. Но есть необходимость в метаданных, поэтому и возникают разные RFC: annotations, annotations-in-docblock.
Популярные фреймворки используют свои парсеры для анализа аннотаций в док-блоках и позволяют управлять проектом «in one place» — сам код и конфиг к нему находится в одном месте, что очень удобно в некоторых случаях. Посмотрите на Symfony2, Doctrine2, FLOW
Вы меня подловили ) Данную фразу правильней трактовать так: «больше нет библиотек на PHP, которые делают Load-Time-Weaving». Т.е. относится только к PHP. В других языках препроцессинг конечно же есть и существует там очень давно.
IDE — старый добрый phpStorm, но строчные докблоки должны поддерживаться любой нормальной IDE, поэтому этот код вполне удобно рефакторить.
Фраза насчет «типичного» программиста — это легкая гипербола, конечно. Понятно, что мы люди взрослые и делаем сложные вещи правильно. Но пытаться объяснить АОП по-другому никак не получается, поэтому пришлось делать акценты на проблемах кода с ООП.
Прелесть АОП в том, что она дает возможность еще больше структурировать вашу программу. Есть отличное исследование АОП, в котором показано, как внедрение АОП улучшает общее качество кода и почему использование аспектов в определенных случаях значительно проще описывает реальные процессы. Постичь АОП довольно сложно, это как новый язык программирования, к которому вы еще не привыкли, но поверьте, за этой технологией будет будущее.
Насчет поддержки в IDE — наверное, вы не заметили inline-докблок в теле советов. Моя IDE прекрасно это понимает, давая мне возможность легко делать рефакторинг кода, находить места использования, использовать автодополнение и даже создавать новые методы в классе «человек», при этом находясь внутри класса аспекта.
Но определить точки, где внедряются аспекты — это да, IDE не подскажет без плагина. Тут с вами соглашусь.
Насчет всего остального — я пытаюсь поделиться с сообществом своими знаниями и решениями. Как и все новое, оно будет и должно вызывать недоверие. Поэтому я очень ценю любые конструктивные замечания и предложения, чтобы попытаться о них подумать. А вот приводить альтернативы в виде шаблонов OOP не нужно, их и так все знают, в сети полно материала на эту тему.
Будет, но на текущем уровне этот вариант задания срезов точек более предпочтительный, так как движок будет разбирать скоро грамматику срезов, выполняя построение AST. Задавать срезы руками с помощью классов будет значительно сложнее и менее читабельно. Но планы есть и на ваш случай ) Спрос рождает предложение.
Все верно говорите, с помощью такого способа можно генерировать прокси-трейты. Правда количество методов в конечном классе будет расти из-за добавления альясов в трейте.
Использование событий влечет за собой написание шаблонного кода, поэтому вы не сможете расширить логику любого метода, если он заранее не написан с учетом возможного расширения. Да и обработка событий добавляет очень большой оверхед. Так что у меня в библиотеке нет никаких событий, хотя если сравнивать по смыслу — то да, в какой-то мере это событие. Только можно создать его в любом месте кода без предварительной подготовки )
Метапрограммирование и переопределение методов в рантайме уже есть ) Без экстеншенов, без магии, с очень низким оверхедом на вызов перехваченого метода. Библиотека Go! AOP к вашим услугам. Только ей под силу добавить к любому вашему классу нужный трейт, не меняя исходный код оригинальных классов.
Пример: берем некоторый класс Example (в нем нет трейтов и интерфейсов), дальше нам захотелось иметь возможность сериализовать объекты данного класса, не меняя исходный код класса. Все просто — создаем трейт SerializableImpl и объявляем совет DeclareParents с интерфейсом Serializable и трейтом SerializableImpl в отношении класса Example, после чего наш класс Example будет имплементировать интерфейс Serializable, в чем можно убедиться проверкой instanceof.
Я уже в своей статье на хабре приводил пример АОП, когда достаточно перед методом добавить аннотацию @Cacheable — и он будет кэшироваться, неважно какой метод — динамический, статический, или же вообще в трейте. Это пример номер раз.
Пример номер два (про него еще будет отдельная статья на хабре): есть у вас тонна сущностей доктрины, а вам хочется отдавать их как-то красиво в JSON и начинается вызов тучи геттеров, либо гидрация, а можно сделать это очень элегантно — ко всем классам-сущностям добавить интерфейс JsonSerializable и трейт с реализацией этого интерфейса. Профит. После этого можно любую сущность заворачивать в REST-интерфейс одним контроллером.
Из реальных примеров — для админки SonataBundle одним классом добавляем журналирование всех действий в админке. Неплохо, да, не меняя оригинальный код?
Еще из интересных применений трейтов — динамическое их подключение к конечным классам с помощью аспектно-ориентированного программирования. Я как раз недавно сделал поддержку технологии внедрения (Introduction) трейтов в любой класс, а также добавил уникальную возможность перехвата методов в трейтах с помощью генерации прокси-трейтов.
Так что трейты — это хорошо, в сочетании с интерфейсами. А декораторы уже есть благодаря АОП.
Думаю, что код большого приложения без аспектов выглядит еще более страшновато )
А теперь по-делу: задавать советы в виде анонимных функции — нет проблем. Внутри библиотека так и работает. Каждый аспект разбивается на серию советников (Advisor), которая регистрируется в контейнере. Советник по своей природе — это пара, состоящая из регулярного выражения для точки кода и замыкания (совета), полученного из метода аспекта. Поэтому можно легко регистрировать в ядре свои советники напрямую, API для этого уже есть.
Это фундамент на будущее, чтобы можно было загружать аспекты откуда угодно: из xml, из yml, с помощью CompilerPass-ов и другого. Однако использование аспектов — более предпочтительно, потому что позволяет структурировать код АОП в логические блоки, как классы в ООП. Тогда как советник в АОП сопоставим с простой функцией в ООП.
На самом деле, стандартизация полезна, но только тогда, когда участвуют в голосовании все, в том числе и сообщество. Потому что хочется иметь возможность менять компоненты в целой системе, сохраняя прежний функционал. И то, что сейчас происходит в PHP — давно пройденный путь в Java.
Даже названия похожие: JSR и PSR. Тут даже равенство напрашивается: PSR-3 = JSR-47 )
К сожалению, про АОП теории крайне мало. И отдельных материалов, без привязки к реализации, практически не найти. Однако есть целый сайт этузиаста АОП: crosscuttingconcerns.com. Там можно найти очень много всего полезного. А еще старенькая статья про АОП с phpAspect.
Ага, как известно есть две основных проблемы в IT: инвалидация кеша и именование переменных. Тут уже нужно думать в каждом конкретном случае, как делать инвалидацию.
В принципе, это возможно с помощью аспектов и метаинформации. Например, для метода update($id, $value) можно использовать аннотацию @CacheInvalidator(get($id)), по которой совет поймет, что ему нужно сбрасывать кэш для метода get($id) с нужным значением $id.
Уважаемое хабрасообщество, что еще вам было бы интересно узнать по АОП в PHP? Также интересно получить обратную связь от тех, кто попробовал установить библиотеку и поработал с аспектами.
Я знаю, что есть небольшая парочка умельцев, которые не побоялись засучить рукава и поставить библиотеку на своем локальном компьютере )
Нет, в открытом доступе пока ничего нет. Но есть примеры в самой библиотеке, а также у меня на гитхабе есть пару тестовых приложений для ZF2 и SF2, которые работают с АОП. Ссылка на ZF2 есть в списке ссылок. Это как раз сделано для практики и оценки скорости работы.
Вариант | время без опкод-кэшера/с опкод-кэшером (apc.stat=off)
Оригинальный код (без логики) 0.2-0.3мс/0.19-0.25мс
Хардкод логики в методах 0.22-0.34мс/0.20-0.25мс
Оригинальный код с вызовами callable замыканий 0.5-0.7мс/0.25-0.37мс
Оригинальный код c АОП (debug=false) 7-8мс/1,6-2.2мс
Это само время выполнения кода, сюда не входит автозагрузка классов, инициализация кэша и прочее.
Лучше, конечно, делать цикл вызовов одинаковых методов с помощью разных способов (callable, call_user_func, __call, AOP и т.д), чтобы получить более правильные цифры, они будут отличаться от единичных вызовов и разница уже будет не 6-8 раз, а меньше. АОП внутри использует кэширование invocation-ов, что позволяет запускать даже огромные проекты типа ZF2, полностью заменив ВЕСЬ код аспектным (см. примеры на гитхабе).
Более того, есть возможность сделать это еще быстрее (хотя уже сейчас скорость моей библиотеки сопоставима со скоростью работы экстеншена PHP-AOP, а иногда и превышает ее из-за технической реализации)
А название было дано как внутренний мотиватор — «давай, делай!». Потому что написание этой библиотеки — сплошная борьба со словом «нельзя»: нельзя подменить класс в PHP проксей (сохранив старое название класса), нельзя перехватить метод в финальном классе (потому что нельзя сделать extends), нельзя перехватить статический метод (потому что в коде они вызываются напрямую, не используя возможные прокси), нельзя сохранить LSB и решить проблему scope при перехвате статического метода, нельзя перехватить обращение к публичному свойству объекта, нельзя сделать прокси для трейта, нельзя перехватить метод в трейте, нельзя изменить код класса так, чтобы xdebug по-прежнему работал с основным классом, нельзя динамически изменить код класса при этом сохранив возможность использовать опкод-кэшер, нельзя сериализовать замыкания и прочие технические проблемы.
Популярные фреймворки используют свои парсеры для анализа аннотаций в док-блоках и позволяют управлять проектом «in one place» — сам код и конфиг к нему находится в одном месте, что очень удобно в некоторых случаях. Посмотрите на Symfony2, Doctrine2, FLOW
Фраза насчет «типичного» программиста — это легкая гипербола, конечно. Понятно, что мы люди взрослые и делаем сложные вещи правильно. Но пытаться объяснить АОП по-другому никак не получается, поэтому пришлось делать акценты на проблемах кода с ООП.
Прелесть АОП в том, что она дает возможность еще больше структурировать вашу программу. Есть отличное исследование АОП, в котором показано, как внедрение АОП улучшает общее качество кода и почему использование аспектов в определенных случаях значительно проще описывает реальные процессы. Постичь АОП довольно сложно, это как новый язык программирования, к которому вы еще не привыкли, но поверьте, за этой технологией будет будущее.
Но определить точки, где внедряются аспекты — это да, IDE не подскажет без плагина. Тут с вами соглашусь.
Насчет всего остального — я пытаюсь поделиться с сообществом своими знаниями и решениями. Как и все новое, оно будет и должно вызывать недоверие. Поэтому я очень ценю любые конструктивные замечания и предложения, чтобы попытаться о них подумать. А вот приводить альтернативы в виде шаблонов OOP не нужно, их и так все знают, в сети полно материала на эту тему.
Пример: берем некоторый класс Example (в нем нет трейтов и интерфейсов), дальше нам захотелось иметь возможность сериализовать объекты данного класса, не меняя исходный код класса. Все просто — создаем трейт SerializableImpl и объявляем совет DeclareParents с интерфейсом Serializable и трейтом SerializableImpl в отношении класса Example, после чего наш класс Example будет имплементировать интерфейс Serializable, в чем можно убедиться проверкой instanceof.
Пример номер два (про него еще будет отдельная статья на хабре): есть у вас тонна сущностей доктрины, а вам хочется отдавать их как-то красиво в JSON и начинается вызов тучи геттеров, либо гидрация, а можно сделать это очень элегантно — ко всем классам-сущностям добавить интерфейс
JsonSerializableи трейт с реализацией этого интерфейса. Профит. После этого можно любую сущность заворачивать в REST-интерфейс одним контроллером.Из реальных примеров — для админки SonataBundle одним классом добавляем журналирование всех действий в админке. Неплохо, да, не меняя оригинальный код?
Так что трейты — это хорошо, в сочетании с интерфейсами. А декораторы уже есть благодаря АОП.
А теперь по-делу: задавать советы в виде анонимных функции — нет проблем. Внутри библиотека так и работает. Каждый аспект разбивается на серию советников (Advisor), которая регистрируется в контейнере. Советник по своей природе — это пара, состоящая из регулярного выражения для точки кода и замыкания (совета), полученного из метода аспекта. Поэтому можно легко регистрировать в ядре свои советники напрямую, API для этого уже есть.
Это фундамент на будущее, чтобы можно было загружать аспекты откуда угодно: из xml, из yml, с помощью CompilerPass-ов и другого. Однако использование аспектов — более предпочтительно, потому что позволяет структурировать код АОП в логические блоки, как классы в ООП. Тогда как советник в АОП сопоставим с простой функцией в ООП.
Даже названия похожие: JSR и PSR. Тут даже равенство напрашивается: PSR-3 = JSR-47 )
В принципе, это возможно с помощью аспектов и метаинформации. Например, для метода
update($id, $value)можно использовать аннотацию @CacheInvalidator(get($id)), по которой совет поймет, что ему нужно сбрасывать кэш для методаget($id)с нужным значением $id.Я знаю, что есть небольшая парочка умельцев, которые не побоялись засучить рукава и поставить библиотеку на своем локальном компьютере )