本記事ではLaravelのアプリケーションを理解し、より良い設計・アーキテクチャを構築できるように学習したことを簡潔にまとめています。
目次
1.Laravelのアーキテクチャ
2.アプリケーションのアーキテクチャ
3.HTTPリクエストとレスポンス
4.データベース
5.認証と許可
6.イベントとキューによる処理の分離
7.コンソールアプリケーション
8.テスト
9.エラーハンドリングとログの活用
10.テスト駆動開発の実践
1.Laravelのアーキテクチャ
1-2.サービスコンテナ
laravelではコントローラやモデルもクラスであり、これらのクラスのインスタンスを管理するのがサービスコンテナの役割。
サービスコンテナとは
通常、他のクラスの機能を利用する場合は下記に示す通り、対象となるクラスのインスタンスを生成し、メソッドをコールする。
//通常クラスメソッドの利用
$hoge = new \App\Services\HogeService();
$hoge->doSomething();
アプリケーション内の複数クラスで同じ機能を利用する場合、利用するクラスに応じてインスタンスを生成する、また、個別に設定値などの初期処理を用意するのは効率が悪いく、不具合の原因にもなる。
→サービスコンテナの出番
サービスコンテナは各クラスのインスタンスやインスタンスの生成方法を保持し、ビジネスロジックから要求されると、所定の手順に従って、インスタンスを生成して返却する。
また、クラス内で必要となる機能クラスのインスタンスを、コンストラクタやメソッドの引数などを使って外部から渡す(注入する)場合も、注入するインスタンスの生成やクラスへの注入をサービスコンテナが担う。
サービスコンテナに対して、インスタンスを生成方法を登録する処理を「バインド」、指定されたインスタンスをサービスコンテナが生成して返すことを「解決する」と呼ぶ。
サービスコンテナの操作は、Illuminate\Foundation\Applicationクラスのインスタンスに対してメソッドを実行する。下記にサービスコンテナからインスタンスを取得する例を示す。
// app関数から取得する場合
$app = app();
//Applicatoin::getInstanceメソッドから取得する場合
$app = \Illuminate\Foundation\Application::getInstance();
//ファサードから取得する場合
$app = \App::getInstance();
続いてFooLoggerクラスをサービスコンテナにバインドして解決する簡単な例を下記に示す。
use Illuminate\Foundation\Application;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
class FooLogger
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
// ①バインド処理
app()->bind(FooLogger::class, function (Applicatoin $app) {
$logger = new Logger('my_log');
return new FooLogger($logger);
});
// ②解決処理
$foologger = app()->make(FooLogger::class);
<コード解説>
①はバインドの一例で、クラスがFooLoggerクラスを要求した際に実行する処理を、bindメソッドを利用して、インスタンスの生成処理をクロージャ(無名関数)で指定している。なお、FooLoggerクラスはLoggerクラスに依存していため、returnの前にnewしている。
②は解決の一例。makeメソッドを利用して解決対象の文字列を引数で指定しており、FooLoggerクラスを要求しているので、①のクロージャーが実行され、FooLoggerクラスのインスタンスが返される。
バインド
バインドの具体的なコード例を以下に示す。
- bindメソッド
- bindlfメソッド
- singletonメソッド
- instanceメソッド
- whenメソッド
bindメソッド
bindは最も利用されるバインドメソッドで、第一引数に文字列、第二引数にインスタンスの生成処理をクロージャで指定する。下記に利用例を示す。
class Number
{
protected $number;
public function __construct($number = 0)
{
$this->number = $number;
}
public function getNumber()
{
return $this->number;
}
}
// ①バインド処理
app()->bind(Number::class, function() {
return new Number();
});
$numcls = app()->make(Number::class);
$number = $numcls->getNumber();
bindメソッドで指定した場合、バインドしたクロージャは解決されるたびに実行されるため、上記コードでは常に新しく生成されたNumberインスタンスが返される。
下記に、解決時にパラメータを指定する例と別名でバインドする例を示す。
//パラメータを指定する例
use Illuminate\Foundation\Application;
//クロージャで引数を受け取り、Numberクラスのコンストラクタに渡す
app()->bind(Number::class, function(Application $app, array $parameters) {
return new Number($parameters[0]);
});
//解決時に引数で値を指定すると、クロージャの第二引数に格納される
$numcls = app()->make(Number::class, [100]);
echo $numcls->getNumber(); //echoの実行結果
//別名でバインドする例
//Numberクラスに対してnumという名前を割り当てる
app()->bind('num', function() {
return new Number();
});
//Numberクラスのインスタンスが返される
$numcls = app()->make('num');
$numcls = $numcls->getNumber();
bindIfメソッド
前述のbindメソッドと引数の指定方法は同じだが、引数で指定された文字列に対するバインドが存在しない場合のみバインド処理を行うメソッド。(同名のバインドが存在する場合は何も行われない)
下記に「Number 100」に対してbindIfメソッドを2回呼ぶ例を示す。
// ① まだバインドされていないため処理が実行される
app()->bindIf('Number100', function() {
return new Number(100);
});
// ② 既に同名でバインドしてあるため、バインドされない
app()->bindIf('Number100', function() {
return new Number(200);
});
$numcls = app()->make('Number100');
$number = $numbcls->getNumber();
// $number = 100
singletonメソッド
インスタンスを1つのみにする場合、singletonメソッドを利用する。2回目以降は、サービスコンテナが解決したインスタンスはキャッシュされ、そのキャッシュされたインスタンスが返される。
class RandomNumber extends Number
{
public function __construct()
{
//Numberクラスのコンストラクタにランダムを渡す
parent::__construct(mt_rand(1, 10000));
}
}
app()->singleton('random', function() {
return new RandomNumber();
});
$number1 = app('random');
$number2 = app('random');
// $number2は新規に生成されずにキャッシュされたインスタンスが返されるため、
$number1->getNumber() === $number2->getNumber()となる。
instanceメソッド
既に生成したインスタンスをサービスコンテナにバインドする際に利用。
$numcls = new Number(1001);
app()->instance('SharedNumber', $numcls);
$number1 = app('SharedNumber');
$number2 = app('SharedNumber');
// $number->getNumber() === $number2->getNumber()
上記のコード例では、あらかじめ生成したNumberクラスのインスタンスをバインドしており、
このインスタンスはsingletonメソッドと同様にサービスコンテナにキャッシュされ、解決では同じインスタンスが返される。
複雑なインスタンス構築処理のバインド
クラスのインスタンスを生成するとき、他のクラスのインスタンスや前処理を必要とする場合がある。そのような場合もまとめてバインドすることができる。
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Illuminate\Foundation\Application;
class Complex
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
//初期化処理を行う
public function setup()
{
//初期化処理を記述
}
}
app()->bind(Complex::class, function(Applicatoin $app) {
$logger = $app->make(Logger::class);
$complex = new Complex($logger);
$complex->setup();
return $complex;
});
<コード解説>
上記のコードでは、はじめにComplexクラスが依存するPsr\Log\LoggerInterfaceインタフェースを実装した
Monolog\Loggerクラスのインスタンスを取得し、それをComplexクラスのコンストラクタに渡して、
Complexクラスのインスタンスを生成している。最後に初期化処理(setup())を呼び出しイスタンスを返している。
バインドの定義場所
これまで学んできたバインドを定義する場所は、app\ProvidersフォルダにServiceProviderクラスを作成するか、
既存のAppServicesProviderクラスに定義する必要がある。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// インスタンス生成時に他のクラスを利用とする必要があるときは、ここにバインド処理を書く
}
public function register()
{
// 通常はここにバインド処理
}
}
解決
サービスコンテナが解決したインスタンスを取得する方法は、
1.makeメソッド
2.appヘルパ関数
の2通りある。
makeメソッド
makeメソッドは引数に対象の文字列を指定する。メソッド実行後は指定された文字列に
バインドされた処理を実行し、戻り値を返す。
app()->bind(Number::class, function() {
return new Number();
});
$number1 = app()->make(Number::class);
app関数
appヘルパ関数の引数はmakeメソッドと同じで、文字列を指定するとサービスコンテナが解決を行いインスタンスを返す。
app()->bind(Number::class, function() {
return new Number();
});
$number2 = app(Number::class);
バインドしていない文字列の解決
バインドする文字列がクラス名であり、かつ具象クラス(メソッドの中身が記述されている・インスタンス化できるクラス)であれば、バインド処理がなくてもサービスコンテナがそのクラスのコンストラクタを実行してインスタンスを生成してくれる。
class Unbinding
{
protected $name;
public function __construct($name = "")
{
$this->name = $name;
}
}
// ①
$unbinding1 = app()->make(Unbinding::class);
// ②
$unbinding2 = app()->make(Unbinding::class, ['Hoge']);
<コード解説>
①では、文字列がクラス名かつ、具象クラスであるため自動でインスタンスが生成される
②では、パラメータを指定して解決することでコンストラクタに渡している。