Laravel驅動管理類Manager的分析和使用

Laravel驅動管理類Manager的分析和使用

第一部分 概念說明php

第二部分 Illuminate\Support\Manager源碼laravel

第三部分 Manager類的使用閉包

第一部分:概念解釋

結合實際解釋一下,啥是驅動:當我點了份外賣,那麼外賣小哥不管如何都要講外賣送到個人手中,我不會關心小哥走的是絲綢之路,仍是強者之路,更不會關心他是騎着飛機、坦克仍是大炮送來的。我只要個人外賣到個人手中。app

概括一下,我點外賣要就要獲得外賣,這就是契約,這就是接口規定的功能。框架

小哥走什麼路線,什麼交通工具是他本身的實現,也就是各類驅動。elasticsearch

是否是和laravel的契約和服務提供者概念很類似呢?ide

只不過今天要講解的Manager更增強調管理各個驅動,提早將全部的驅動所有註冊好,在使用的時候直接解析或者切換。工具

說到這道友們應該理解了,Manager能作的事情Container和Provider可以作的更好。測試

那麼Manager和Container、Provider的區別在哪呢?這裏我只說明個人理解,Container和Provider更加的偏向框架層級,雖然也能夠非侵入式的擴展和修改,可是相對Manager稍加麻煩,我將Manager看作一個小型的Container,裏面包含了我要實現某個功能的各類驅動(實現),Manager更加的偏向業務邏輯層。ui

當要頻繁切換一個功能實現的時候(他更像一個頻繁更換內容,可是說明書不換的組件,俗話說的換湯不換藥),我可能會選擇Manager(好比發送短信,可使用阿里大於,京東萬象,飛鴿等等),由於他更加輕量。當要實現一個系統級的服務的時候,我會選擇Container和Provider,好比上一篇中的日誌服務。

第二部分:源碼說明
# 直接上代碼 挺簡單的一個類,基本能夠見名知意。未展現屬性
<?php

namespace Illuminate\Support;

use Closure;
use Illuminate\Contracts\Container\Container;
use InvalidArgumentException;

// 值得注意的是 Manager是一個抽象類,必定要實現了其中的抽象方法getDefaultDriver才能實例化
// 咱們觀察構造方法中的參數,你會不會想到在Provider中掛載Manager是一個好方法呢?
public function __construct(Container $container)
{
    $this->app = $container;
    $this->container = $container;
    $this->config = $container->make('config');
}

// 此類是一個抽象類 這個方法用來返回默認的驅動名
abstract public function getDefaultDriver();

public function driver($driver = null)
{
    $driver = $driver ?: $this->getDefaultDriver();

    if (is_null($driver)) {
        throw new InvalidArgumentException(sprintf(
            // 此處的static顯然是實際調用該方法的類
            'Unable to resolve NULL driver for [%s].', static::class
        ));
    }
	
    // 有點相似單例的寫法
    // 若是要解析的驅動已經解析過 那麼直接返回
    // 若是沒有解析過 那麼解析 並掛載到類中
    if (! isset($this->drivers[$driver])) {
        $this->drivers[$driver] = $this->createDriver($driver);
    }

    return $this->drivers[$driver];
}

// 建立指定驅動
// 要注意此類中傳遞的$driver就是指定驅動的名字
protected function createDriver($driver)
{
    // First, we will determine if a custom driver creator exists for the given driver and
    // if it does not we will check for a creator method for the driver. Custom creator
    // callbacks allow developers to build their own "drivers" easily using Closures.
    # 官方註釋已經很是清晰了
    # 若是要解析的驅動,是由咱們手動經過鍵值對註冊進來的 那麼就調用對應的閉包
    # 不然觸發魔術方法__call
    # 顯然Manager本類中並不存在額外的方法,因此魔術方法調用的方法,也要咱們在子類中實現
    # 以上就是兩種從Manager中返回驅動的方式了
    if (isset($this->customCreators[$driver])) {
        return $this->callCustomCreator($driver);
    } else {
        $method = 'create'.Str::studly($driver).'Driver';

        if (method_exists($this, $method)) {
            return $this->$method();
        }
    }

    throw new InvalidArgumentException("Driver [$driver] not supported.");
}

// 上面說的經過此方法調用咱們註冊進來的閉包 從而返回驅動
protected function callCustomCreator($driver)
{
    return $this->customCreators[$driver]($this->container);
}

// 這個就是註冊閉包進來
// 你固然能夠在業務邏輯中、甚至是指定的中間件中擴展你的Manager類
// 但我更喜歡在ServiceProvider的boot方法中進行擴展
public function extend($driver, Closure $callback)
{
    $this->customCreators[$driver] = $callback;

    return $this;
}

public function getDrivers()
{
    return $this->drivers;
}

// __call魔術方法 從Manager中解析驅動的第二種方式
public function __call($method, $parameters)
{
    return $this->driver()->$method(...$parameters);
}
第三部分:使用(依然經過日誌這個不恰當例子進行展現)
1 建立契約
<?php

namespace App\Contracts;

interface ManageLog
{
    public function logCertains($level, $foo);
}

2 建立日誌組件,使用管理器管理
<?php

namespace App\Components\Log;

use Illuminate\Support\Manager;

class LogManager extends Manager
{   
    // 這是個類,而且你能夠經過$this->app拿到容器實例,也就意味着你能夠作不少事情
    public function getDefaultDriver()
    {   
        // 你也能夠將返回的字符串寫在配置中 等等
        return 'elasticsearch';
    }

    // 展現魔術方法解析驅動
    // $logManager->driver('elasticsearch')時觸發
    public function createElasticsearchDriver()
    {   
        // 上一篇有簡單示例
        return '你的es日誌驅動';
    }
    
    // 查看manager中的驅動
    public function getCustomCreators()
    {
        return $this->customCreators;
    }
}

3 建立不一樣的日誌驅動
<?php

namespace App\Drivers\Log;

use App\Contracts\ManageLog;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\MemoryPeakUsageProcessor;
use Monolog\Processor\MemoryUsageProcessor;

class RotateDriver implements ManageLog
{
    protected $logger;

    public function __construct()
    {
        $logger = new Logger('manager');
        $rotatingHandler = new RotatingFileHandler(storage_path('logs/test/manager.log'), 7);
        $logger->pushHandler($rotatingHandler);
        //  隨便加點什麼吧
        $procesccor1 = new MemoryPeakUsageProcessor();
        $procesccor2 = new MemoryUsageProcessor();
        $logger->pushProcessor($procesccor1);
        $logger->pushProcessor($procesccor2);
        $this->logger = $logger;
    }

    public function logCertains($level, $foo)
    {
        $this->logger->{$level}($foo);
    }
}

<?php

namespace App\Drivers\Log;

use App\Contracts\ManageLog;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class StreamDriver implements ManageLog
{
    protected $logger;

    public function __construct()
    {
        $logger = new Logger('manager');
        $streamHandler = new StreamHandler(storage_path('logs/test/manager.log'));
        $logger->pushHandler($streamHandler);
        $this->logger = $logger;
    }

    public function logCertains($level, $foo)
    {
        $this->logger->{$level}($foo);
    }
}

4 建立服務提供者 
# 此處說明一下 爲何使用singleton進行綁定,由於我在boot方法中兩次解析manager爲了將其擴展,保證每次解析都是同一個manager,
# 也就修改了綁定到容器的manager,一旦在register方法中使用bind綁定的話,每次從容器中解析出來的都會是一個全新的manager,
# 也就是說咱們的boot方法白白浪費了,也就天然不可以進行任何的操做了。其實laravel爲了解決這個問題還有其餘方法,
# 請各位仔細查看服務提供者部分的文檔,我這裏選擇在boot方法中對manager進行擴展,其實你能夠在任何你喜歡的地方擴展。
    
php artisan make:provider LogManagerServiceProvider
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Components\Log\LogManager;
use App\Drivers\Log\StreamDriver;
use App\Drivers\Log\RotateDriver;

class LogManagerServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {   
        $this->app->singleton('logManager', function ($app) {
            return new LogManager($app);
        });
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        // 擴展咱們的logmanager
        $this->app['logManager']->extend('stream', function () {
            return new StreamDriver();
        });
        $this->app['logManager']->extend('rotate', function () {
            return new RotateDriver();
        });
        // dd($this->app['logManager']->getCustomCreators());
    }
}

5 註冊服務
config/app.php
...   
App\Providers\RouteServiceProvider::class,
// 註冊自定義日誌服務
App\Providers\LogServiceProvider::class,
// 註冊日誌管理服務
App\Providers\LogManagerServiceProvider::class,

6 使用測試 
Route::get('logmanager', function () { 
    resolve('logManager')->driver('stream')->logCertains('emergency', 'something emergency');
    resolve('logManager')->driver('rotate')->logCertains('debug', 'debug something');
});

以上代碼比較簡單,各位領會精神就好,你們能夠結合前面說過的facade,仿照laravel原生Log服務實現各功能一致的log manager。

今天沒有下集預告,發現錯誤歡迎指正,感謝!!!

相關文章
相關標籤/搜索