1.2 - Laravel 5.6 - Extend 擴充套件機制

HarveyNorman發表於2020-03-04

擴充套件機制也挺重要。先看下文件的解釋

extend 方法可以修改解析的服務。例如,當一個服務被解析後,你可以新增額外的程式碼去修飾或配置這個服務。 extend 方法接受一個閉包,閉包的唯一引數和返回值都是一個服務:


$this->app->extend(Service::class, function($service) {
    return new DecoratedService($service);
});

不明白?我也不明白。我用我的話講一下。

extend主要的作用是在解析後,使用一個閉包函式產生的值(通常為當前實體類的子類)替換對應父實體類,從而對其產生擴充套件影響。這個閉包的引數和返回值都必須是物件。比如:


app()->extend('service', function ($service, $app) {
    return new DecoratedService($service);
});

這裡DecoratedService 是 service 的子類。

更簡單來說,當我們從Container容器中取出一個例項後,用這個類的子類例項替換掉當前這個父類例項,達到擴充套件的作用。

當然這是一種用法。還有一些細節,我們去看下原始碼。

我們看下原始碼:

1.先獲取Container中這個id($abstract)的別名

2.檢視容器已有的例項陣列instance裡面有沒有對應的例項,如果有直接執行我們extend的這個閉包方法的返回值,存入這個陣列中。就是替換了原來的例項。

2.2.然後使用了rebound(),目的是看看有沒有附帶的回撥函式,觸發它,這個我們在回撥函式中會提。

這說明extend的時候會觸發回撥函式。

3.如果instance中沒有找到對應的例項,就把這個閉包函式存入extenders陣列,做個記錄,以後用。

並且如果這個id已經被resolved過了,還要觸發rebound函式,觸發一些對應的回撥函式。


public function extend($abstract, Closure $closure)
{
    $abstract = $this->getAlias($abstract);
    if (isset($this->instances[$abstract])) {
        $this->instances[$abstract] = $closure($this->instances[$abstract], $this);
        $this->rebound($abstract);
    } else {
        $this->extenders[$abstract][] = $closure;
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
}

現在就很清楚了。

總結

extend的機制就是如果instance中存在例項,就用extend中的閉包執行結果替換掉。如果instances例項列表中不存在就存起來以後備用。

某個適用場景可以是:用子類例項替換父類例項達到擴充套件的作用。

但不管怎麼樣,都會觸發當前存在的回撥函式一次。下一章講一下回撥函式。

實測例項

例項1:替換依賴的例項物件

本來的Boss例項的依賴Money是一個Cheque物件,最後解析的時候,被替換成了Dollar物件。


public function testClosure(){
    app()->bind('boss', Boss::class);

    $this->app->when(Boss::class)
    ->needs(Money::class)
    ->give(Cheque::class);
    app()->extend(Money::class, function() {
    return new Dollar();
    });
    $boss= app()->make('boss');
    $output = $boss->getA();
    $this->assertEquals($output, 1);
}

//

interface Money
{
    public function getAmount();
}

class Dollar implements Money
{
    public function getAmount()
    {
        return 1;
    }
}

class Cheque implements Money
{
    public function getAmount()
    {
        return 100000;
    }
}

class Worker
{
    private $money;
    public function __construct(Money $money)
    {
        $this->money = $money; // prints '1'
    }

    public function getA(){
        return $this->money->getAmount();
    }
}

class Boss
{
    private $money;
    public function __construct(Money $money)
    {
        $this->money = $money; // prints '100000'
    }
    public function getA(){
        return $this->money->getAmount();
    }
}

例項2:子類替換父類,向下擴充

public function testClosure()
{
    app()->extend(Money::class, function() {
    return new Dollar();
    });
    $money= app()->make(Money::class);
    $output = $money->getAmount();
    $this->assertEquals($output, 1);
}

class Money
{
    public function getAmount(){
        return 100;
    }
}

class Dollar extends Money
{
    public function getAmount()
    {
        return 1;
    }
}

class Cheque extends Money
{
    public function getAmount()
    {
        return 100000;
    }
}

例項3:向其他地方擴充, extend第二個引數閉包的第一個引數就是 extend第一個引數的例項,在這裡就是我們事先繫結的Money::class例項。

public function testClosure()
{
    app()->bind('money', Money::class);
    app()->extend('money', function($money) {
    return new Currenty($money);
    });
    $boss= app()->make('money');
    $output = $boss->getAmount();
    $this->assertEquals($output, "harveynorman");
}

class Currenty{
    protected $money;
    public function __construct(Money $money)
    {
        $this->money = $money;
    }
    public function getAmount(){
        return "harveynorman";
    }
}

class Money
{
    public function getAmount(){
        return 100;
    }
}

class Dollar extends Money
{
    public function getAmount()
    {
        return 1;
    }
}

class Cheque extends Money
{
    public function getAmount()
    {
        return 100000;
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章