Имхо, тестирование не при чем. Достаточно соблюдать правило Деметры. То есть, объект должен вызывать методы максимум на один уровень ниже (либо свои собственные методы, либо методы у своих прямых зависимостей):
class Test
{
function goodMethod() {
$this->dependency->method1();
$this->another->method2();
}
function badMethodInTheClass() {
$this->dependency->anotherMethod()->method3(); // Very bad!
}
}
Соблюдая это простое правило, всегда можно любому классу подсунуть нужные моки и протестировать независимо от всего остального кода. DI-контейнеры немного помогают соблюдать это правило, собирая зависимости в один уровень.
Затем, что эта штука применяется в Symfony2 и ZF2 для реализации Lazy-сервисов в контейнере. И производительность должна быть очень хорошей, иначе профита было бы мало в случае массового применения в приложении.
Кхм, эта статья — результат моего обсуждения на гитхабе с Marco и Matthieu )) Но, пожалуйста, сделайте правки в тексте: Marco — это он, а то как-то неудобно даже это видеть.
Имхо, самый большой недостаток-это ругань IDE на этот сахар. Не имея нормальной поддержки для этих конструкций на уровне IDE, сама разработка уже не покажется сахаром.
Эх, подлил я масла в огонь со своими фильтрами )
Однако приятно, что мои слова помогли изучить такую интересную технику. Реализация получилась весьма интересная, однако надо учесть много факторов, таких как опкод-кэшеры, корректную работу со стримами и прочее. А то в реальном применении все свалится благополучно, потому что код будет закэширован опкод-кэшером.
Если интересно посмотреть на динамические примеси, то это технология называется Introduction в мире AOP:
Introduction: declaring additional methods or fields on behalf of a type. AOP allows you to introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a class implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)
Эта фишка уже поддерживается в моем АОП-фреймворке для PHP. Если вкратце — она дает возможность к любому классу добавить «на лету» интерфейсы и трейты без изменения исходного кода класса. Если интересно — небольшой материал есть тут: Aspect-Oriented Pointcuts and Advices (последний параграф)
Есть данные с прошлой версии фреймворка на слабенькой машинке: performance test. Этот тест показывает, что 10000 итераций пустого метода выполняются за 3.0мс, тогда как с дополнительным советом в моей библиотеке — за 90мс. Если делать аналогичное с помощью экстеншена PHP-AOP, то получится за 25мс. Однако, скорость экстеншена очень сильно падает с ростом количества советов (обратите внимание, как начинает тормозить экстеншен, в случае если есть много срезов, которые не совпадают с измеряемым методом)
Тем не менее, это синтетический тест, потому что, как правило, сам метод работает в реальных условиях медленнее совета, вызывается всего раз или два и замедление скорости от совета практически незаметно из-за значительной оптимизации. Таким образом, добавление совета к большому методу почти не тормозит его работу и если этот метод вызывается один или несколько раз, то его даже не будет заметно.
Я бы назвал все это аспектно-ориентированным программированием ) То, что вы делаете с помощью парсера очень походит на то, что делает моя либа, написанная на PHP. Я скоро планирую завершить работы и по перехвату стандартных функций, типа fopen, mysqli_open и др. Основное отличие-работать будет везде, на любом хостинге.
Всех интересующихся приглашаю на DevConf )
Да, там есть несколько мест, которые приводили меня в полный ступор, потому что не было возможности двинуться дальше из-за всяких ограничений языка. Два года потребовалось на то, чтобы решить этот большой ребус, но я рад, что у меня все получилось и я могу показать ее сообществу.
Надеюсь, она найдет свое место в приложениях, потому что область применения широка: контрактное программирование, логирование, кэширование, новые шаблоны программирования, новые подходы к разработке ПО.
Это будет позже, вся теория и практические наброски у меня готовы. Можно будет перехватывать даже простые функции, например file_get_contens, mysqli_open и т.д. Но только если они будут использоваться в неймспейсе, а не в глобальном пространстве.
нет, не сможете, это под силу только расширению, такому как runkit, либо PHP-AOP. Тот код, который уже загружен в память, невозможно изменить со стороны PHP. Но вот если вы перед этим подключите АОП, а потом уже передадите управление на superlib.php, то методы в классах уже можно будет перехватить.
Как говорят: «копейка — рубль бережет». Так и здесь, в коде одного метода — да, всего одна строчка, в коде класса их может быть уже пара десятков, а в коде всего приложения — несколько тысяч.
А еще интереснее будет, если у вас уже есть эти несколько тысяч сеттеров по всему проекту и у вас есть необходимость сделать их цепочечными, чтобы было удобно работать.
ок, уточнение: я перехватываю именно require/include и как частный случай, получается, перехватываю autoload, потому что где-то в нем тоже есть include/require. Сама функция include/require для меня недосягаема, но вот путь к подключаемому файлу я изменить могу, что моя библиотека и делает.
Это концепция маркеров, вы с ней не сталкивались ранее? Когда, например, класс исключения помечают дополнительно такими маркерами, а потом ловят по классу интерфейса-маркера.
class AppException extends Exception implements ServiceLayerException
{
}
// ...
try {
throw new AppException();
} catch (ServiceLayerException $e) {
// .. service layer fault
}
Не обязательно вам писать аннотацию с @return, ее может вставить сама библиотека АОП, а IDE ее подхватит сама. Насчет магии — это ваш выбор, использовать или нет. Статья имеет исключительно ознакомительный характер, но ваши мысли и опасения мне понятны.
Не всегда сеттеры находятся в моделях. И не всегда они ваши и находятся на уровне приложения. Очень часто я встречаю код, где нужно настроить вендорский сервис, а в нем есть лишь тонна сеттеров и нет цепочечного интерфейса.
Реализуется АОП в PHP ручками )
В самом PHP это пока не планируется, хотя для версии 4.0 были попытки, я даже где-то видел грамматику лексера PHP с учетом аспектов, но это уже в прошлом. Из нового — есть PECL-расширение PHP-AOP, которое разрабатывается, но там куча багов, сегфолтов, да и скорость в отдельных случаях меньше, чем у моей PHP-библиотеки. Моя же библиотека написана на чистом PHP и не требует никаких дополнительных экзотических расширений, за счет этого она стабильна и активно развивается.
Идея работы такая — отслеживаем момент загрузки любого PHP-кода (перехватываются incude/require), делаем статический анализ кода, вносим в него необходимые правки, создаем декоратор, подменяем им оригинальный класс и сохраняем в кэше, дальше отдаем этот код парсеру PHP. После таких манипуляций в памяти PHP будет находиться класс, уже имеющий расширенную функциональность, но с прежним именем класса.
Это краткий обзор предыдущих серий Санта-Барбары под названием «как сделать АОП»
Соблюдая это простое правило, всегда можно любому классу подсунуть нужные моки и протестировать независимо от всего остального кода. DI-контейнеры немного помогают соблюдать это правило, собирая зависимости в один уровень.
Однако приятно, что мои слова помогли изучить такую интересную технику. Реализация получилась весьма интересная, однако надо учесть много факторов, таких как опкод-кэшеры, корректную работу со стримами и прочее. А то в реальном применении все свалится благополучно, потому что код будет закэширован опкод-кэшером.
Эта фишка уже поддерживается в моем АОП-фреймворке для PHP. Если вкратце — она дает возможность к любому классу добавить «на лету» интерфейсы и трейты без изменения исходного кода класса. Если интересно — небольшой материал есть тут: Aspect-Oriented Pointcuts and Advices (последний параграф)
Тем не менее, это синтетический тест, потому что, как правило, сам метод работает в реальных условиях медленнее совета, вызывается всего раз или два и замедление скорости от совета практически незаметно из-за значительной оптимизации. Таким образом, добавление совета к большому методу почти не тормозит его работу и если этот метод вызывается один или несколько раз, то его даже не будет заметно.
Всех интересующихся приглашаю на DevConf )
коммент товарища Groove выглядит так:
Надеюсь, она найдет свое место в приложениях, потому что область применения широка: контрактное программирование, логирование, кэширование, новые шаблоны программирования, новые подходы к разработке ПО.
А еще интереснее будет, если у вас уже есть эти несколько тысяч сеттеров по всему проекту и у вас есть необходимость сделать их цепочечными, чтобы было удобно работать.
@return
, ее может вставить сама библиотека АОП, а IDE ее подхватит сама. Насчет магии — это ваш выбор, использовать или нет. Статья имеет исключительно ознакомительный характер, но ваши мысли и опасения мне понятны.Не всегда сеттеры находятся в моделях. И не всегда они ваши и находятся на уровне приложения. Очень часто я встречаю код, где нужно настроить вендорский сервис, а в нем есть лишь тонна сеттеров и нет цепочечного интерфейса.
В самом PHP это пока не планируется, хотя для версии 4.0 были попытки, я даже где-то видел грамматику лексера PHP с учетом аспектов, но это уже в прошлом. Из нового — есть PECL-расширение PHP-AOP, которое разрабатывается, но там куча багов, сегфолтов, да и скорость в отдельных случаях меньше, чем у моей PHP-библиотеки. Моя же библиотека написана на чистом PHP и не требует никаких дополнительных экзотических расширений, за счет этого она стабильна и активно развивается.
Идея работы такая — отслеживаем момент загрузки любого PHP-кода (перехватываются incude/require), делаем статический анализ кода, вносим в него необходимые правки, создаем декоратор, подменяем им оригинальный класс и сохраняем в кэше, дальше отдаем этот код парсеру PHP. После таких манипуляций в памяти PHP будет находиться класс, уже имеющий расширенную функциональность, но с прежним именем класса.
Это краткий обзор предыдущих серий Санта-Барбары под названием «как сделать АОП»