第一部分 概念說明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。
今天沒有下集預告,發現錯誤歡迎指正,感謝!!!