---
# System prepended metadata

title: Laravel Architecture Concepts 心得
tags: [PHP, Laravel]

---

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

```
