Laravel HTTP——Pipeline 中介軟體裝飾者模式原始碼分析

leoyang發表於2017-07-24

前言

本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/lar...
當所有的路由都載入完畢後,就會根據請求的 url 來將請求分發到對應的路由上去。然而,在分發到路由之前還要經過各種中介軟體的計算。laravel 利用裝飾者模式來實現中介軟體的功能。

 

從原始裝飾者模式到閉包裝飾者

裝飾者模式是設計模式的一種,主要進行物件的多次處理與過濾,是在開放-關閉原則下實現動態新增或減少功能的一種方式。下面先看一個裝飾者模式的例子:

總共有兩種咖啡:Decaf、Espresso,另有兩種調味品:Mocha、Whip(3種設計的主要差別在於抽象方式不同)

裝飾模式分為3個部分:

1,抽象元件 -- 對應Coffee類

2,具體元件 -- 對應具體的咖啡,如:Decaf,Espresso

3,裝飾者 -- 對應調味品,如:Mocha,Whip

原始裝飾者模式

public interface Coffee
{  
    public double cost();  
} 

public class Espresso implements Coffee 
{  
    public double cost()
    {  
        return 2.5;  
    }  
}  

public class Dressing implements Coffee 
{  
    private Coffee coffee;  

    public Dressing(Coffee coffee)
    {  
        this.coffee = coffee;  
    }  

    public double cost()
    {  
        return coffee.cost();  
    }  
}  

public class Whip extends Dressing {  
    public Whip(Coffee coffee)
    {  
        super(coffee);  
    }  

    public double cost()
    {  
        return super.cost() + 0.1;  
    }  
} 

public class Mocha extends Dressing 
{  
    public Mocha(Coffee coffee)
    {  
        super(coffee);  
    }  

    public double cost()
    {  
        return super.cost() + 0.5;  
    }  
}

當我們使用裝飾者模式的時候:

public class Test {  
    public static void main(String[] args) {  
        Coffee coffee = new Espresso();  
        coffee = new Mocha(coffee);  
        coffee = new Mocha(coffee);  
        coffee = new Whip(coffee);  
        //3.6(2.5 + 0.5 + 0.5 + 0.1)  
        System.out.println(coffee.cost());  
    }  
}

我們可以看出來,裝飾者模式就是利用裝飾者類來對具體類不斷的進行多層次的處理,首先我們建立了 Espresso 類,然後第一次利用 Mocha 裝飾者對 Espresso 咖啡加了摩卡,第二次重複加了摩卡,第三次利用裝飾者 WhipEspresso 咖啡加了奶油。每次加入新的調料,裝飾者都會對價格 cost 做一些處理(+0.1、+0.5)。

無建構函式的裝飾者

我們對這個裝飾者進行一些改造:

public class Espresso
{  
    double cost;

    public double cost()
    {  
        $this-> cost = 2.5;  
    }  
}  

public class Dressing 
{    
    public double cost(Espresso $espresso)
    {  
        return ($espresso);
    }  
}  

public class Whip extends Dressing 
{  
    public double cost(Espresso $espresso)
    {  
        $espresso->cost = espresso->cost() + 0.1;  

        return ($espresso);
    }  
} 

public class Mocha extends Dressing 
{   
    public double cost(Espresso $espresso)
    {  
        $espresso->cost = espresso->cost() + 0.5;  

        return ($espresso);
    }  
}
public class Test {  
    public static void main(String[] args) {  
        Coffee $coffee = new Espresso();  

        $coffee = (new Mocha())->cost($coffee); 
        $coffee = (new Mocha())->cost($coffee); 
        $coffee = (new Whip())->cost($coffee);  

        //3.6(2.5 + 0.5 + 0.5 + 0.1)  
        System.out.println(coffee.cost());  
    }  
}

改造後,裝飾者類通過函式 cost 來注入具體類 caffee,而不是通過建構函式,這樣做有助於自動化進行裝飾處理。我們改造後發現,想要對具體類通過裝飾類進行處理,需要不斷的呼叫 cost 函式,如果有10個裝飾操作,就要手動寫10個語句,因此我們繼續進行改造:

閉包裝飾者模式

public class Espresso
{  
    double cost;

    public double cost()
    {  
        $this-> cost = 2.5;  
    }  
}  

public class Dressing 
{    
    public double cost(Espresso $espresso,  Closure $closure)
    {  
        return ($espresso);
    }  
}  

public class Whip extends Dressing 
{  
    public double cost(Espresso $espresso, Closure $closure)
    {  
        $espresso->cost = espresso->cost() + 0.1;  

        return $closure($espresso);
    }  
} 

public class Mocha extends Dressing 
{   
    public double cost(Espresso $espresso, Closure $closure)
    {  
        $espresso->cost = espresso->cost() + 0.5;  

        return $closure($espresso);
    }  
}
public class Test {  
    public static void main(String[] args) {  
        Coffee $coffee = new Espresso();  

        $fun = function($coffee,$fuc,$dressing) {
            $dressing->cost($coffee, $fuc); 
        }                

        $fuc0 = function($coffee) {
            return $coffee;
        };

        $fuc1 = function($coffee) use ($fuc0, $dressing = (new Mocha(),$fun)) {
            return $fun($coffee, $fuc0, $dressing);
        }

        $fuc2 = function($coffee) use ($fuc1, $dressing = (new Mocha(),$fun)) {
            return $fuc($coffee, $fun1, $dressing);
        }

        $fuc3 = function($coffee) use ($fuc2, $dressing = (new Whip(),$fun) ){
            return $fuc($coffee, $fun2, $dressing);
        }

        $coffee = $fun3($coffee);

        //3.6(2.5 + 0.5 + 0.5 + 0.1)  
        System.out.println(coffee.cost());  
    }  
}

在這次改造中,我們使用了閉包函式,這樣做的目的在於,我們只需要最後一句 $fun3($coffee),就可以啟動整個裝飾鏈條。

閉包裝飾者的抽象化

然而這種改造還不夠深入,因為我們還可以把 $fuc1$fuc2$fuc3 繼續抽象化為一個閉包函式,這個閉包函式僅僅是引數 $fuc$dressing 每次不同,$coffee 相同,因此改造如下:

public class Test {  
    public static void main(String[] args) {  
        Coffee $coffee = new Espresso();  

        $fun = function($coffee) use ($fuc,$dressing) {
            $dressing->cost($coffee, $fuc); 
        }

        $fuc = function($fuc,$dressing) use ($fun) {
            return $fun;
        };

        $fuc0 = function($coffee) {
            return $coffee;
        };

        $fuc1 = $fuc($fuc0, (new Mocha());

        $fuc2 = $fuc($fuc1, (new Mocha());

        $fuc3 = $fuc($fuc2, (new Whip());

        $coffee = $fun3($coffee);

        //3.6(2.5 + 0.5 + 0.5 + 0.1)  
        System.out.println(coffee.cost());  
    }  
}

這次,我們把之前的閉包分為兩個部分,$fun 負責具體類的引數傳遞,$fuc負責裝飾者和閉包函式的引數傳遞。在最後一句 $fun3,只需要傳遞一個具體類,就可以啟動整個裝飾鏈條。

閉包裝飾者的自動化

到這裡,我們還有一件事沒有完成,那就是 $fuc1$fuc2$fuc3 這些閉包的構建還是手動的,我們需要將這個過程改為自動的:

public class Test {  
    public static void main(String[] args) {  
        Coffee $coffee = new Espresso();  

        $fun = function($coffee) use ($fuc,$dressing) {
            $dressing->cost($coffee, $fuc); 
        }

        $fuc = function($fuc,$dressing) use ($fun) {
            return $fun;
        };

        $fuc0 = function($coffee) {
            return $coffee;
        };

        $fucn = array_reduce(
            [(new Mocha(),(new Mocha(),(new Whip()], $fuc, $fuc0
        );

        $coffee = $fucn($coffee);

        //3.6(2.5 + 0.5 + 0.5 + 0.1)  
        System.out.println(coffee.cost());  
    }  
}

 

laravel的閉包裝飾者——Pipeline

上一章我們說到了路由的註冊啟動與載入過程,這個過程由 bootstrap() 完成。當所有的路由載入完畢後,就要進行各種中介軟體的處理了:

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

public function shouldSkipMiddleware()
{
    return $this->bound('middleware.disable') &&
           $this->make('middleware.disable') === true;
}

laravel 的中介軟體處理由 Pipeline 來完成,它是一個閉包裝飾者模式,其中

  • request 是具體類,相當於我們上面的 caffee 類;
  • middleware 中介軟體是裝飾者類,相當於上面的 dressing 類;

我們先看看這個類內部的程式碼:

class Pipeline implements PipelineContract
{
    public function __construct(Container $container = null)
    {
      $this->container = $container;
    }

    public function send($passable)
    {
        $this->passable = $passable;

        return $this;
    }

    public function through($pipes)
    {
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();

        return $this;
    }

    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }

    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            return $destination($passable);
        };
    }

    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                if ($pipe instanceof Closure) {
                    return $pipe($passable, $stack);
                } elseif (! is_object($pipe)) {
                    list($name, $parameters) = $this->parsePipeString($pipe);

                    $pipe = $this->getContainer()->make($name);

                    $parameters = array_merge([$passable, $stack], $parameters);
                } else {
                    $parameters = [$passable, $stack];
                }

                return $pipe->{$this->method}(...$parameters);
            };
        };
    }
}

pipeline 的構造和我們上面所講的閉包裝飾者相同,我們著重來看 carry() 函式的程式碼:

function ($stack, $pipe) {
    ...
}

最外層的閉包相當於上個章節的 $fuc,

function ($passable) use ($stack, $pipe) {
    ...
}

裡面的這一層比閉包型黨與上個章節的 $fun

prepareDestination 這個函式相當於上面的 $fuc0,

            if ($pipe instanceof Closure) {
                return $pipe($passable, $stack);
            } elseif (! is_object($pipe)) {
                list($name, $parameters) = $this->parsePipeString($pipe);

                $pipe = $this->getContainer()->make($name);

                $parameters = array_merge([$passable, $stack], $parameters);
            } else {
                $parameters = [$passable, $stack];
            }

            return $pipe->{$this->method}(...$parameters);

這一部分相當於上個章節的 $dressing->cost($coffee, $fuc);,這部分主要解析中介軟體 handle() 函式的引數:

public function via($method)
{
    $this->method = $method;

    return $this;
}

protected function parsePipeString($pipe)
{
    list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);

    if (is_string($parameters)) {
        $parameters = explode(',', $parameters);
    }

    return [$name, $parameters];
}

這樣,laravel 就實現了中介軟體對 request 的層層處理。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章