在laravel框架中,根據laravel開發文檔,文檔介紹了框架中的4中核心,一個是請求周期,一個是服務提容器,一個是服務提供者,還有則是facade,另外還有契約,這幾天跟隨laravel社區大佬的腳本,讀那位大佬所實現的容器代碼,收獲頗豐,今天主要總結的就是有關服務容器所實現的東西。
根據開發文檔,為什么需要服務容器,開到開發文檔中的示例,在一個控制器被實例化的時候,控制器可能依賴於某個實例,比如控制器對應的數據模型實例,但是控制器的方法不是我們調用的,是框架自己在請求的過程中自己調用的,由於控制器依賴於某個對象,所以最好的實例化時機就是在類被初始化即調用構造方法的時候,我們可以在夠造方法的參數中添加一個(帶類型提示)參數,並在控制器中設置值,通過這種方式實現依賴。構造方法中傳入的參數必須是已經注冊過的類,否則會報錯,注冊類的文件即為服務提供者,而服務容器則用來存儲待實例化的類或已經實例化的實例。
引用官方的例子,以下是控制器實例依賴於另一個實例:
<?php namespace App\Http\Controllers; use App\User; use App\Repositories\UserRepository; use App\Http\Controllers\Controller; class UserController extends Controller { /** * 用戶存儲庫的實現。 * * @var UserRepository */ protected $users; /** * 創建新的控制器實例。 * * @param UserRepository $users * @return void */ public function __construct(UserRepository $users) { $this->users = $users; } /** *顯示指定用戶的 profile。 * * @param int $id * @return Response */ public function show($id) { $user = $this->users->find($id); return view('user.profile', ['user' => $user]); } }
創建一個服務提供者,可以使用php artisan 命令:
php artisan make:provider RiakServiceProvider
生成的文件內容如下:
<?php namespace App\Providers; use Riak\Connection; use Illuminate\Support\ServiceProvider; class RiakServiceProvider extends ServiceProvider { /** * 在服務容器里注冊 * * @return void */ public function register() { $this->app->singleton(Connection::class, function ($app) { return new Connection(config('riak')); }); } }
在這個文件中,我們通過在register中編寫后面需要使用的實例,框架則會在實例化控制器的時候同時將這個服務提供者生成的對象傳入,在學java過程中,spring篇也提到了這一項:控制反轉。(稍微提一下)
重點來了,一下是以為大佬寫的簡化版的服務容器:
<?php /** * Created by PhpStorm. * User: @DennisRitche * Date: 2019/11/25 0025 * Time: 9:00 */ class Container { /** * @var $instance array * **/ private $instance = []; /** * @var $binds array * **/ private $binds = []; public function __construct()//構造方法 { $this->instance[Container::class] = $this; } /** * add new instance * @param $name string * @param $value mixed */ public function addInstance($name, $value) { $this->instance[$name] = $value; } /** * @param $name string * @param $value mixed * @param bool $shared */ public function newBind($name, $value, $shared = false) { if ($value instanceof Closure) { $this->binds[$name] = ['concrete' => $value, "shared" => $shared]; } else { if (!is_string($value) || !class_exists($value, true)) { throw new InvalidArgumentException("value must be callback or class name"); } } $this->binds[$name] = ['concrete' => $value, 'shared' => $shared]; } /** * @param $name string * @param array $real_args * @return mixed */ public function make($name, $real_args = []) { //check firstly if (!isset($this->instance[$name]) && !isset($this->binds[$name])) { if (!class_exists($name, true)) { throw new InvalidArgumentException("class not exist"); } } if (isset($this->instance[$name])) { return $this->instance[$name]; } try { if (isset($this->binds[$name])) { $concrete = $this->binds[$name]['concrete']; if (is_callable($concrete)) { print_r($real_args); $instance = $this->parseCallable($concrete, $real_args); } else { $instance = $this->parseClass($concrete, $real_args); } } else { $instance = $this->parseClass($name, $real_args); } if (isset($this->binds[$name]) && $this->binds[$name]['shared']) { $this->instance[$name] = $instance; } return $instance; } catch (Exception $exception) { echo $exception->getMessage(); } } /** * @param callable $callback * @param $real_args * @return mixed * @throws ReflectionException */ private function parseCallable(callable $callback, $real_args) { $refl_function = new ReflectionFunction($callback); $parameters = $refl_function->getParameters(); $parsed_args = []; if (count($parameters) > 0) { $this->resolveDependencies($parameters, $parsed_args, $real_args); } return $refl_function->invokeArgs($parsed_args); } /** * @param $class_name * @param array $real_args * @return object * @throws ReflectionException */ private function parseClass($class_name, $real_args = []) { $refl_class = new ReflectionClass($class_name); if (!$refl_class->isInstantiable()) { throw new RuntimeException("{$class_name} can not be initialized"); } if (!($constructor = $refl_class->getConstructor())) { //no default constructor,so create it directly return new $class_name; } else { $args = []; return $refl_class->newInstanceArgs($this->resolveDependencies($constructor->getParameters(), $args, $real_args)); } } /** * @param $parameters * @param $parsed_args * @param $real_args * @return array|mixed * @throws ReflectionException */ private function resolveDependencies($parameters, &$parsed_args, $real_args = []) { /** * @var $parameter ReflectionParameter * **/ foreach ($parameters as $parameter) { if ($parameter->getClass() != null) { if (!class_exists($parameter->getClass()->getName(), true)) { throw new RuntimeException($parameter->getClass()->getName() . " not exist"); } else { $parsed_args[] = $this->make($parameter->getClass()->getName()); } } else { if (!$parameter->isDefaultValueAvailable()) { if (!isset($real_args[$parameter->getName()])) { throw new RuntimeException($parameter->getName() . " has no value"); } else { $parsed_args[] = $real_args[$parameter->getName()]; } } else { if (isset($real_args[$parameter->getName()])) { $parsed_args[] = $real_args[$parameter->getName()]; } else { $parsed_args[] = $parameter->getDefaultValue(); } } } } return $parsed_args; } }
這個類中定義的一系列方法,作用是什么呢,讓我們看看大佬發的一個例子:
<?php /** * Created by PhpStorm. * User: Administrator * Date: 2019/11/25 0025 * Time: 11:32 */ require_once "./Container.php"; class Family { private $level; private $children_num; /** * @var $container Container * **/ private $container; /** * Family constructor. * @param $level int * @param $children_num string * @param $container */ public function __construct($level, $children_num, $container) { $this->level = $level; $this->children_num = $children_num; $this->container = $container; } } class Student { /** * @var $name string * **/ private $name; /** * @var $family Family * **/ private $family; /** * Student constructor. * @param $name * @param Family $family */ public function __construct($name, Family $family) { $this->name = $name; $this->family = $family; } } /*$family=new Family();*/ $container = new Container(); $container->newBind(Family::class, function (Container $container) { $family = new Family(10, 3, $container); return $family; }); $inst = $container->make(Student::class, ["name" => "obama"]); print_r($inst);
首先創建了一個容器對象,創建時,會把當前對象存儲至$instance數組中key是類名,value是對應的實例,然后調用了newBind方法,第一個是Family類名,第二個是個閉包函數,進入newBind方法中,經過一系列操作,它將該閉包存入了$bind數組, $bind數組的內容為:
$bind = [ 'Family'=>[ 'concrete'=>閉包對象, ‘share’=>false, ] ]
然后在dubug.php中調用make方法創建Student的實例,進入make方法,一系列檢測之后發現bind和instance屬性都沒有有關該類的東西,直接調用了parseClass()方法,這個方法通過反射機制獲取構造方法的參數名數組,並調用resolveDependencies()方法傳入參數名數組,和傳入的實際參數,之后檢測發現少了Family類型參數,於是再一次進行make,此次make在bind屬性中找到了閉包方法,公共閉包方法返回了Family的一個實例,期間還去找了Container實例傳入閉包方法中,最終成功實例化Student。
之前一直在納悶,newbind方法有什么用,之后發現是給bind傳入待實例化的東西,這個東西可以是閉包可以是一個類的名稱,其實感覺還可以優化一下如果第二個參數是字符串,則存儲類名,第一個也是類名,第一個參數和第二個參數的關系可以是同一個類,也可以有繼承或者實現的關系這種模式與Laravel中的契約不謀而合。后面感覺很奇怪,為什么resolveDependencies()需要的第二個參數需要引用,之后發現在parseCallable這個方法中有用到,其實可以稍微改下parseCallable這個方法結構就不需要引用傳值了。
讀完這個容器代碼之后,感覺很奇妙,知道了這個類以及這個類中的方法的具體職責是什么,這個類究竟干了些什么,哪些場景可以用到。
