# 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');
```