# Laravel Architecture Concepts 心得 ###### tags: `Laravel`,`PHP` - Service Container - Service Providers - Facades - Helper Function ## Service Container ### 依賴注入(Dependency Injection) ```php class MyClass { private $dependency; public function __construct(AnotherClass $dependency) { $this->dependency = $dependency; } } ``` #### 一般實例化: ```php $instance = new MyClass(new AnotherClass()); ``` 假設依賴很多類別... ```php $instance = new MyClass( new AClass(), new BClass(), new CClass(), new DClass(), new EClass(), ... ); ``` #### Laravel [illuminate/container](https://github.com/illuminate/container) ```php use Illuminate\Container\Container; ... $container = Container::getInstance(); // 使用 make 方法来代替 new class,container 會自動實例化依賴對象 $instance = $container->make(MyClass::class); ``` ##### Bind 如果有 interface... ```php class MyClass implements MyInterface { private $dependency; public function __construct(AnotherInterface $dependency) { $this->dependency = $dependency; } } ``` ```php $container->bind(MyInterface::class, MyClass::class); $container->bind(AnotherInterface::class, AnotherClass::class); ``` bind 很強大,可以 bind 抽象類別和繼承類別,還可以自定義綁定... ```php $container->bind(MyClass::class, function (Container $container) { $yourClass = $container->make(YourClass::class); return new MyClass($yourClass); }); ``` 也可以 bind 字串... ```php $container->bind('burgess', MyClass::class); $instance = $container->make('burgess'); ``` ##### Singleton 使用 `bind` 方法,每次 `make` 都會實例化新的類別,若要單例模式使用 `singleton` ```php $container->singleton(MyClass::class); ``` ```php $instance1 = $container->make(MyClass::class); $instance2 = $container->make(MyClass::class); // return the same instance ``` ##### Primitives 上下文綁定 ```php $container ->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('local'); }); $container ->when(VideoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('s3'); }); ``` ##### Tag ```php $this->app->bind(SpeedReport::class); $this->app->bind(MemoryReport::class); $this->app->tag([SpeedReport::class, MemoryReport::class], 'reports'); // 可以透過 tagged method 取回實例的 array $container->tagged('reports') ``` ##### Extend 擴展一個類,當某個 service 被調用時,會執行額外的 code 去裝飾這 service ```php $container->extend(Service::class, function ($service, $container) { return new DecoratedService($service); }); ``` ```php interface ServiceInterface { public function get(); } class Service implements ServiceInterface { public function get() { ... } } class DecoratedService implements ServiceInterface { protected $service; public function __construct(Service $service) { $this->service = $service; } public function get() { ... } } ``` ##### Resolving 監聽 container event... ```php // 當 container 的 MyClass 被調用後會呼叫 MyClass 的 someMethod $container->resolving(MyClass::class, function (MyClass $myClass) { $myClass->someMethod(); }); ``` #### 備註 - container 是使用 [ReflectionClass](https://www.php.net/manual/en/class.reflectionclass.php) 去[取得依賴對象並且實例化](https://github.com/illuminate/container/blob/0a4b06bfa5cddf9dd5a0e566873b3b3adac6eccc/Container.php#L793),達到控制反轉的效果。 - 官方文件使用 [`$this->app`](https://laravel.com/docs/6.x/container) 代替 `$container`,其實 `\Illuminate\Contracts\Foundation\Application` 繼承了 `\Illuminate\Contracts\Container\Container` - Laravel 的 controllers, event listeners, middleware 的 `__construct` 以及 queued jobs 的 `handle` method 會 Automatic Injection ## Service Providers Service providers 是 Laravel application register 的地方,包含 service container bindings, event listeners, middleware, routes 等等,在 `config/app.php` 的 `providers` array 可以看到 Service Providers list - 常用的 services 可以在 `AppServiceProvider` 下去註冊。 - 特別的功能模組可考慮自建自己的 ServiceProvider,記得在`config/app.php` 的 `providers` array 加入新增的 provider。 ### The Register Method ```php namespace App\Providers; use App\Services\HelloService; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { $this->app->bind(HelloService::class); $this->app->singleton(MyService::class); } } ``` ```php namespace App\Providers; use App\Services\HelloService; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * All of the container bindings that should be registered. * * @var array */ public $bindings = [ ServerProvider::class => DigitalOceanServerProvider::class, ]; /** * All of the container singletons that should be registered. * * @var array */ public $singletons = [ DowntimeNotifier::class => PingdomDowntimeNotifier::class, ServerToolsProvider::class => ServerToolsProvider::class, ]; } ``` ### The Boot Method Register view composer,在 Laravel View 內綁定參數資料,可以在 template 共用 ### Deferred Providers 可以等到 service container 真的要使用的時候再去註冊 binding,因為不會每一次 request 都去載入這個 Providers ,所以會提升 performance。 設定方式: - implements `DeferrableProvider` - 定義 provides method,要返回 provider 註冊的 service container ```php class MyServiceProvider extends ServiceProvider implements DeferrableProvider { /** * Register any application services. * * @return void */ public function register() { $this->app->singleton(MyService::class); } /** * Get the services provided by the provider. * * @return array */ public function provides() { return [MyService::class]; } } ``` ## Facades 為 service container 提供 "static" interface, 就像 proxy 一樣,透過靜態呼叫方法去對應綁定的 service container,Laravel facades 定義在 `Illuminate\Support\Facades` 的 namespace 下。 EX: ```php Route::get('/cache', function () { return Cache::get('key'); }); Config::get('database.default'); ``` - 不用去記住長串需要 inject 的 class name - 要避免 facades 過肥過大或是在一個 class 使用太多 facades - facade class scope 儘量簡單 - 一般來說不容易去 mock or stub 靜態類別的方法,但 facades 有提供 stub 方法 ### Use Facades ```php class MyService extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return MyService::class; } } ``` ```php class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { $this->app->bind(MyService::class); } } ``` ```php MyService::someMethod() ``` ## Helpers Function Global "helper" PHP functions ```php // helper function $value = cache('key'); // facade use Illuminate\Support\Facades\Cache; ... $value = Cache::get('key'); // service container use Illuminate\Foundation\Application; ... // cache 是 Deferred Provider,要用 make 而非 get $cache = Application::getInstance()->make('cache'); $value = $cache->get('key'); ```