内容简介:Laravel Container 是什麼呢 ? 我們先來理解 Container 容器 是什麼。容器抽象一點概念是指用來裝東西的載體,向菜籃也算個容器,而在 Laravel 中所代表的意思就是指 :裡面裝了一堆可以用的服務載體,就叫 Container。
Container 是什麼 ?
Laravel Container 是什麼呢 ? 我們先來理解 Container 容器 是什麼。
容器抽象一點概念是指用來裝東西的載體,向菜籃也算個容器,而在 Laravel 中所代表的意思就是指 :
裡面裝了一堆可以用的服務載體,就叫 Container。
像我們每當要執行 Laravel 時,都會先執行下面這段程式碼,其中 $app 就是我們的 Container,然後接下來會使用 Container 來實體化一些物件,例如 $kernel。
<?php public/index.php $app = require_once __DIR__.'/../bootstrap/app.php'; /* |-------------------------------------------------------------------------- | Run The Application |-------------------------------------------------------------------------- | | Once we have the application, we can handle the incoming request | through the kernel, and send the associated response back to | the client's browser allowing them to enjoy the creative | and wonderful application we have prepared for them. | */ $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); $response->send(); $kernel->terminate($request, $response);
為什麼要使用 Container ?
上面我們理解 Container 是做什麼用以後,接下來我們要來想想一件事情。
為什麼 Laravel 要使用 Container 呢,為什麼上面的要實體化 $knernel 時,不使用 new Knernel() 這種實體化的方式呢 ?
因為它想解決依賴與耦合。
這就是 Conainter 想解決的事情。
(高)依賴與耦合
高依賴與耦合 : 程式碼中綁死了某個模組,如下面程式碼綁死了 Log Service。
假設有一段程式碼如下 :
<?php class Log { public function send(log): void { $awsLogService = new AWSLogService(); $awsLogService->send(log); } } class AWSLogService { public function send(log): void { .... } }
但假設今天我們要將 Log 改傳到 GCP ( Google 雲端 ),那我們程式碼要修改成如下 :
<?php class Log { public function send(log): void { //$awsLogService = new AWSLogService(); //$awsLogService->send(log); $gcpLogService = new GCPLogService(); $gcpLogService->send(log); } } class GCPLogService { public function send(log): void { .... } } // 使用 $log = new Log(); $log->send('log.....');
從上面程式碼中,我們可以注意到我們沒當要換個服務時,都需要修改程式碼,並且這裡還有一個缺點,你要如何做單元測試 ? 程式碼裡面完全的綁死了 AWSLogService 或是 GCPLogService,沒有地方可以給我們進行替換,沒辦法替換就代表我們在做測試時,只能真的將資料丟到 AWS 或 GCP。
(低) 依賴與耦合
然後由於有上面說的缺點,因此會將程式碼改成如下。基本上就是將 LogService 改成由使用這個物件時來決定是用選擇 AWS 還是 GCP,並且這兩個 service 都實作同一個 ILogService 的 interface。
<?php class Log { private ILogService $logService; public function __construct(ILogService $logService) { $this->logService = $logService; } public function send(log): void { $this->logService->send(log); } } class GCPLogService implements ILogService { public function send(log): void { .... } } class AWSLogService implements ILogService { public function send(log): void { .... } } interface ILogService { public function send(); } // 使用 $log = new Log(new AWSLogServcie()); $log->send('log......');
好接下來在拉回主題。
為什麼要使用 Laravel Container ?
上面我們的範例程式碼最後要執行時,會如下 :
<?php $log = new Log(new AWSLogServcie()); $log->send('log......');
這樣事實上沒什麼問題。
但是如果這一段程式碼有很多地方使用怎麼辦 ? 有沒有可能系統中統一都要使用 AWS 的,但是其中一個地方忘了改,而不小心使用到 GCP ? 嗯這是有可能發生的。
還有另一個問題,這一段程式碼本身就依賴了 Log
這個類別,這樣事實上還是沒有解決依賴的問題。
因此 Laravel 建立了 Container,並且會在開啟服務時,先行註冊好,例如下面偽代碼。只要在這個 conatiner 內部的 class 都會根據它註冊好的東西來進行處理。
<?php $containter = require('Container'); // 它會在這一段先將 ILogService 綁定好,如果 construct 中有使用到它的,將會將它實體化為 // AWSLogServcie。 $containter->bind(ILogService, AWSLogServcie::class); // 實體化 Log 類別。 $log = $container->make(Log::class); $log->send('log....');
那有兩個類別,它們內部有使用相同抽像類別,但這時它們實際上要使用不同的類別要怎麼處理呢 ?
Laravel 官網有給個範例如下,Photo 與 Video 都有使用到 Filesystem 這個抽象類別,但它們實際上要使用不一樣的類別,則可以使用如下的方法來進行指定。
<?php $this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('local'); }); $this->app->when(VideoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('s3'); });
Laravel 如何建立 Container ?
這裡我們就要開始來研究一下 Laravel Container 的原始碼。
首先最一開始是這裡,它會實體化一個 $app conatiner。
<?php $app = new Illuminate\Foundation\Application( $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) );
接下來我們來看一下 Illuminate\Foundation\Application 的程式碼。這裡可以知道 Application 繼承了 Container 這個類別。
<?php class Application extends Container implements ApplicationContract, HttpKernelInterface { public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); } }
然後 Container 類別中,有兩個方法是重點那就是 bind
與 make
。
bind
建立抽象與實體的綁定表
bind 使用方式
基本上分為以下四種 :
<?php // 1. 類別綁定 clouse App::bind('UserRepository', function() { return new AWSUserRepository; }); // 2. 抽像類別綁定實際類別 App::bind('UserRepositoryInterface', 'DbUserRepository'); // 3. 實際類別綁定 APP::bind('UserRepository') // 4. singleton 綁定 App::singleton('UserRepository', function() { return new AWSUserRepository; });
原始碼解析
<?php /** * Register a binding with the container. * * @param string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void */ public function bind($abstract, $concrete = null, $shared = false) { $this->dropStaleInstances($abstract); // 例如這種 APP::bind('UserRepository') 的註冊,就會執行這一段。 if (is_null($concrete)) { $concrete = $abstract; } // 如果是上面那種情況或是沒有 Closure,就直接產生一個 Closure。 if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } // 綁定,就是用一個 HashTable 來建立綁定對應。 $this->bindings[$abstract] = compact('concrete', 'shared'); // 如果此類別已被 resolve 則進行 rebound。 if ($this->resolved($abstract)) { $this->rebound($abstract); } } /** * Get the Closure to be used when building a type. * * @param string $abstract * @param string $concrete * @return \Closure */ protected function getClosure($abstract, $concrete) { return function ($container, $parameters = []) use ($abstract, $concrete) { if ($abstract == $concrete) { return $container->build($concrete); } return $container->make($concrete, $parameters); }; }
make
產生實際的實體物件
使用方法
<?php $app->make('UserRepository');
原始碼解析
<?php /** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed */ public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); } /** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed */ protected function resolve($abstract, $parameters = []) { $abstract = $this->getAlias($abstract); $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); // 如果此抽象類別已經實體化了,且 construct 沒使用其它外部注入,則回傳此物件。 if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $this->with[] = $parameters; // 這個地方有兩種情況 // 1. 從抽象類別的建構式取出有使用的類別,並回傳。 // 2. 如果沒有,則從 bindings 中找出對應的實體類別。 $concrete = $this->getConcrete($abstract); // isBuildable => true // 1. $concrete 與 $abstract 為相同 (也就直接使用類別來綁定) // // isBuildable => false // 1. 直接使用介面。 // 2. $abstract 本身內部還有依賴的外部套件。 if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } // 不太懂 foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } // 註冊的類別如果被指定為 singleton 就要 cache 它。 if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); // 記錄那個類別已經被 resolve $this->resolved[$abstract] = true。; array_pop($this->with); return $object; } /** * Determine if the given concrete is buildable. * * @param mixed $concrete * @param string $abstract * @return bool */ protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; }
參考資料
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 理解原型其实是理解原型链
- 要理解深度学习,必须突破常规视角去理解优化
- 深入理解java虚拟机(1) -- 理解HotSpot内存区域
- 荐 【C++100问】深入理解理解顶层const和底层const
- 深入理解 HTTPS
- 深入理解 HTTPS
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。