溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2020-07-20 05:30:36 來源:網(wǎng)絡(luò) 閱讀:249 作者:dyllove98 欄目:web開發(fā)


  面向方面編程(AOP)對于PHP來說是一個新的概念。現(xiàn)在PHP對于 AOP 并沒有官方支持,但有很多擴展和庫實現(xiàn)了這個特性。本課中,我們將使用 Go! PHP library 來學(xué)習(xí) PHP 如何進(jìn)行 AOP 開發(fā),或者在需要的時候,可以回來看一眼。

 AOP簡史

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

  面向方面編程的思想在二十世紀(jì)90年代中期,于施樂帕洛阿爾托研究中心(PARC)成型。同很多有趣的新技術(shù)一樣,由于缺少明確的定義,起初 AOP 備受爭議。因此相關(guān)小組決定將未完成的想法公之于眾,以便接受廣大社區(qū)的反饋。關(guān)鍵問題在于“關(guān)注點分離(Separation of Concerns)”的概念。AOP 是一種可以分離關(guān)注的可行系方案。

  AOP 于90年代末趨于成熟,標(biāo)識為施樂 AspectJ 的發(fā)布,IBM 緊隨其后,于2001年發(fā)布了 Hyper/J。現(xiàn)在,AOP是一種對于常用編程語言來說都是一種成熟的技術(shù)。

 基本詞匯

  AOP 的核心就是“方面”,但在我們定義「方面『aspect』」之前,我們需要先討論兩個術(shù)語;「切點『 point-cut』 」和「通知advise』」。切點代表我們代碼中的一個時間點,特指運行我們代碼的某個時間。在切點運行代碼被稱為通知,結(jié)合一個活多個切點及通知的即為方面。

  通常,每個類都會有一個核心的行為或關(guān)注點,但有時,類可能存在次要的行為。例如,類可能會調(diào)用一個日志記錄器或是通知一個觀察員。因為類中的這些功能是次要的,其行為通常都是相同的。這種行為被稱為“交叉關(guān)注點”;使用 AOP 可以避免。

 PHP的各種AOP工具

  Chris Peters 已經(jīng)討論過在PHP中實現(xiàn) AOP 的Flow 框架。 Lithium 框架也提供了對AOP的實現(xiàn)。

  另一個框架采用了不同的方法,創(chuàng)建了一個 C/C++ 編寫的PHP擴展,在PHP解釋器的層級上宣示著它的魔力。名為AOP PHP Extension,我會在后續(xù)文章中討論它。

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

 安裝并配置 Go!

  Go! 庫并未擴展;它完全由PHP編寫,并為PHP5.4或更高版本使用。作為一個純PHP庫,它部署簡易,即使是在不允許編譯安裝你自己的PHP擴展的受限及共享主機環(huán)境,也可以輕易安裝。

  使用 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 將會在之后數(shù)秒中內(nèi)安裝引用的包以及需求。如果成功,你將看到類似下面的輸出:

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

  在安裝完成后,你可以在你的代碼目錄中發(fā)現(xiàn)名為 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

  整合到我們的項目

  我們需要創(chuàng)建一個調(diào)用,介于路由/應(yīng)用程序的入口點。自動裝彈機的然后自動包括類。開始吧!引用作為一個切面內(nèi)核。

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() {
 
    }
 
}
現(xiàn)在,AOP是一種在通用編程語言中相當(dāng)成熟的技術(shù)。

  例如,我創(chuàng)建了一個目錄,調(diào)用應(yīng)用程序,然后添加一個類文件: ApplicationAspectKernel.php 。

  我們開始切面擴展!AcpectKernel 類提供了基礎(chǔ)的方法用于完切面內(nèi)核的工作。有兩個方法,我們必須知道:configureAop()用于注冊頁面特征,和 getApplicationLoaderPath() 返回自動加載程序的全路徑。

  現(xiàn)在,一個簡單的建立一個空的 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';
    }
 
}

  別擔(dān)心 autoload.php 就是這樣。我們將會填寫被省略的片段。

  當(dāng)我們第一次安裝 Go語言!和達(dá)到這一點我的過程中,我覺得需要運行一些代碼。所以開始構(gòu)建一個小應(yīng)用程序。

 創(chuàng)建一個簡單的日志記錄器

  我們的「方面」為一個簡單的日志記錄器,但在繼續(xù)我們應(yīng)用的主要部分之前,有些代碼需要看一下。

  創(chuàng)建一個最小的應(yīng)用

  我們的小應(yīng)用是一個電子經(jīng)紀(jì)人,能夠購買和***。

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 類擁有兩個私有字段,儲存經(jīng)紀(jì)人的名稱和 ID。

  這個類同時提供了兩個方法,buy() 和 sell(),分別用于收購和***。每個方法接受三個參數(shù):股票標(biāo)識、股票數(shù)量、每股價格。sell() 方法***,并計算總收益。相應(yīng)的,buy()方法購買股票并計算總支出。

  考驗我們的經(jīng)紀(jì)人

  通過PHPUnit 測試程序,我們可以很容易的考驗我們經(jīng)紀(jì)人。在應(yīng)用目錄內(nèi)創(chuàng)建一個子目錄,名為 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));
    }
 
}

  這個檢驗程序檢查經(jīng)紀(jì)人方法的返回值。我們可以運行這個檢查程序檢驗我們的代碼,至少是不是語法正確。

  添加一個自動加載器

  讓我們創(chuàng)建一個自動加載器,在應(yīng)用需要的時候加載類。這是一個簡單的加載器,基于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 文件中的全部內(nèi)容?,F(xiàn)在,變更 BrokerTest.php, 改引用Broker.php 為引用自動加載器 。

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

  運行 BrokerTest,驗證代碼運行情況。

  連接到應(yīng)用方面核心

  我們最后的一件事是配置Go!.為此,我們需要連接所有的組件讓們能和諧工作。首先,創(chuàng)建一個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框架初始化并在需要時調(diào)用autoload.php

  第一行,我明確地載入AspectKernel.php和ApplicationAspectKernel.php,因為,要記住,在這個點我們還沒有自動加載器。

  接下來的代碼段,我們調(diào)用ApplicationAspectKernel對象init()方法,并且給他傳遞了一個數(shù)列參數(shù):

  • autoload 定義了初始化AOP類庫的路徑。根據(jù)你實際的目錄機構(gòu)調(diào)整為相應(yīng)的值。

  • appDir 引用了應(yīng)用的目錄

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

  • includePaths 對aspects的一個過濾器。我想看到所有特定的目錄,所以設(shè)置了一個空數(shù)組,以便看到所有的值。

  • debug  提供了額外的調(diào)試信息,這對開發(fā)非常有用,但是對已經(jīng)要部屬的應(yīng)用設(shè)置為false。

  為了最后實現(xiàn)各個不同部分的連接,找出你工程中autoload.php自動加載所有的引用并且用AspectKernelLoader.php替換他們。在我們簡單的例子中,僅僅test文件需要修改:

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

  對大一點的工程,你會發(fā)現(xiàn)使用bootstrap.php作為單元測試但是非常有用;用require_once()做為autoload.php,或者我們的AspectKernelLoader.php應(yīng)該在那載入。

  記錄Broker的方法

  創(chuàng)建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框架有用的語句。接著,我們創(chuàng)建了自己的方面類叫BrokerAspect,用它實現(xiàn)Aspect。接著,我們指定了我們aspect的匹配邏輯。

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

  • "execution(public Broker->*(*))" 給執(zhí)行一個類所有的公共方法指出了匹配規(guī)則,可以用任意數(shù)量的參數(shù)調(diào)用Broker,語法是:                 

1
[operation - execution/access]([method/attribute type - public/protected] [class]->[method/attribute]([params])

   請注意匹配機制不可否認(rèn)有點笨拙。你在規(guī)則的每一部分僅可以使用一個星號‘*‘。例如public Broker->匹配一個叫做Broker的類;public Bro*->匹配以Bro開頭的任何類;public *ker->匹配任何ker結(jié)尾的類。

public *rok*->將匹配不到任何東西;你不能在同一個匹配中使用超過一個的星號。

   緊接著匹配程序的函數(shù)會在有時間發(fā)生時調(diào)用。在本例中的方法將會在每一個Broker公共方法調(diào)用之前執(zhí)行。其參數(shù)$invocation(類型為MethodInvocation)子自動傳遞到我們的方法的。這個對象提供了多種方式獲取調(diào)用方法的信息。在第一個例子中,我們使用他獲取了方法的名字,并且輸出。

  注冊切面

  僅僅定義一個切面是不夠的;我們需要把它注冊到AOP架構(gòu)里。否則,它不會生效。編輯ApplicationAspectKernel.php同時在容器上的configureAop()方法里調(diào)用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)

  就這樣我們已設(shè)法讓代碼無論什么時候發(fā)生在broker上時都會執(zhí)行。

  查找參數(shù)和匹配@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";
    }
}

  這個方法在一個公共方法執(zhí)行后運行(注意@After匹配器)。染污我們加入另外一行來輸出用來調(diào)用方法的參數(shù)。我們的測試現(xiàn)在輸出:

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)

  獲得返回值并操縱運行

  目前為止,我們學(xué)習(xí)了在一個方法執(zhí)行的之前和之后,怎樣運行額外的代碼。當(dāng)這個漂亮的實現(xiàn)后,如果我們無法看到方法返回了什么的話,它還不是非常有用。我們給aspect增加另一個方法,修改現(xiàn)有的代碼:

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基礎(chǔ)設(shè)施。

  這個新的代碼把參數(shù)信息移動到@Before方法。我們也增加了另一個特殊的@Around匹配器方法。這很整潔,因為原始的匹配方法調(diào)用被包裹于aroundMethodExecution()函數(shù)之內(nèi),有效的限制了原始的調(diào)用。在advise里,我們要調(diào)用$invocation->proceed(),以便執(zhí)行原始的調(diào)用。如果你不這么做,原始的調(diào)用將不會發(fā)生。

  這種包裝也允許我們操作返回值。advise返回的就是原始調(diào)用返回的。在我們的案例中,我們沒有修改任何東西,輸出應(yīng)該看起來像這樣:

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));
    }
 
}

  這會失?。?/p>

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。只要像下面所示實現(xiàn)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;
    }
 
    // [...]
 
}

  現(xiàn)在,修改aspect以調(diào)整具有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()函數(shù)?,F(xiàn)在它正好匹配方法,稱作‘buy‘,并觸發(fā)了$invocation->getThis()。這有效的返回了原始的Broker對象,以便我們可以執(zhí)行它的代碼。于是我們做到了!我們向broker要它的ID,如果ID等于2的話就提供一個折扣。測試現(xiàn)在通過了。

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)

  匹配異常

  我們現(xiàn)在可以在一個方法的開始和執(zhí)行之后、繞過時,執(zhí)行附加程序。但當(dāng)方法拋出異常時又如何呢?

  添加一個測試方法來購買大量微軟的股票:

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

  現(xiàn)在,創(chuàng)建一個異常類。我們需要它是因為內(nèi)建的異常類不能被 Go!AOP 或 PHPUnit 捕捉.

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

  修改經(jīng)紀(jì)人類,對大值拋出異常:

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;
    }
 
    // [...]
 
}

  運行測試,確保它們產(chǎn)生失敗消息:

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.

  現(xiàn)在,期待異常(在測試中),確保它們通過:

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匹配器抑制拋出的異常,并允許你去采取自己的行動。在我們的代碼中,我們簡單的顯示一個信息,但你可以做任何你的應(yīng)用程序需要的事情。

 最后的思考

這就是為什么我建議你小心使用“方面”。

  面向方面編程就像給怪人們的新玩意兒;您可以立即看到其巨大的潛力。方面允許我們在我們的系統(tǒng)的不同部分引入額外的代碼,而無需修改原始代碼。當(dāng)你需要實現(xiàn)一些通過緊耦合引用和方法調(diào)用會污染你的方法和類的模塊時,這會非常有用。

  然而,這種靈活性,是有代價的:陰暗朦朧。有沒有辦法告訴如果一方面表的方法只是在尋找方法或類。例如,在我們的Broker類中執(zhí)行方法時沒有跡象表明發(fā)生任何事情。這就是為什么我建議你小心使用“方面”的原因。

  我們使用“方面”來給一個特定的經(jīng)紀(jì)人提供折扣是誤用的一個例子。不要在一個真實的項目中這樣做。經(jīng)紀(jì)人的折扣與經(jīng)紀(jì)人相關(guān);所以,在Broker類中保持這個邏輯。“方面”應(yīng)該只執(zhí)行不直接關(guān)系到對象主要行為的任務(wù)。

  樂在其中吧!

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

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI