デザインパターン
デザインパターンは、いろんな文脈で使われるが、ここでのデザインパターンはGoFのデザインパターン。
その中で今回は、Builder Patternでの実装箇所を取り上げる。
Builder Patternは、デザインパターンの分類、生成・構成・振る舞いのうちの生成に分類される。
生成過程を抽象化したり、コンストラクタの引数が多いとか複雑なときに使うデザインパターン。
基本的な説明は以下のリンクに任せる
[デザインパターン]
https://www.techscore.com/tech/DesignPattern/index.html/
[Builderパターン | PHPデザインパターン]
https://www.ritolab.com/entry/125
Illuminate\Support\Managerクラス
Illuminate\Support\Managerクラスは、Laravelに用意されているBuilder Patternを簡単に利用するための抽象クラス。
インスタンス生成過程を隠蔽し、シンプルに再利用するために継承して使用することができる。
Session, Cache, Auth等で使用されていて、Authの認証方式やSessionのストレージをLaravelで初期状態で使用できるもの以外を使いたい場合は、自前ですべて作成するのではなく、Manageクラスに従って拡張すれば、Laravelの機能をできるだけ残したまま拡張できる。 また、configの変更等最小のステップでほぼコードを書くことなく、設定を変更してSessionを別プロジェクトでも再利用が簡単にできる。
なので機能拡張の際、Managerクラスを継承あるいは引用しているクラスをLaravelが使用している場合、これに乗っかって拡張するのが楽。 新規機能を作る時も、Managerクラスを使えば、Driverを使ったBuilderパターンを容易に使用できる。
最近はLaravelのManagerコンポーネント(AuthManager等)は、継承せずに実装されるように変更されている(処理自体は踏襲されている)。
現在継承して使用されているのはSessionManagerなのでSessionManagerを使用して説明する。
##SessionManager
Sessionで使用する機能(Illuminate\Session\Store)を再利用しつつ、使用者は保存先のストレージをconfigを使用して簡単に変更し、Session用のオブジェクト(Illuminate\Session\Store)を生成できるようにというのが、SessionManagerの趣旨
Sessionのストレージの選択肢は、ほぼ確定しているのでそのストレージごとに生成過程を見やすく管理できる。
SessionServiceProviderh
protected function registerSessionManager()
{
$this->app->singleton('session', function ($app) {
return new SessionManager($app);
});
}
SessionServiceProviderでSessionManagerがコンテナに登録されている
SessionManagerは、あくまで生成過程を抽象化してくれているもので、最終的にはIlluminate\Session\Storeのインスタンスが帰ってくる。
SessionManager
protected function callCustomCreator($driver) //たぶん独自にDriverを登録できるやつ
{
return $this->buildSession(parent::callCustomCreator($driver));
}
// 既存のDriverメソッドの抜粋
protected function createFileDriver()
{
return $this->createNativeDriver();
}
protected function createMemcachedDriver()
{
return $this->createCacheBased('memcached');
}
protected function createDynamodbDriver()
{
return $this->createCacheBased('dynamodb');
}
// ここで/config/session.phpのdriverをDriverとしてセットしている。
public function getDefaultDriver()
{
return $this->app['config']['session.driver'];
}
生成過程で必要になる要素を、Driverとして登録する。
ここではSessionを保存するためのStorageをDriverとして登録しておく。
既にFile、memcached、Redis、Dynamoとかいくつか用意されている。
callCustomCreatorで独自のDriverを登録できるはず(試したことない)
各Driverメソッド内で生成過程の差異を吸収する。
Sessionに別のストレージを使いたい場合この仕組みを利用するとLaravelの仕様に乗っかれて便利。
また、configでSessionを設定できるように、$this->app['config']を使用してStoreの生成を抽象化している。
Manager
protected function createDriver($driver)
{
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
} else {
$method = 'create'.Str::studly($driver).'Driver';
if (method_exists($this, $method)) {
return $this->$method();
}
}
throw new InvalidArgumentException("Driver [$driver] not supported.");
}
// これでdriverメソッドで生成したStoreのメソッドをSessionManagerから直接使用できる
// Session::get('hoge')て感じで(SessionはSessionManagerのファサード)
public function __call($method, $parameters)
{
return $this->driver()->$method(...$parameters);
}
createDriverにはDefaultDriverが渡されて、
$method = 'create'.Str::studly($driver).'Driver';
のところでDriverメソッドを確定している。
SessionManagerの効果
SessionManagerにより、SessionのストレージをDATABASEにしたい場合、下記を設定するだけで、SessionファサードでDatabaseストレージに対応したStoreインスタンスが取得できるようになる。
SESSION_DRIVER=database
php artisan session:table
php artisan migrate
Session::all();
Session::get('hoge');
SMS用のManagerクラスを作ってみる
Smsは外部サービスに処理を投げるので、Driverとして使用するSmsサービスを定義しておく。
今回は、NexmoとTwilioのDriverメソッドを定義する。このメソッドからSms送信用のDriverクラスを定義しておく。なのでSmsManagerが返すインスタンスは、Driverクラス。
App\Components\Sms\SmsManager
<?php
namespace App\Components\Sms;
use Illuminate\Support\Manager;
use Nexmo\Client as NexmoClient; // NexmoのPHPクライアント
use Twilio\Rest\Client as TwilioClient; // TwilioののPHPクライアント
use App\Components\Sms\Drivers\NullDriver; // configに設定がない時用のDriver
use App\Components\Sms\Drivers\NexmoDriver;
use App\Components\Sms\Drivers\TwilioDriver;
use Nexmo\Client\Credentials\Basic as NexmoBasicCredentials; // Nexmoの認証に使うやつ
class SmsManager extends Manager
{
public function channel($name = null)
{
return $this->driver($name);
}
public function createNexmoDriver()
{
return new NexmoDriver(
$this->createNexmoClient(),
$this->app['config']['sms.nexmo.from']
);
}
public function createTwilioDriver()
{
return new TwilioDriver(
$this->createTwilioClient(),
$this->app['config']['sms.twilio.from']
);
}
protected function createNexmoClient()
{
return new NexmoClient(
new NexmoBasicCredentials(
$this->app['config']['sms.nexmo.key'],
$this->app['config']['sms.nexmo.secret']
)
);
}
protected function createTwilioClient()
{
return new TwilioClient(
$this->app['config']['sms.twilio.key'],
$this->app['config']['sms.twilio.secret']
);
}
public function createNullDriver()
{
return new NullDriver;
}
public function getDefaultDriver()
{
return $this->app['config']['sms.default'] ?? 'null';
}
}
createClientで、SMSのapiの初期化をして各DriverClassに渡す。
App\Components\Sms\Drivers\Driver
各外部サービスのDriverクラスの抽象クラス、共通の設定処理を抽象化しておく。
<?php
namespace App\Components\Sms\Drivers;
use Illuminate\Support\Arr;
use App\Components\Sms\Exceptions\SmsException;
abstract class Driver implements
{
protected $recipient;
protected $message;
abstract public function send();
public function to(string $recipient) // 宛先の設定
{
$this->recipient = $recipient;
return $this; // メソッドチェーンしたいので$thisを返しておく。
}
public function content(string $message) // メッセージの設定
{
$this->message = $message;
return $this;
}
}
App\Components\Sms\Drivers\TwilioDriver
Twilio用のDriverクラス
<?php
namespace App\Components\Sms\Drivers;
use Twilio\Rest\Client as TwilioClient;
class TwilioDriver extends Driver
{
/**
* The Twilio client.
*
* @var \Twilio\Rest\Client
*/
protected $client;
/**
* The phone number this sms should be sent from.
*
* @var string
*/
protected $from;
/**
* Create a new Twilio driver instance.
*
* @param \Twilio\Rest\Client $twilio
* @param string $from
* @return void
*/
public function __construct(TwilioClient $twilio, $from)
{
$this->client = $twilio;
$this->from = $from;
}
public function send() // 送信処理
{
return $this->client->messages->create(
$this->recipient, [
'from' => $this->from,
'body' => trim($this->message)
]
);
}
}
使ってみる
SMS::channel('twilio')
->to($phoneNumber)
->content('Using twilio driver to send SMS')
->send();
channelメソッドでDriver(TwilioかNexmo)を指定して、各種設定をしてメッセージを送信できて便利。
参考サイト
https://www.slideshare.net/BobbyBouwmann/laravel-design-patterns-86729254
https://itnext.io/building-driver-based-components-in-laravel-5b390dc25bd9
https://github.com/orobogenius/building-driver-based-components