你據說過PHP 的面向方面編程嗎?


  面向方面編程(AOP)對於PHP來講是一個新的概念。如今PHP對於 AOP 並無官方支持,但有不少擴展和庫實現了這個特性。本課中,咱們將使用 Go! PHP library 來學習 PHP 如何進行 AOP 開發,或者在須要的時候,能夠回來看一眼。php

 AOP簡史

Aspect-Oriented programming is like a new gadget for geeks.html

  面向方面編程的思想在二十世紀90年代中期,於施樂帕洛阿爾託研究中心(PARC)成型。同不少有趣的新技術同樣,因爲缺乏明確的定義,起初 AOP 備受爭議。所以相關小組決定將未完成的想法公之於衆,以便接受廣大社區的反饋。關鍵問題在於「關注點分離(Separation of Concerns)」的概念。AOP 是一種能夠分離關注的可行系方案。前端

  AOP 於90年代末趨於成熟,標識爲施樂 AspectJ 的發佈,IBM 緊隨其後,於2001年發佈了 Hyper/J。如今,AOP是一種對於經常使用編程語言來講都是一種成熟的技術。git

 基本詞彙

  AOP 的核心就是「方面」,但在咱們定義「方面『aspect』」以前,咱們須要先討論兩個術語;「切點『 point-cut』 」和「通知advise』」。切點表明咱們代碼中的一個時間點,特指運行咱們代碼的某個時間。在切點運行代碼被稱爲通知,結合一個活多個切點及通知的即爲方面程序員

  一般,每一個類都會有一個核心的行爲或關注點,但有時,類可能存在次要的行爲。例如,類可能會調用一個日誌記錄器或是通知一個觀察員。由於類中的這些功能是次要的,其行爲一般都是相同的。這種行爲被稱爲「交叉關注點」;使用 AOP 能夠避免。github

 PHP的各類AOP工具

  Chris Peters 已經討論過在PHP中實現 AOP 的Flow 框架。 Lithium 框架也提供了對AOP的實現。編程

  另外一個框架採用了不一樣的方法,建立了一個 C/C++ 編寫的PHP擴展,在PHP解釋器的層級上宣示着它的魔力。名爲AOP PHP Extension,我會在後續文章中討論它。json

  但正如我以前所言,本文將檢閱Go! AOP-PHP 庫。bootstrap

 安裝並配置 Go!

  Go! 庫並未擴展;它徹底由PHP編寫,併爲PHP5.4或更高版本使用。做爲一個純PHP庫,它部署簡易,即便是在不容許編譯安裝你本身的PHP擴展的受限及共享主機環境,也能夠輕易安裝。數組

  使用 Composer 安裝 Go!

  Composer 是安裝 PHP 包的首選方法。若是你沒有使用過 Composer,你能夠在Go! GitHub repository下載。

  首先,將下面幾行加入你的 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 將會在以後數秒中內安裝引用的包以及需求。若是成功,你將看到相似下面的輸出:

1
2
3
4
5
6
7
8
9
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 的文件夾。Go! 庫及其需求就安裝在這。

1
2
3
4
5
6
7
8
9
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

  整合到咱們的項目

  咱們須要建立一個調用,介於路由/應用程序的入口點。自動裝彈機的而後自動包括類。開始吧!引用做爲一個切面內核。

1
2
3
4
5
6
7
8
9
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是一種在通用編程語言中至關成熟的技術。

  例如,我建立了一個目錄,調用應用程序,而後添加一個類文件: ApplicationAspectKernel.php 。

  咱們開始切面擴展!AcpectKernel 類提供了基礎的方法用於完切面內核的工做。有兩個方法,咱們必須知道:configureAop()用於註冊頁面特徵,和 getApplicationLoaderPath() 返回自動加載程序的全路徑。

  如今,一個簡單的創建一個空的 autoload.php 文件在你的程序目錄。和改變 getApplicationLoaderPath() 方法。以下:

1
2
3
4
5
6
7
8
9
10
// [...]
class ApplicationAspectKernel extends AspectKernel {
 
     // [...]
 
     protected function getApplicationLoaderPath() {
         return __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php' ;
     }
 
}

  別擔憂 autoload.php 就是這樣。咱們將會填寫被省略的片斷。

  當咱們第一次安裝 Go語言!和達到這一點個人過程當中,我以爲須要運行一些代碼。因此開始構建一個小應用程序。

 建立一個簡單的日誌記錄器

  咱們的「方面」爲一個簡單的日誌記錄器,但在繼續咱們應用的主要部分以前,有些代碼須要看一下。

  建立一個最小的應用

  咱們的小應用是一個電子經紀人,可以購買和***。

1
2
3
4
5
6
7
8
9
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 類擁有兩個私有字段,儲存經紀人的名稱和 ID。

  這個類同時提供了兩個方法,buy() 和 sell(),分別用於收購和***。每一個方法接受三個參數:股票標識、股票數量、每股價格。sell() 方法***,並計算總收益。相應的,buy()方法購買股票並計算總支出。

  考驗咱們的經紀人

  經過PHPUnit 測試程序,咱們能夠很容易的考驗咱們經紀人。在應用目錄內建立一個子目錄,名爲 Test,並在其中添加 BrokerTest.php 文件。並添加下面的代碼:

1
2
3
4
5
6
7
8
9
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 autoloader.

1
2
3
4
5
6
7
8
9
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 ) . DIRECTORY_SEPARATOR;
     }
     $fileName .= str_replace ( '_' , DIRECTORY_SEPARATOR, $className ) . '.php' ;
 
     $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!.爲此,咱們須要鏈接全部的組件讓們能和諧工做。首先,建立一個php文件AspectKernelLoader.php,其代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
include __DIR__ . '/../vendor/lisachenko/go-aop-php/src/Go/Core/AspectKernel.php' ;
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__ . '/../Application' ,
     'cacheDir' => null,
     'includePaths' => array (),
     'debug' => true
));

咱們須要鏈接全部的組件讓們能和諧工做!

  這個文件位於前端控制器和自動加載器之間。他使用AOP框架初始化並在須要時調用autoload.php

  第一行,我明確地載入AspectKernel.php和ApplicationAspectKernel.php,由於,要記住,在這個點咱們尚未自動加載器。

  接下來的代碼段,咱們調用ApplicationAspectKernel對象init()方法,而且給他傳遞了一個數列參數:

  • autoload 定義了初始化AOP類庫的路徑。根據你實際的目錄機構調整爲相應的值。

  • appDir 引用了應用的目錄

  • cacheDir 指出了緩存目錄(本例中中咱們忽略緩存)。

  • includePaths 對aspects的一個過濾器。我想看到全部特定的目錄,因此設置了一個空數組,以便看到全部的值。

  • debug  提供了額外的調試信息,這對開發很是有用,可是對已經要部屬的應用設置爲false。

  爲了最後實現各個不一樣部分的鏈接,找出你工程中autoload.php自動加載全部的引用而且用AspectKernelLoader.php替換他們。在咱們簡單的例子中,僅僅test文件須要修改:

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

  對大一點的工程,你會發現使用bootstrap.php做爲單元測試可是很是有用;用require_once()作爲autoload.php,或者咱們的AspectKernelLoader.php應該在那載入。

  記錄Broker的方法

  建立BrokerAspect.php文件,代碼以下:

1
2
3
4
5
6
7
8
9
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 " . $invocation ->getMethod()->getName() . "()\n" ;
     }
}

  咱們在程序開始指定一些有對AOP框架有用的語句。接着,咱們建立了本身的方面類叫BrokerAspect,用它實現Aspect。接着,咱們指定了咱們aspect的匹配邏輯。

1
* @Before( "execution(public Broker->*(*))" )
  • @Before 給出合適應用建議. 可能的參數有@Before,@After,@Around和@After線程.

  • "execution(public Broker->*(*))" 給執行一個類全部的公共方法指出了匹配規則,能夠用任意數量的參數調用Broker,語法是:                 

1
[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)子自動傳遞到咱們的方法的。這個對象提供了多種方式獲取調用方法的信息。在第一個例子中,咱們使用他獲取了方法的名字,而且輸出。

  註冊切面

  僅僅定義一個切面是不夠的;咱們須要把它註冊到AOP架構裏。不然,它不會生效。編輯ApplicationAspectKernel.php同時在容器上的configureAop()方法裏調用registerAspect():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Go\Core\AspectKernel;
use Go\Core\AspectContainer;
 
class ApplicationAspectKernel extends AspectKernel
{
 
     protected function getApplicationLoaderPath()
     {
         return __DIR__ . DIRECTORY_SEPARATOR . 'autoload.php' ;
     }
 
     protected function configureAop(AspectContainer $container )
     {
         $container ->registerAspect( new BrokerAspect());
     }
}

  運行測試和檢查輸出。你會看到相似下面的東西:

1
2
3
4
5
6
7
8
9
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)

  就這樣咱們已設法讓代碼不管何時發生在broker上時都會執行。

  查找參數和匹配@After

  讓咱們加入另外的方法到BrokerAspect。

1
2
3
4
5
6
7
8
9
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 " . $invocation ->getMethod()->getName() . "()\n" ;
         echo "with parameters: " . implode( ', ' , $invocation ->getArguments()) . ".\n\n" ;
     }
}

  這個方法在一個公共方法執行後運行(注意@After匹配器)。染污咱們加入另一行來輸出用來調用方法的參數。咱們的測試如今輸出:

1
2
3
4
5
6
7
8
9
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)

  得到返回值並操縱運行

  目前爲止,咱們學習了在一個方法執行的以前和以後,怎樣運行額外的代碼。當這個漂亮的實現後,若是咱們沒法看到方法返回了什麼的話,它還不是很是有用。咱們給aspect增長另外一個方法,修改現有的代碼:

1
2
3
4
5
6
7
8
9
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 " . $invocation ->getMethod()->getName() . "()\n" ;
         echo "with parameters: " . implode( ', ' , $invocation ->getArguments()) . ".\n" ;
     }
 
     /**
      * @param MethodInvocation $invocation Invocation
      * @After("execution(public Broker->*(*))")
      */
     public function afterMethodExecution(MethodInvocation $invocation ) {
         echo "Finished executing method " . $invocation ->getMethod()->getName() . "()\n\n" ;
     }
 
     /**
      * @param MethodInvocation $invocation Invocation
      * @Around("execution(public Broker->*(*))")
      */
     public function aroundMethodExecution(MethodInvocation $invocation ) {
         $returned = $invocation ->proceed();
         echo "method returned: " . $returned . "\n" ;
 
         return $returned ;
     }
 
}

僅僅定義一個aspect是不夠的;咱們須要將它註冊到AOP基礎設施。

  這個新的代碼把參數信息移動到@Before方法。咱們也增長了另外一個特殊的@Around匹配器方法。這很整潔,由於原始的匹配方法調用被包裹於aroundMethodExecution()函數以內,有效的限制了原始的調用。在advise裏,咱們要調用$invocation->proceed(),以便執行原始的調用。若是你不這麼作,原始的調用將不會發生。

  這種包裝也容許咱們操做返回值。advise返回的就是原始調用返回的。在咱們的案例中,咱們沒有修改任何東西,輸出應該看起來像這樣:

1
2
3
4
5
6
7
8
9
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)

  咱們增長一點變化,賦以一個具體的broker一個discount。返回到測試類,寫以下的測試:

1
2
3
4
5
6
7
8
9
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));
     }
 
}

  這會失敗:

1
2
3
4
5
6
7
8
9
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.

  下一步,咱們須要修改broker以便提供它的ID。只要像下面所示實現agetId()方法:

1
2
3
4
5
6
7
8
9
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;
     }
 
     // [...]
 
}

  如今,修改aspect以調整具備ID值爲2的broker的購買價格。

1
2
3
4
5
6
7
8
9
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對象,以便咱們能夠執行它的代碼。因而咱們作到了!咱們向broker要它的ID,若是ID等於2的話就提供一個折扣。測試如今經過了。

1
2
3
4
5
6
7
8
9
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);
}

  如今,建立一個異常類。咱們須要它是由於內建的異常類不能被 Go!AOP 或 PHPUnit 捕捉.

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

  修改經紀人類,對大值拋出異常:

1
2
3
4
5
6
7
8
9
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 ;
     }
 
     // [...]
 
}

  運行測試,確保它們產生失敗消息:

1
2
3
4
5
6
7
8
9
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.

  如今,期待異常(在測試中),確保它們經過:

1
2
3
4
5
6
7
8
9
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;

1
2
3
4
5
6
7
8
9
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類中保持這個邏輯。「方面」應該只執行不直接關係到對象主要行爲的任務。

  樂在其中吧!

  英文原文:Aspect-Oriented Programming in PHP with Go!

相關文章
相關標籤/搜索