Laravel5.3之Decorator Pattern

說明:Laravel中Middleware的實現主要利用了Decorator Pattern的設計,本文主要先學習下Decorator Pattern如何實現,爲後面學習Middleware的設計作個鋪墊。Decorator Pattern和Adapter Pattern會有不少類似之處,但相比較於Adapter Pattern重點突出adapter,Decorator Pattern重點突出的是wrapper,兩個不是同一律念。php

開發環境:Laravel5.3 + PHP7 + OS X 10.11api

Decorator Pattern

Decorator Pattern做爲一種結構型模式,能夠給現有對象Component裝飾decorate幾個feature,而不影響原有的Component對象,這幾個feature就是裝飾對象Decorator。這種設計很好用,由於能夠隨時增長或減小想要的feature,而且增長或減小這種操做又很簡單,實現了程序鬆耦合。就像Laravel中每個middleware就是一個feature,若是想要增長一個不緩存request的feature,能夠增長一個middleware假設叫作NoCacheMiddleware,寫好後只須要在app/Http/Kernel.php文件中添加下配置就可。看下一個簡單的demo實例,看看如何使用Decorator Pattern。先定義一個IMiddleware的接口,保證設計的features都是同一物種,即只有實現了該接口的feature才稱爲middleware:緩存

namespace MyRightCapital\Development\DecoratorPattern;

interface IMiddleware
{
    public function handle();
}

在該接口中定義一個handle()函數,每個feature必須實現這個handle()來作邏輯。如今須要設計5個features,而且每個feature都必須是middleware:cookie

$features = [
    CheckForMaintenanceMode::class,
    AddQueuedCookiesToResponse::class,
    StartSession::class,
    ShareErrorsFromSession::class,
    VerifyCsrfToken::class,
];

OK,如今實現第一個feature,並改造爲middleware:session

namespace MyRightCapital\Development\DecoratorPattern;

class CheckForMaintenanceMode implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * CheckForMaintenanceMode constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        echo 'Check if the application is in the maintenance status.' . PHP_EOL;
        $this->middleware->handle();
    }
}

第一個middleware是CheckForMaintenanceMode,須要檢查程序是否處於維護模式。實現第二個feature,並改造爲middleware:app

namespace MyRightCapital\Development\DecoratorPattern;

class AddQueuedCookiesToResponse implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * AddQueuedCookiesToResponse constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        $this->middleware->handle();
        echo 'Add queued cookies to the response' . PHP_EOL;
    }
}

第二個middleware實現把cookie添加到response。實現第三個feature,並改造爲middleware:composer

namespace MyRightCapital\Development\DecoratorPattern;

class StartSession implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * StartSession constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        echo 'Start session of this request.' . PHP_EOL;
        $this->middleware->handle();
        echo 'Close session of this request.' . PHP_EOL;
    }
}

第三個feature主要實現開啓和關閉session。實現第四個feature,並改造爲middleware:函數

class ShareErrorsFromSession implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * ShareErrorsFromSession constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        $this->middleware->handle();
        echo 'Share the errors variable from request to the views.' . PHP_EOL;
    }
}

第四個feature主要實現共享變量$errors,以便在視圖中使用該變量。實現第五個feature,並改造爲middleware:post

namespace MyRightCapital\Development\DecoratorPattern;

class VerifyCsrfToken implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * VerifyCsrfToken constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        echo 'Verify csrf token when post request.' . PHP_EOL;
        $this->middleware->handle();
    }
}

第五個feature主要實現CSRF驗證。OK,如今每個feature都已經實現了,並將做爲Decorator來裝飾初始的Component。學習

OK,Decorator Pattern中已經有了五個Decorators,如今須要實現一個Component,而後用這五個Decorators來裝飾Component。如今定義一個Component接口,保證Component與Decorator是類似物種,而且Component又有本身的實現接口:

namespace MyRightCapital\Development\DecoratorPattern;

interface IComponent extends IMiddleware
{
    public function getRequest();
}

如今構造一個Component:

namespace MyRightCapital\Development\DecoratorPattern;

class Request implements IComponent
{
    public function handle()
    {
        echo 'This is a request from the client. And this request will go through the middlewares.' . PHP_EOL;
    }

    public function getRequest()
    {
        return $this;
    }
}

OK,在Decorator Pattern中,目前已經構造好了Component和Decorator。把Component和Decorator拼接在一塊兒的場所是Client,因此須要造一個Client類,在其內部實現對Component的Decorate操做:

namespace MyRightCapital\Development\DecoratorPattern;

class Client
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\Request
     */
    protected $request;

    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    protected $response;

    public function __construct()
    {
        // Component
        $this->request  = new Request();
        
        // Decorate the Component
        $this->response = $this->wrapDecorator($this->request);
    }

    /**
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $decorator
     *
     * @return \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    public function wrapDecorator(IMiddleware $decorator)
    {
        $decorator = new VerifyCsrfToken($decorator);
        $decorator = new ShareErrorsFromSession($decorator);
        $decorator = new StartSession($decorator);
        $decorator = new AddQueuedCookiesToResponse($decorator);
        $response  = new CheckForMaintenanceMode($decorator);

        return $response;
    }

    /**
     * @return \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    public function getResponse()
    {
        return $this->response->handle();
    }
}

Client中wrapDecorator()實現了把原有的Component進過5個Middlewares的裝飾後獲得的新的Component,新的Component仍是IMiddleware的實現,仍是原來的物種。整個UML圖:

OK,如今執行整個Decorator Pattern,看看是否是這些middlewares已經被裝飾進原來的Component,建立一個index.php文件:

// 加載composer的autoload.php文件
include __DIR__ . '/../../../vendor/autoload.php';

$client = new \MyRightCapital\Development\DecoratorPattern\Client();
$client->getResponse();

php index.php文件看看輸出什麼:

Check if the application is in the maintenance status.
Start session of this request.
Verify csrf token when post request.
This is a request from the client. And this request will go through the middlewares.
Share the errors variable from request to the views.
Close session of this request.
Add queued cookies to the response.

的確,五個middlewares已經裝飾了原有的component,並檢查下裝飾次序是不是正確的?實際上,Client中的$this->response等同於:

$response = new CheckForMaintenanceMode(
                new AddQueuedCookiesToResponse(
                    new StartSession(
                        new ShareErrorsFromSession(
                            new VerifyCsrfToken(
                                new Request()
                        )
                    )
                )
            )
        );

因此,執行次序是:

1. CheckForMaintenanceMode::handle() -> 先執行 echo 'Check if the application is in the maintenance status.', 而後執行 AddQueuedCookiesToResponse::handle()
2. AddQueuedCookiesToResponse::handle() -> 先執行 StartSession::handle(), 而後執行 echo 'Add queued cookies to the response.'
3. StartSession::handle() -> 先執行 echo 'Start session of this request.', 而後執行 ShareErrorsFromSession::handle(), 最後執行 echo 'Close session of this request.'
4. ShareErrorsFromSession::handle() -> 先執行VerifyCsrfToken::handle(), 而後執行 echo 'Share the errors variable from request to the views.'
5. VerifyCsrfToken::handle() -> 先執行 echo 'Verify csrf token when post request.', 而後執行 Request::handle()
6. Request::handle() -> 執行 echo 'This is a request from the client. And this request will go through the middlewares.'

// So,執行順序等同於:
echo 'Check if the application is in the maintenance status.' -> 
echo 'Start session of this request.' -> 
echo 'Verify csrf token when post request.' -> 
echo 'This is a request from the client. And this request will go through the middlewares.' ->
echo 'Share the errors variable from request to the views.' ->
echo 'Close session of this request.' ->
echo 'Add queued cookies to the response.' ->

在Laravel裏每個Middleware中有前置操做和後置操做。在本demo裏echo語句前置於$this->middleware->handle();則爲前置操做,後置則爲後置操做。
OK,再加一個Kernel類,保證Request通過Middleware的前置操做後進入Kernel,而後從Kernel出來進入Middlewares的後置操做,一步步過濾:

namespace MyRightCapital\Development\DecoratorPattern;

interface IKernel extends IMiddleware
{

}

class Kernel implements IKernel
{
    public function handle()
    {
        echo 'Kernel handle the request, and send the response.' . PHP_EOL;
    }
}

// 修改Request
class Request implements IRequest
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IKernel
     */
    private $kernel;

    public function __construct(IKernel $kernel)
    {
        $this->kernel = $kernel;
    }

    public function handle()
    {
        echo 'This request has been filtering by the before action in the middlewares, and go into the kernel.' . PHP_EOL;
        $this->kernel->handle();
        echo 'The request has been handled by the kernel, and will be send to the after action in the middlewares' . PHP_EOL;
    }

    public function getRequest()
    {
        return $this;
    }
}

// 修改下Client的構造函數
public function __construct()
    {
        // Component
        $this->request = new Request(new Kernel());

        // Decorate the Component
        $this->response = $this->wrapDecorator($this->request);
    }

則再次執行index.php文件,獲得:

Check if the application is in the maintenance status.
Start session of this request.
Verify csrf token when post request.
This request has been filtering by the before action in the middlewares, and go into the kernel.
Kernel handle the request, and send the response.
The request has been handled by the kernel, and will be send to the after action in the middlewares
Share the errors variable from request to the views.
Close session of this request.
Add queued cookies to the response.

具體流程上文已經討論,可畫一張草圖展現處理流程,其中Before表示該Middleware的前置操做,After表示該Middleware的後置操做:

OK,使用Decorator Pattern來層層過濾Request,並實現分層,最後進入Kernel執行獲得Response,而後Response通過層層過濾,返回給客戶端。很是讚的設計。

總結:本文主要學習Laravel如何使用Decorator Pattern來設計Middleware。下一篇學習下Laravel中Middleware的源碼。

相關文章
相關標籤/搜索