Статьи

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

Концепция аспектно-ориентированного программирования (AOP) является довольно новой для PHP. В настоящее время в PHP нет официальной поддержки AOP, но есть некоторые расширения и библиотеки, которые реализуют эту функцию. В этом уроке мы будем использовать Go! Библиотека PHP для изучения АОП в PHP и обзор, когда это может быть полезно.


Аспектно-ориентированное программирование похоже на новый гаджет для гиков.

Термин Аспектно-ориентированное программирование сформировался в середине 1990-х годов в небольшой группе в исследовательском центре Xerox в Пало-Альто (PARC). AOP считался спорным в первые дни — как в случае с любой новой и интересной технологией — в основном из-за отсутствия четкого определения. Группа приняла сознательное решение выпустить его в полусгоревшем виде, чтобы дать возможность большему сообществу предоставить обратную связь. В основе проблемы лежала концепция «Разделение интересов». АОП было одним из возможных решений для разделения проблем.

AOP сформировался в конце 1990-х годов, когда Xerox выпустила AspectJ, а IBM в 2001 году последовала их примеру Hyper / J. Сегодня AOP — это устоявшаяся технология, принятая большинством распространенных языков программирования.


В основе AOP лежит аспект, но прежде чем мы сможем определить «аспект», мы должны обсудить два других термина: срез и совет . Точка отсечения представляет момент в нашем исходном коде, определяя подходящий момент для запуска нашего кода. Код, который выполняется с точечным сокращением, называется, советует, и комбинация одного или нескольких точечных сокращений и советов является аспектом .

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


Крис Питерс уже обсуждал фреймворк Flow для AOP в PHP. Другая реализация АОП может быть найдена в платформе Lithium .

Другой фреймворк использовал другой подход и создал полное расширение PHP на C / C ++, выполняя свою магию на том же уровне, что и интерпретатор PHP. Он называется расширением AOP PHP , и я могу обсудить его в следующей статье.

Но, как я уже отмечал ранее, для этого урока мы рассмотрим Go! AOP-PHP библиотека.


Иди! библиотека не является расширением; он полностью написан на PHP для PHP 5.4 и выше. Простая библиотека PHP позволяет легко развертывать даже в ограниченных средах общего хостинга, которые не позволяют вам компилировать и устанавливать свои собственные расширения PHP.

Composer является предпочтительным методом для установки пакетов PHP. Если у вас нет доступа к Composer, вы всегда можете скачать его с Go! GitHub хранилище .

Сначала добавьте следующие строки в ваш файл composer.json .

1
2
3
4
5
{
    «require»: {
        «lisachenko/go-aop-php»: «*»
    }
}

Затем используйте Composer для установки go-aop-php . Запустите следующую команду из терминала:

1
2
$ cd /your/project/folder
$ php composer.phar update lisachenko/go-aop-php

Composer установит необходимые пакеты и зависимости всего за несколько секунд. В случае успеха вы должны увидеть что-то похожее на следующий результат:

01
02
03
04
05
06
07
08
09
10
11
12
13
Loading composer repositories with package information
Updating dependencies
  — Installing doctrine/common (2.3.0)
    Downloading: 100%
 
  — Installing andrewsville/php-token-reflection (1.3.1)
    Downloading: 100%
 
  — Installing lisachenko/go-aop-php (0.1.1)
    Downloading: 100%
 
Writing lock file
Generating autoload files

После завершения установки вы найдете каталог с именем vendor в вашей source папке. Иди! библиотека и ее зависимости установлены там.

01
02
03
04
05
06
07
08
09
10
11
$ ls -l ./vendor
total 20
drwxr-xr-x 3 csaba csaba 4096 Feb 2 12:16 andrewsville
-rw-r—r— 1 csaba csaba 182 Feb 2 12:18 autoload.php
drwxr-xr-x 2 csaba csaba 4096 Feb 2 12:16 composer
drwxr-xr-x 3 csaba csaba 4096 Feb 2 12:16 doctrine
drwxr-xr-x 3 csaba csaba 4096 Feb 2 12:16 lisachenko
 
$ ls -l ./vendor/lisachenko/
total 4
drwxr-xr-x 5 csaba csaba 4096 Feb 2 12:16 go-aop-php

Нам нужно создать вызов, который находится между точкой маршрутизации / входа нашего приложения. Затем автозагрузчик автоматически включает класс. Идти! ссылается на это как Aspect Kernel .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;
 
class ApplicationAspectKernel extends AspectKernel {
 
    protected function configureAop(AspectContainer $container) {
 
    }
 
    protected function getApplicationLoaderPath() {
 
    }
 
}

Сегодня AOP — это хорошо зарекомендовавшая себя технология, принятая большинством распространенных языков программирования.

Для этого примера я создал каталог с именем Application и добавил в него новый файл класса ApplicationAspectKernel.php .

Наше ядро ​​аспекта расширяет абстрактный класс AspectKernel Go !, который предоставляет базовую функциональность, которая необходима ядру аспекта для выполнения своей работы. Мы должны реализовать два метода: configureAop() , который регистрирует наши будущие аспекты, и getApplicationLoaderPath() , который предоставляет строку, представляющую полный путь к автозагрузчику приложения.

Сейчас просто создайте пустой файл getApplicationLoaderPath() в каталоге Application и измените метод getApplicationLoaderPath() соответственно.

01
02
03
04
05
06
07
08
09
10
// […]
class ApplicationAspectKernel extends AspectKernel {
 
    // […]
 
    protected function getApplicationLoaderPath() {
        return __DIR__ .
    }
 
}

Пока не беспокойтесь о autoload.php ; мы скоро заполним недостающие части.

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


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

Нашим небольшим приложением станет электронный брокер, способный покупать и продавать акции.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
class Broker {
 
    private $name;
    private $id;
 
    function __construct($name, $id) {
        $this->name = $name;
        $this->id = $id;
    }
 
    function buy($symbol, $volume, $price) {
        return $volume * $price;
    }
 
    function sell($symbol, $volume, $price) {
        return $volume * $price;
    }
 
}

Этот код довольно прост. Класс Broker имеет два приватных поля, в которых хранятся имя и идентификатор брокера.

Этот класс также предлагает два метода, buy() и sell() для покупки и продажи акций, соответственно. Каждый из этих методов принимает три аргумента: символ акции, количество акций и цену за акцию. Метод sell() продает акции, а затем вычисляет общую сумму полученных денег. И наоборот, метод buy() покупает акции и рассчитывает общую сумму потраченных денег.

Мы можем легко использовать нашего Broker , написав тест PHPUnit. Создайте каталог с именем Test внутри каталога Application и в нем добавьте новый файл BrokerTest.php . Добавьте следующий код в этот файл:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
require_once ‘../Broker.php’;
 
class BrokerTest extends PHPUnit_Framework_TestCase {
 
    function testBrokerCanBuyShares() {
        $broker = new Broker(‘John’, ‘1’);
        $this->assertEquals(500, $broker->buy(‘GOOGL’, 100, 5));
    }
 
    function testBrokerCanSellShares() {
        $broker = new Broker(‘John’, ‘1’);
        $this->assertEquals(500, $broker->sell(‘YAHOO’, 50, 10));
    }
 
}

Этот тест просто проверяет возвращаемые значения методов брокера. Мы можем запустить этот тест и увидеть, что наш код по крайней мере синтаксически корректен.

Давайте создадим автозагрузчик, который физически загружает классы, которые нужны нашему приложению. Это будет простой загрузчик, основанный на автозагрузчике PSR-0 .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
ini_set(‘display_errors’, true);
 
spl_autoload_register(function($originalClassName) {
    $className = ltrim($originalClassName, ‘\\’);
    $fileName = »;
    $namespace = »;
    if ($lastNsPos = strripos($className, ‘\\’)) {
        $namespace = substr($className, 0, $lastNsPos);
        $className = substr($className, $lastNsPos + 1);
        $fileName = str_replace(‘\\’, DIRECTORY_SEPARATOR, $namespace) .
    }
    $fileName .= str_replace(‘_’, DIRECTORY_SEPARATOR, $className) .
 
    $resolvedFileName = stream_resolve_include_path($fileName);
    if ($resolvedFileName) {
        require_once $resolvedFileName;
    }
    return (bool) $resolvedFileName;
});

Это все, что нам нужно для файла autoload.php . Теперь измените BrokerTest.php чтобы он требовал автозагрузчика вместо класса Broker.php .

1
2
3
4
5
require_once ‘../autoload.php’;
 
class BrokerTest extends PHPUnit_Framework_TestCase {
    // […]
}

Запуск BrokerTest доказывает, что код все еще работает.

Наш последний шаг — настроить Go !. Нам нужно соединить все компоненты так, чтобы они работали в гармонии. Сначала создайте файл с именем AspectKernelLoader.php и добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
include __DIR__ .
include ‘ApplicationAspectKernel.php’;
 
ApplicationAspectKernel::getInstance()->init(array(
    ‘autoload’ => array(
        ‘Go’ => realpath(__DIR__ . ‘/../vendor/lisachenko/go-aop-php/src/’),
        ‘TokenReflection’ => realpath(__DIR__ . ‘/../vendor/andrewsville/php-token-reflection/’),
        ‘Doctrine\\Common’ => realpath(__DIR__ . ‘/../vendor/doctrine/common/lib/’)
    ),
    ‘appDir’ => __DIR__ .
    ‘cacheDir’ => null,
    ‘includePaths’ => array(),
    ‘debug’ => true
));

Нам нужно соединить все компоненты так, чтобы они работали в гармонии.

Этот файл находится между фронт-контроллером и автозагрузчиком. Он использует инфраструктуру AOP для инициализации и вызова autoload.php при необходимости.

В первой строке мы явно включаем AspectKernel.php и ApplicationAspectKernel.php . Эти файлы должны быть явно включены, потому что, помните, на данный момент у нас нет автозагрузчика.

В следующем фрагменте кода мы вызываем метод init() для объекта ApplicationAspectKernel и передаем ему массив параметров:

  • autoload определяет пути для инициализации библиотеки AOP. Настройте пути в соответствии с вашей структурой каталогов.
  • appDir ссылается на каталог приложения.
  • cacheDir указывает каталог cache (мы проигнорируем это в этом уроке).
  • includePaths представляет фильтр для аспектов. Мы хотим, чтобы все указанные каталоги отслеживались, поэтому оставьте этот массив пустым, чтобы смотреть все.
  • debug предоставляет дополнительную информацию об отладке, которая полезна для разработки, но вы должны установить ее в false для развернутых приложений.

Чтобы завершить соединение между различными частями, найдите все ссылки на autoload.php в вашем проекте и замените их на AspectKernelLoader.php . В нашем простом примере, только тестовый файл требует модификации:

1
2
3
4
5
6
7
require_once ‘../AspectKernelLoader.php’;
 
class BrokerTest extends PHPUnit_Framework_TestCase {
 
// […]
 
}

Для больших проектов может оказаться полезным использовать bootstrap.php для PHPUnit; require_once() для AspectKernelLoader.php или нашего AspectKernelLoader.php должны быть включены туда.

Создайте файл с именем BrokerAspect.php и добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
use Go\Aop\Aspect;
use Go\Aop\Intercept\FieldAccess;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\After;
use Go\Lang\Annotation\Before;
use Go\Lang\Annotation\Around;
use Go\Lang\Annotation\Pointcut;
use Go\Lang\Annotation\DeclareParents;
 
class BrokerAspect implements Aspect {
 
    /**
     * @param MethodInvocation $invocation Invocation
     * @Before(«execution(public Broker->*(*))») // This is our PointCut
     */
    public function beforeMethodExecution(MethodInvocation $invocation) {
        echo «Entering method » .
    }
}

Мы начнем с определения нескольких операторов use для инфраструктуры АОП. Затем мы создаем наш аспектный класс с именем BrokerAspect , который должен реализовывать Aspect . Далее мы определяем логику соответствия для нашего аспекта:

1
* @Before(«execution(public Broker->*(*))»)
  • @Before когда следует применить совет. @Before @After : @Before , @After , @Around и @AfterThrowing .
  • "execution(public Broker->*(*))" задает правило соответствия как выполнение любого открытого метода в классе, называемом Broker , с любым количеством параметров. Синтаксис: [operation - execution/access]([method/attribute type - public/protected] [class]->[method/attribute]([params])

Обратите внимание, что механизм сопоставления, по общему признанию, несколько неудобен. Вы можете использовать только одну ‘ * ‘ (звездочку) в каждой части правила. Например, public Broker-> соответствует классу с именем Broker . public Bro*-> соответствует любому классу, начинающемуся с Bro , а public *ker-> соответствует любому классу, оканчивающемуся на ker .

public *rok*-> не будет ничего совпадать; Вы не можете использовать более одной звезды для одного и того же матча.

Метод, следующий за совпадением, будет вызван при возникновении события. В нашем случае метод выполняется перед каждым вызовом одного из открытых методов Broker . Параметр, называемый $invocation (типа MethodInvocation ), автоматически передается нашему методу. Этот объект предоставляет различные способы получения информации о вызываемом методе. В этом первом примере мы используем его, чтобы получить имя метода и распечатать его.

Простого определения аспекта недостаточно; нам нужно зарегистрировать его в инфраструктуре АОП. В противном случае он не будет применяться. Отредактируйте ApplicationAspectKernel.php и вызовите registerAspect() для контейнера в методе configureAop() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;
 
class ApplicationAspectKernel extends AspectKernel
{
 
    protected function getApplicationLoaderPath()
    {
        return __DIR__ .
    }
 
    protected function configureAop(AspectContainer $container)
    {
        $container->registerAspect(new BrokerAspect());
    }
}

Запустите тесты и проверьте вывод. Вы должны увидеть что-то похожее на следующее:

01
02
03
04
05
06
07
08
09
10
11
PHPUnit 3.6.11 by Sebastian Bergmann.
 
.Entering method __construct()
Entering method buy()
.Entering method __construct()
Entering method sell()
 
 
Time: 0 seconds, Memory: 5.50Mb
 
OK (2 tests, 2 assertions)

Таким образом, нам удалось запустить код всякий раз, когда что-то происходит на брокере.

Давайте добавим еще один метод в BrokerAspect .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// […]
class BrokerAspect implements Aspect {
 
    // […]
 
    /**
     * @param MethodInvocation $invocation Invocation
     * @After(«execution(public Broker->*(*))»)
     */
    public function afterMethodExecution(MethodInvocation $invocation) {
        echo «Finished executing method » .
        echo «with parameters: » .
    }
}

Этот метод запускается после выполнения открытого метода (обратите внимание на @After ). Затем мы добавляем еще одну строку для вывода параметров, используемых для вызова метода. Результат нашего теста теперь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
PHPUnit 3.6.11 by Sebastian Bergmann.
 
.Entering method __construct()
Finished executing method __construct()
with parameters: John, 1.
 
Entering method buy()
Finished executing method buy()
with parameters: GOOGL, 100, 5.
 
.Entering method __construct()
Finished executing method __construct()
with parameters: John, 1.
 
Entering method sell()
Finished executing method sell()
with parameters: YAHOO, 50, 10.
 
Time: 0 seconds, Memory: 5.50Mb
 
OK (2 tests, 2 assertions)

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//[…]
class BrokerAspect implements Aspect {
 
    /**
     * @param MethodInvocation $invocation Invocation
     * @Before(«execution(public Broker->*(*))»)
     */
    public function beforeMethodExecution(MethodInvocation $invocation) {
        echo «Entering method » .
        echo «with parameters: » .
    }
 
    /**
     * @param MethodInvocation $invocation Invocation
     * @After(«execution(public Broker->*(*))»)
     */
    public function afterMethodExecution(MethodInvocation $invocation) {
        echo «Finished executing method » .
    }
 
    /**
     * @param MethodInvocation $invocation Invocation
     * @Around(«execution(public Broker->*(*))»)
     */
    public function aroundMethodExecution(MethodInvocation $invocation) {
        $returned = $invocation->proceed();
        echo «method returned: » .
 
        return $returned;
    }
 
}

Простого определения аспекта недостаточно; нам нужно зарегистрировать его в инфраструктуре АОП.

Этот новый код перемещает информацию о параметре в метод @Before . Мы также добавили еще один метод с помощью специального @Around . Это аккуратно, потому что исходный вызов метода соответствия заключен в aroundMethodExecution() , эффективно подавляя исходный вызов. Внутри advise нам нужно вызвать $invocation->proceed() , чтобы выполнить исходный вызов. Если вы этого не сделаете, исходный вызов не произойдет.

Эта упаковка также позволяет нам манипулировать возвращаемым значением. То, что мы возвращаем в нашем совете, это то, что возвращается в исходном вызове В нашем случае мы ничего не меняли, и ваш вывод должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
PHPUnit 3.6.11 by Sebastian Bergmann.
 
.Entering method __construct()
with parameters: John, 1.
method returned:
Finished executing method __construct()
 
Entering method buy()
with parameters: GOOGL, 100, 5.
method returned: 500
Finished executing method buy()
 
.Entering method __construct()
with parameters: John, 1.
method returned:
Finished executing method __construct()
 
Entering method sell()
with parameters: YAHOO, 50, 10.
method returned: 500
Finished executing method sell()
 
Time: 0 seconds, Memory: 5.75Mb
 
OK (2 tests, 2 assertions)

Давайте немного поиграем и предложим скидку для конкретного брокера. Вернитесь в класс теста и напишите следующий тест:

01
02
03
04
05
06
07
08
09
10
11
12
require_once ‘../AspectKernelLoader.php’;
 
class BrokerTest extends PHPUnit_Framework_TestCase {
 
    // […]
 
    function testBrokerWithId2WillHaveADiscountOnBuyingShares() {
        $broker = new Broker(‘Finch’, ‘2’);
        $this->assertEquals(80, $broker->buy(‘MS’, 10, 10));
    }
 
}

Это не удастся с:

01
02
03
04
05
06
07
08
09
10
11
12
Time: 0 seconds, Memory: 6.00Mb
 
There was 1 failure:
 
1) BrokerTest::testBrokerWithId2WillHaveADiscountOnBuyingShares
Failed asserting that 100 matches expected 80.
 
/home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Test/BrokerTest.php:19
/usr/bin/phpunit:46
 
FAILURES!
Tests: 3, Assertions: 3, Failures: 1.

Далее нам нужно изменить брокера для предоставления его идентификатора. Просто getId() метод getId() , как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class Broker {
 
    private $name;
    private $id;
 
    function __construct($name, $id) {
        $this->name = $name;
        $this->id = $id;
    }
 
    function getId() {
        return $this->id;
    }
 
    // […]
 
}

Теперь измените аспект, чтобы скорректировать цену покупки для брокера с идентификатором 2 .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// […]
class BrokerAspect implements Aspect {
 
    // […]
 
    /**
     * @param MethodInvocation $invocation Invocation
     * @Around(«execution(public Broker->buy(*))»)
     */
    public function aroundMethodExecution(MethodInvocation $invocation) {
        $returned = $invocation->proceed();
        $broker = $invocation->getThis();
 
        if ($broker->getId() == 2) return $returned * 0.80;
        return $returned;
    }
 
}

Вместо добавления нового метода просто измените aroundMethodExecution() . Теперь он соответствует только методам, называемым « buy », и запускает $invocation->getThis() . Это эффективно возвращает исходный объект Broker чтобы мы могли выполнить его код. И так мы и сделали! Мы спрашиваем у брокера его ID и предлагаем скидку, если ID равен 2 . Тест сейчас проходит.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
PHPUnit 3.6.11 by Sebastian Bergmann.
 
.Entering method __construct()
with parameters: John, 1.
Finished executing method __construct()
 
Entering method buy()
with parameters: GOOGL, 100, 5.
Entering method getId()
with parameters: .
Finished executing method getId()
 
Finished executing method buy()
 
.Entering method __construct()
with parameters: John, 1.
Finished executing method __construct()
 
Entering method sell()
with parameters: YAHOO, 50, 10.
Finished executing method sell()
 
.Entering method __construct()
with parameters: Finch, 2.
Finished executing method __construct()
 
Entering method buy()
with parameters: MS, 10, 10.
Entering method getId()
with parameters: .
Finished executing method getId()
 
Finished executing method buy()
 
Time: 0 seconds, Memory: 5.75Mb
 
OK (3 tests, 3 assertions)

Теперь мы можем выполнить дополнительный код при вводе метода, после его выполнения и вокруг него. Но что, если метод выдает исключение?

Добавьте метод тестирования, чтобы купить большое количество акций:

1
2
3
4
function testBuyTooMuch() {
    $broker = new Broker(‘Finch’, ‘2’);
    $broker->buy(‘MS’, 10000, 8);
}

Теперь создайте класс исключения. Нам это нужно, потому что встроенный класс Exception не может быть пойман Go! АОП или PHPUnit.

1
2
3
4
5
6
7
class SpentTooMuchException extends Exception {
 
    public function __construct($message) {
        parent::__construct($message);
    }
 
}

Измените посредника, чтобы выдавать исключение для большого значения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
class Broker {
 
    // […]
 
    function buy($symbol, $volume, $price) {
        $value = $volume * $price;
        if ($value > 1000)
            throw new SpentTooMuchException(sprintf(‘You are not allowed to spend that much (%s)’, $value));
        return $value;
    }
 
    // […]
 
}

Запустите тесты и убедитесь, что они провалились:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Time: 0 seconds, Memory: 6.00Mb
 
There was 1 error:
 
1) BrokerTest::testBuyTooMuch
Exception: You are not allowed to spend that much (80000)
 
/home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Broker.php:20
// […]
/home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Broker.php:47
/home/csaba/Personal/Programming/NetTuts/Aspect Oriented Programming in PHP/Source/Application/Test/BrokerTest.php:24
/usr/bin/phpunit:46
 
FAILURES!
Tests: 4, Assertions: 3, Errors: 1.

Теперь ожидайте исключение (в тесте) и убедитесь, что оно прошло:

01
02
03
04
05
06
07
08
09
10
11
12
13
class BrokerTest extends PHPUnit_Framework_TestCase {
 
    // […]
 
    /**
     * @expectedException SpentTooMuchException
     */
    function testBuyTooMuch() {
        $broker = new Broker(‘Finch’, ‘2’);
        $broker->buy(‘MS’, 10000, 8);
    }
 
}

Создайте новый метод в нашем аспекте, чтобы он соответствовал @AfterThrowing , и не забудьте указать Use Go\Lang\Annotation\AfterThrowing;

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// […]
Use Go\Lang\Annotation\AfterThrowing;
 
class BrokerAspect implements Aspect {
 
    // […]
 
    /**
     * @param MethodInvocation $invocation Invocation
     * @AfterThrowing(«execution(public Broker->buy(*))»)
     */
    public function afterExceptionMethodExecution(MethodInvocation $invocation) {
        echo ‘An exception has happened’;
    }
 
}

Средство @AfterThrowing подавляет выброшенное исключение и позволяет вам предпринимать собственные действия. В нашем коде мы просто выводим сообщение, но вы можете делать все, что требует ваше приложение.


Вот почему я призываю вас использовать аспекты с осторожностью.

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

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

Использование нами аспекта предоставления скидки для конкретного брокера является примером неправильного использования. Воздержитесь от этого в реальном проекте. Брокерская скидка связана с брокерами; Итак, сохраните эту логику в классе Broker . Аспекты должны выполнять только те задачи, которые непосредственно не связаны с основным поведением объекта.

Веселитесь с этим!