Концепция аспектно-ориентированного программирования (AOP) является довольно новой для PHP. В настоящее время в PHP нет официальной поддержки AOP, но есть некоторые расширения и библиотеки, которые реализуют эту функцию. В этом уроке мы будем использовать Go! Библиотека PHP для изучения АОП в PHP и обзор, когда это может быть полезно.
Краткая история АОП
Аспектно-ориентированное программирование похоже на новый гаджет для гиков.
Термин Аспектно-ориентированное программирование сформировался в середине 1990-х годов в небольшой группе в исследовательском центре Xerox в Пало-Альто (PARC). AOP считался спорным в первые дни — как в случае с любой новой и интересной технологией — в основном из-за отсутствия четкого определения. Группа приняла сознательное решение выпустить его в полусгоревшем виде, чтобы дать возможность большему сообществу предоставить обратную связь. В основе проблемы лежала концепция «Разделение интересов». АОП было одним из возможных решений для разделения проблем.
AOP сформировался в конце 1990-х годов, когда Xerox выпустила AspectJ, а IBM в 2001 году последовала их примеру Hyper / J. Сегодня AOP — это устоявшаяся технология, принятая большинством распространенных языков программирования.
Основной словарь
В основе AOP лежит аспект, но прежде чем мы сможем определить «аспект», мы должны обсудить два других термина: срез и совет . Точка отсечения представляет момент в нашем исходном коде, определяя подходящий момент для запуска нашего кода. Код, который выполняется с точечным сокращением, называется, советует, и комбинация одного или нескольких точечных сокращений и советов является аспектом .
Как правило, каждый класс имеет одно основное поведение или проблему, но во многих ситуациях класс может демонстрировать вторичное поведение. Например, классу может потребоваться вызвать регистратор или уведомить наблюдателя. Поскольку эти функции вторичны, их поведение в основном одинаково для всех классов, которые их демонстрируют. Этот сценарий называется перекрестной заботой ; этого можно избежать с помощью АОП.
Различные инструменты АОП для PHP
Крис Питерс уже обсуждал фреймворк Flow для AOP в PHP. Другая реализация АОП может быть найдена в платформе Lithium .
Другой фреймворк использовал другой подход и создал полное расширение PHP на C / C ++, выполняя свою магию на том же уровне, что и интерпретатор PHP. Он называется расширением AOP PHP , и я могу обсудить его в следующей статье.
Но, как я уже отмечал ранее, для этого урока мы рассмотрим Go! AOP-PHP библиотека.
Установка и подготовка Go!
Иди! библиотека не является расширением; он полностью написан на PHP для PHP 5.4 и выше. Простая библиотека PHP позволяет легко развертывать даже в ограниченных средах общего хостинга, которые не позволяют вам компилировать и устанавливать свои собственные расширения PHP.
Установите Go! с композитором
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
|
Интегрируйте Go! В наш проект
Нам нужно создать вызов, который находится между точкой маршрутизации / входа нашего приложения. Затем автозагрузчик автоматически включает класс. Идти! ссылается на это как 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)
|
Таким образом, нам удалось запустить код всякий раз, когда что-то происходит на брокере.
Поиск параметров и сопоставление @After
Давайте добавим еще один метод в 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
. Аспекты должны выполнять только те задачи, которые непосредственно не связаны с основным поведением объекта.
Веселитесь с этим!