はじめに
本記事はLaravel(5.6)をより深く理解することを目的としており、アプリケーション開発のhow-toのような内容ではありませんので予めご了承ください。アプリケーション開発を行う上での情報は公式ドキュメントをご覧ください。
Laravel5.6公式ドキュメント
https://laravel.com/docs/5.6/
【追記 2018/03/25】
Eloquent編をアップしました。ご興味のある方はこちらもどうぞ。
Deep Dive into Laravel Eloquent ORM
Laravelのリクエスト ライフサイクル
まずは、リクエストを受けてレスポンスを返すまでの間にLaravel内でどのような処理が行われているのか見てみましょう。下図が全体の流れをシーケンス図風にまとめたものになります(スペースの関係上、細かい内容やメッセージ線を省略しているのでご了承ください)。
上図で登場するオブジェクトは以下の4つです。
- bootstrap ・・・起動スクリプト(bootstrap/app.php)
- Container ・・・サービス コンテナ(Illuminate\Foundation\Application)
- Component(framework) ・・・Laravelフレームワークの各種コンポーネント
- Component(application) ・・・アプリケーションのコンポーネント(コントローラ etc)
それでは順番に処理を見て行きましょう。
アプリケーションの作成
クライアントからリクエストが送られてくると始めにアプリケーションの作成が行われます。ここでいうアプリケーションとはサービス コンテナ(DIコンテナ)を表します。アプリケーションの作成で行われる主な処理は以下の2つです。
- サービス プロバイダの登録
- コアクラスのエイリアスの登録
サービス プロバイダの登録では、以下の3つのサービス プロバイダがサービス コンテナに登録されます。
- Illuminate\Log\LogServiceProvider
- Illuminate\Events\EventServiceProvider
- Illuminate\Routing\RoutingServiceProvider
そして、各サービス プロバイダからログやイベント、ルーティングに関する基本的なクラスがサービス コンテナに登録されます。
コアクラスのエイリアス登録では、以下のように一つのキーに対して複数のエイリアス名が登録される形になります。
[
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
]
補足説明するとサービス コンテナの内部では抽象エイリアスとエイリアスという2種類のエイリアスを持っていて、上記でいうと配列のキーが抽象エイリアスで配列の値(クラス名、インターフェース名)がエイリアスになります。
例えば、LogManager
はサービス プロバイダで以下のように抽象エイリアスを使ってサービス コンテナに登録されています。
$this->app->singleton('log', function () {
return new LogManager($this->app);
});
コンポーネントを取得する場合は、抽象エイリアス / エイリアスのどちらを指定してもサービス コンテナが名前解決してくれて同じインスタンス(singletonの場合)を返してくれます。
var_dump(app('log') === app('Illuminate\Log\LogManager')); // boolean true
var_dump(app('log') === app('Psr\Log\LoggerInterface')); // boolean true
なお、混同しやすいのですが上記のエイリアスはconfig\app.php
で定義するFacadeのエイリアスとは別のものです。
'aliases' => [
...
'Route' => Illuminate\Support\Facades\Route::class,
...
こちらのエイリアスはIlluminate\Foundation\AliasLoader
を使ってFacadeのインスタンスを取得するために使用されます。こちらも簡単に説明しておきますと、AliasLoaderはclass_alias関数を使用してエイリアスから実体となるクラスをオートロードしています。
例えば、ルートの定義でRoute::get
と記述しますが、このRoute
がエイリアスです。
Route::get('/', function () {
return view('welcome');
});
Route
はAliasLoaderによってIlluminate\Support\Facades\Route::class
と紐づけられているため、初回呼び出し時にIlluminate\Support\Facades\Route
がオートロードされます。Routeファサードクラスは、getFacadeAccessor
でrouter
を返しますが、前述した抽象エイリアスになります。
protected static function getFacadeAccessor()
{
return 'router';
}
Routingサービス プロバイダでIlluminate\Routing\Router
をrouter
としてバインドしているため、ルートの定義のRoute::get
はIlluminate\Routing\Router
のgetメソッドを呼び出していることになります。
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
bootstrapperによる設定
アプリケーションの作成が終わるとbootstrapperが起動されます。ここでは、Illuminate\Foundation\Bootstrap
直下にある以下のクラスのことを便宜的にbootstrapperと呼んでいます。
- \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables
- \Illuminate\Foundation\Bootstrap\LoadConfiguration
- \Illuminate\Foundation\Bootstrap\HandleExceptions
- \Illuminate\Foundation\Bootstrap\RegisterFacades
- \Illuminate\Foundation\Bootstrap\RegisterProviders
- \Illuminate\Foundation\Bootstrap\BootProviders
まず、最初に.envファイルが読み込まれます。環境変数を設定しておけば.env.localのように環境ごとにファイルを分けることができます。
例)Nginx(/etc/nginx/fastcgi_params)
fastcgi_param APP_ENV local;
なお、configのキャッシュを作成した場合、bootstrapperは.envファイルを読み込みません。
php artisan config:cache
.envファイルの読み込みが終わると次にconfigディレクトリ直下にあるapp.phpなどの各設定ファイルが読み込まれます(configのキャッシュがある場合はキャッシュを読み込む)。その次にbootstrapでサービス コンテナにバインドしている例外ハンドラがset_exception_handler
に設定されます。
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
その次が前述したFacadeのエイリアスの登録になります。app.phpの”aliases”に設定した全エイリアスがここでAliasLoaderに登録されます。また、パッケージManifestファイル(後述)に設定されているエイリアスもここで登録されます。
そして、最後がサービス プロバイダの登録になります。app.phpに設定した”providers”に設定したプロバイダおよびパッケージManifestファイルに設定されているサービス プロバイダがサービス コンテナに登録されます。
パッケージManifestファイルとはbootstrap\cache\packages.php
ファイルの事を指しています。このファイルはphp artisan package:discover
が実行された時に作成されるのですが、このコマンドが実行されるのが、composerのpost-autoload-dump
が実行されるタイミングになります。
"scripts": {
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover"
]
},
新しいパッケージが追加されるとartisanコマンドが起動されてvendor\composer\installed.json
ファイルが読み込まれ、extraプロパティに"laravel"が定義されているパッケージから"providers"と"aliases"を抽出してパッケージManifestファイルに出力します。
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
},
"laravel": {
"providers": [
"Laravel\\Socialite\\SocialiteServiceProvider"
],
"aliases": {
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
}
}
},
なお、app.phpとパッケージManifestファイルのプロバイダ情報は初回起動時にbootstrap\cache\services.php
というサービスManifestファイルに出力され、そちらがプロバイダの設定情報として読み込まれるようになります(新しいパッケージが追加・削除された場合は自動的に再作成される)。 1
サービスManifestファイルをもとにサービス プロバイダは"eager"と"deferred"に分類されてサービス コンテナに登録され、"deferred"で登録されたサービスプロバイダは必要になった時点でインスタンス化されサービス コンテナにバインドされます。
リクエストの処理
bootstrapperによる設定が終わるとリクエストがKernelに渡り、まず最初に以下の5つのグローバル ミドルウェアによってリクエストが処理されます。 2
- \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode
- \Illuminate\Foundation\Http\Middleware\ValidatePostSize
- \App\Http\Middleware\TrimStrings
- \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull
- \App\Http\Middleware\TrustProxies
CheckForMaintenanceModeはアプリケーションがメンテナンスモードかどうかをチェックします(artisanコマンドを実行することでstorage\framework
直下にdownという名前のファイルが作成されメンテナンスモードにすることができる)。メンテナンスモードの場合は503 HTTPステータスコードが返されます。
php artisan down
ValidatePostSizeはリクエストのCONTENT_LENGTHがphp.iniに設定されているpost_max_size
を超えていないかどうかをチェックします。超えている場合は413 HTTPステータスコードが返されます。
TrimStringsはGETパラメータおよびPOSTパラメータの値(JSONリクエストの場合は各プロパティの値)をトリムします。ただし、"password"と"password_confirmation"という名前のパラメータはトリムしません。
ConvertEmptyStringsToNullはGETパラメータおよびPOSTパラメータの値(JSONリクエストの場合は各プロパティの値)が空文字の場合NULL値に変更します。
TrustProxiesはクライアントとWebサーバの間にロードバランサーやリバースプロキシを挟んでいる場合に、URLの生成やリダイレクトなどが正常に動作するようにリクエストにプロキシ情報を設定します。
class TrustProxies extends Middleware
{
// プロキシのアドレス
protected $proxies = [
'192.168.1.1',
'192.168.1.2',
];
// プロキシヘッダのマップ
protected $headers = [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];
}
configにtrustedproxy.phpファイルを作成して設定することもできます。
<?php
return [
'proxies' => [
'192.168.1.1',
'192.168.1.2',
],
'headers' => [
\Illuminate\Http\Request::HEADER_FORWARDED => 'FORWARDED',
\Illuminate\Http\Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
\Illuminate\Http\Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
\Illuminate\Http\Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
\Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
]
];
グローバル ミドルウェアの処理が終わるとリクエストにマッチするRoute情報を探し出し、設定されているコントローラ ミドルウェアにリクエストの処理をさせます。コントローラ ミドルウェアとは、コントローラのルートやコントローラのコンストラクタの中で指定したミドルウェアのことです。
// ルートで設定する例
Route::get('profile', 'UserController@show')->middleware('auth');
// コントローラのコンストラクタで設定する例
public function __construct()
{
$this->middleware('auth');
}
ミドルウェアグループとミドルウェアはapp\Http\Kernel.php
に定義されています。
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
※app\Providers\RouteServiceProvider
によってroutes/web.php
にはwebミドルウェアグループ、routes/api.php
にはapiミドルウェアグループが自動的に適用されます。
EncryptCookiesはCookieデータをopenssl_encrypt
およびopenssl_decrypt
を使って暗号化・復号化します。暗号化・復号化で使用するキーは.envファイルに設定しているAPP_KEYになります。暗号化したくないcookieがある場合はミドルウェアの$except
に名前を設定することで生データのまま保存することができます。
AddQueuedCookiesToResponseはcookieキューに保存されたcookieをレスポンスに追加します。レスポンスが作成される前に先にcookieに値をセットしたい場合にcookieキューを使用することができ、Cookieファサードを使用することでキューにcookieを追加することができます。
Cookie::queue('name', 'value', 60 * 24);
StartSessionはconfig/session.php
が定義されている場合にセッションをスタートします。また、session.phpに設定したlottery
の値に基づきセッションのgcが行われます。
'lottery' => [2, 100], // random_int(1, 100) <= 2 がtrueの場合にgcが行われる
AuthenticateSession(デフォルトでコメントアウト)はセッションにユーザーのパスワードのハッシュ値を保存し、セッションに保存されたハッシュ値と現在のユーザーのパスワードのハッシュ値が一致しない場合は強制的にログアウトさせます。例えばパスワードが第3者に漏れて不正にログインされた場合、ユーザーがパスワードを変更すると第3者は強制的にログアウトされます。
ShareErrorsFromSessionはバリデーションエラーなどセッションに'errors'として保存されたデータをerrors変数としてビューにアサインします。
VerifyCsrfTokenはセッションに保存しているトークンとリクエストされたトークンが一致するかどうかをチェックします。トークンに一致しない場合は419 HTTPステータスコードを返します。トークンチェックをしたくないURLがある場合はミドルウェアの$except
にURLを設定することでトークンチェックを回避することができます。メソッドが"HEAD"、"GET"、"OPTIONS"であったりコンソールからユニットテストを実行している場合はトークンチェックは行われません。
SubstituteBindingsはルートパラメータの値を別の値に付け替える処理を行います。具体的にはRoute::bind
やモデル結合ルートがSubstituteBindingsの処理に該当します。
public function boot()
{
parent::boot();
// userパラメータの値からユーザー情報を取得し、userパラメータにモデルインスタンスをセットし直す
Route::bind('user', function ($value) {
return App\User::where('name', $value)->first() ?? abort(404);
});
}
// userパラメータの値からユーザー情報を取得し、モデルインスタンスを自動的に注入
Route::get('api/users/{user}', function (App\User $user) {
return $user->email;
});
Authenticateは指定されたガードでユーザー認証を行い、ユーザー認証に失敗したらログインページにリダイレクトします(JSONレスポンスの場合は401 HTTPステータスコード)。ガードを指定していない場合はconfig\auth.php
に設定しているデフォルトガードを使用します。なお、ガードは複数指定でき、どれか一つのガードで認証できれば認証成功になります。
AuthenticateWithBasicAuthはBasic認証によるログインを行います。Basic認証のユーザー名にはメールアドレスを使用します。認証に失敗した場合は401 HTTPステータスコードを返します。なお、AuthenticateWithBasicAuthはconfig\auth.php
に設定しているデフォルトガードを使用しますが、ドライバが"token"だとBasic認証は使用できません(TokenGuardにBasic認証処理が実装されていないため)。
SetCacheHeadersはレスポンスにキャッシュヘッダ(ETag、max-age、s-max-age)を設定します。
Route::get('/', function () {
return view('welcome');
})->middleware('cache.headers:etag;max_age=120;s_maxage=60');
Authorizeは指定されたアビリティ(許可したい操作)およびルートパラメータ(もしくはモデルクラス名)を基にApp\Providers\AuthServiceProvider
でマッピングされたポリシーを呼び出し認可処理を行います。権限がない場合は403 HTTPステータスコードを返します。ルートパラメータを指定した場合、前述したSubstituteBindingsによって暗黙のモデル結合が行われます。
// ルートパラメータにpostを指定
Route::put('/post/{post}', function (Post $post) {
// do something
})->middleware('can:update,post');
RedirectIfAuthenticatedは指定されたガードによるログインチェックを行い、ユーザーがログイン済みの場合はホーム画面へリダイレクトします。ログインしていない場合はそのまま後続へ処理を引き渡します。
ThrottleRequestsはユーザーが同一ルートに対して一定の時間間隔(分)で何回までアクセス可能かを制限します。制限を超えた場合は429 HTTPステータスコードを返します。レスポンスヘッダーにX-RateLimit-Limit
とX-RateLimit-Remaining
が付加され、制限値と残回数を確認することができます。また、制限を超えるとRetry-After
(何秒後にリトライ可能)とX-RateLimit-Reset
(制限がリセットされるUNIXタイムスタンプ)ヘッダーも付加されます。同一ユーザーの判別はログインユーザーの場合はユーザー情報のハッシュ値、ゲストユーザーの場合はサイトのドメインとIPアドレスを組み合わせたもののハッシュ値で行われます。なお、ユーザーの試行回数などのデータはconfig\cache.php
に設定されたドライバに基づきキャッシュとして保存されます。
// 1分間に5回まで
Route::get('/', function () {
return view('welcome');
})->middleware('throttle:5,1');
上記、コントローラ ミドルウェアの処理が終わるとControllerDispatcherによりリクエストされたコントローラのアクションが実行され、その結果がレスポンスとしてクライアントに返されます。
以上がLaravelの大まかなリクエスト ライフサイクルになります。続いてLaravelのコアの部分であるサービス コンテナ、サービス プロバイダ、カーネル & ルーター、コントローラ、ビューを見て行きたいと思います(ここでは公式サイトの内容を補足していきます)。
サービスコンテナ
[公式サイト]Service Container
https://laravel.com/docs/5.6/container
LaravelのサービスコンテナはIlluminate\Foundation\Application
です。サービスコンテナが実装しているインターフェースは以下のようなメソッドが定義されています(理解しやすいように順番を並び替えてグルーピングしています)。
// コンテナにコンポーネントを登録する
public function bind($abstract, $concrete = null, $shared = false);
public function bindIf($abstract, $concrete = null, $shared = false);
public function singleton($abstract, $concrete = null);
public function instance($abstract, $instance);
public function extend($abstract, Closure $closure);
public function when($concrete);
// コンテナからコンポーネントを取得する
public function make($abstract, array $parameters = []);
public function factory($abstract);
public function call($callback, array $parameters = [], $defaultMethod = null);
// コンテナの状態を判定する
public function bound($abstract);
public function resolved($abstract);
// コンポーネントに情報を付加する
public function alias($abstract, $alias);
public function tag($abstracts, $tags);
public function tagged($tag);
// コンテナのイベントを監視する
public function resolving($abstract, Closure $callback = null);
public function afterResolving($abstract, Closure $callback = null);
// アプリケーションの情報・状態など
public function version();
public function basePath();
public function environment();
public function isDownForMaintenance();
public function getCachedServicesPath();
public function getCachedPackagesPath();
public function runningInConsole();
public function runningUnitTests();
// サービス プロバイダの登録・起動
public function registerConfiguredProviders();
public function register($provider, $options = [], $force = false);
public function registerDeferredProvider($provider, $service = null);
public function boot();
// 起動イベントを監視する
public function booting($callback);
public function booted($callback);
// リクエストの処理
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
インターフェースを見て分かるようにApplication(サービスコンテナ)の主な役割はコンポーネントの登録・管理、サービス プロバイダの登録・管理、リクエストの処理になります。
ただし、リクエストの処理に関しては、標準的なプロジェクト構成ではKernel
が直接リクエストを処理するようになっています。
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
なお、サービス コンテナのboot
メソッド(サービス プロバイダの起動)は、前述したリクエスト ライフサイクルのbootstrapperの最後で、BootProviders
というサービス プロバイダによって実行されます。
次にコンポーネントの登録・取得の内部動作を簡単に見てみましょう。
/* コンポーネントの登録例 */
$this->app->bind('App\Services\Twitter', function ($app) {
return new App\Services\Twitter($app->make('App\Libraries\OAuthClient'));
});
// インターフェース名と実装クラス名を指定
$this->app->bind('App\Contracts\SocialMedia', 'App\Services\Twitter');
$this->app->bind(\App\Contracts\SocialMedia::class, \App\Services\Twitter::class);
/* コンポーネントの取得例 */
App::make('App\Services\Twitter'); // Appファサード利用
app('App\Services\Twitter'); // appヘルパー利用
コンポーネントを登録する場合は、上の例のように第1引数に実装クラス名やインターフェース名、第2引数にクロージャや実装クラス名を指定します。コンポーネントを取得する場合は、バインドする際に指定した実装クラス名やインターフェース名を指定します。
コンテナ内部ではbindメソッドで連想配列にバインド情報を保存し、makeメソッドでクロージャを実行して結果を返しています(singletonの場合は結果を保存)。第2引数に実装クラス名を指定した場合はリフレクションを使って実装クラスをインスタンス化します。実装クラスのコンストラクタに引数が指定されている場合も依存性を解決してインスタンス化してくれます。
// OAuthClientをインスタンス化して注入
class Twitter {
public function __construct(OAuthClient $client) {}
}
// OAuthClientをインスタンス化して注入、$keyはデフォルト値が入る
class Twitter {
public function __construct(OAuthClient $client, $key = 'xxxx') {}
}
// $keyが解決できないのでエラー
// 取得の際にkeyを指定すればOK app('App\Services\Twitter', ['key' => 'xxxxx']);
class Twitter {
public function __construct(OAuthClient $client, $key) {}
}
なお、bindメソッドの第1引数にはインターフェース名(もしくは実装クラス名)を指定しますが、ユニークな文字列であればインターフェース名でなくても構いません。
$this->app->bind('socialmedia', 'App\Services\Twitter');
インスタンス結合も同様にクラス名である必要はないため、セッションやリクエストを使わずに値を引き継ぐような用途でも利用できます。
$this->app->instance('debug', true);
サービス プロバイダ
[公式サイト]Service Providers
https://laravel.com/docs/5.6/providers
サービス プロバイダは"eager"と"deferred"の2種類に分かれます。"eager"のサービス プロバイダはbootstrapperでコンテナに登録されるタイミングでregister
メソッドが実行され、全てのプロバイダの登録が完了したらすぐにboot
メソッドが実行されます。
"deferred"のサービス プロバイダはコンテナからコンポーネントを取得するタイミングで、そのコンポーネントがまだコンテナに登録されていない場合にプロバイダがコンテナに登録されてregister
メソッドとboot
メソッドが実行されます。
もう少し分かりやすいように実例でご説明します。
BroadcastServiceProviderはprovides
メソッドで3つのクラス名を返しています。
public function provides()
{
return [
BroadcastManager::class,
BroadcastingFactory::class,
BroadcasterContract::class,
];
}
そして、前述したManifestファイルの作成のときに上記情報が参照されて以下のような内容のファイルが作られます。
'deferred' =>
array (
'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
この情報により3つのコンポーネントがBroadcastServiceProviderによって依存性を注入されるということをコンテナが把握します。そして、上記3つのうちどれか一つのコンポーネントをコンテナから取得しようとしたタイミングでBroadcastServiceProviderが呼ばれてregister
メソッドおよびboot
メソッドが実行されます。一度プロバイダが実行されるとコンポーネントの依存性は解決されてコンテナに登録されるため、次回以降コンテナからコンポーネントを取得する際はプロバイダは実行されなくなります。
なお、フレームワーク内にあるサービス プロバイダの一覧を以下に列挙します。
サービス プロバイダ | app.phpに定義あり | 遅延 | 概要 |
---|---|---|---|
Illuminate\Auth\AuthServiceProvider | ○ | 認証・認可で必要となるコンポーネントを登録する | |
Illuminate\Auth\Passwords\PasswordResetServiceProvider | ○ | ○ | パスワードリセットで必要となるコンポーネントを登録する |
Illuminate\Broadcasting\BroadcastServiceProvider | ○ | ○ | ブロードキャストで必要となるコンポーネントを登録する |
Illuminate\Bus\BusServiceProvider | ○ | ○ | 同期・非同期ジョブで必要となるコンポーネントを登録する |
Illuminate\Cache\CacheServiceProvider | ○ | ○ | キャッシュで必要となるコンポーネントを登録する |
Illuminate\Cookie\CookieServiceProvider | ○ | セッションで必要となるクッキー関連のコンポーネントを登録する | |
Illuminate\Database\DatabaseServiceProvider | ○ | データベース接続で必要となるコンポーネントの登録を行う。また、プロバイダのboot時にEloquentモデルにConnectionResolverやEventDispatcherをセットする | |
Illuminate\Database\MigrationServiceProvider | ○ | DBのマイグレーションで必要となるコンポーネントを登録する。このプロバイダはConsoleSupportServiceProvider内で登録される(configの記述不要) | |
Illuminate\Encryption\EncryptionServiceProvider | ○ | 暗号化で必要となるコンポーネントを登録する | |
Illuminate\Events\EventServiceProvider | イベントで必要となるコンポーネントを登録する。このプロバイダはフレームワーク内で自動的に登録される(configの記述不要) | ||
Illuminate\Filesystem\FilesystemServiceProvider | ○ | ファイルストレージで必要となるコンポーネントを登録する | |
Illuminate\Foundation\Providers\ArtisanServiceProvider | ○ | Artisanコンソールの全コマンドを登録する。このプロバイダはConsoleSupportServiceProvider内で登録される(configの記述不要) | |
Illuminate\Foundation\Providers\ComposerServiceProvider | ○ | フレームワーク内のComposer関連の処理で必要となるコンポーネントを登録する。このプロバイダはConsoleSupportServiceProvider内で登録される(configの記述不要) | |
Illuminate\Foundation\Providers\FormRequestServiceProvider | プロバイダのboot時にコンテナイベント(フォームリクエストの初期処理、バリデーションエラー処理)を登録する。このプロバイダはFoundationServiceProvider内で登録される(configの記述不要) | ||
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider | ○ | MigrationServiceProvider、ArtisanServiceProvider、ArtisanServiceProviderを登録するためのアグリゲーションプロバイダ | |
Illuminate\Foundation\Providers\FoundationServiceProvider | ○ | FormRequestServiceProviderの登録およびリクエストのvalidateマクロを設定する | |
Illuminate\Foundation\Support\Providers\AuthServiceProvider | 認可のポリシーの登録を行う。アプリ側のAuthServiceProviderが継承する親クラス | ||
Illuminate\Foundation\Support\Providers\EventServiceProvider | イベントのlistenerとsubscriberを登録する。アプリ側のEventServiceProviderが継承する親クラス | ||
Illuminate\Foundation\Support\Providers\RouteServiceProvider | アプリケーションのルート情報を読み込む。アプリ側のRouteServiceProviderが継承する親クラス | ||
Illuminate\Hashing\HashServiceProvider | ○ | ○ | ハッシュで必要となるコンポーネントを登録する。デフォルトはCRYPT_BLOWFISH アルゴリズムだが、ドライバを指定することでArgon2 アルゴリズムも利用可能(php7.2以降) |
Illuminate\Log\LogServiceProvider | ログで必要となるコンポーネントを登録する。このプロバイダはフレームワーク内で自動的に登録される(configの記述不要) | ||
Illuminate\Mail\MailServiceProvider | ○ | ○ | メールで必要となるコンポーネントを登録する |
Illuminate\Notifications\NotificationServiceProvider | ○ | 通知で必要となるコンポーネントの登録およびboot時にビューに名前空間"notifications"を作成する | |
Illuminate\Pagination\PaginationServiceProvider | ○ | ページネーションで必要となるコンポーネントの設定およびboot時にビューに名前空間"pagination"を作成する | |
Illuminate\Pipeline\PipelineServiceProvider | ○ | ○ | パイプラインHubで必要となるコンポーネントを登録する |
Illuminate\Queue\QueueServiceProvider | ○ | ○ | 同期・非同期ジョブで必要となるコンポーネントを登録する |
Illuminate\Redis\RedisServiceProvider | ○ | ○ | Redisで必要となるコンポーネントを登録する |
Illuminate\Routing\RoutingServiceProvider | ルーティングで必要となるコンポーネントを登録する。このプロバイダはフレームワーク内で自動的に登録される(configの記述不要) | ||
Illuminate\Session\SessionServiceProvider | ○ | セッションで必要となるコンポーネントを登録する | |
Illuminate\Translation\TranslationServiceProvider | ○ | ○ | 多言語化で必要となるコンポーネントを登録する |
Illuminate\Validation\ValidationServiceProvider | ○ | ○ | バリデーションで必要となるコンポーネントを登録する |
Illuminate\View\ViewServiceProvider | ○ | ビューで必要となるコンポーネントやビューエンジン(file、php、blade)を登録する |
カーネル & ルーター
[公式サイト]Routing
https://laravel.com/docs/5.6/routing
カーネルはリクエストを受け取りルーターに渡すフロント コントローラの役割を果たします。カーネルが実装しているインターフェースは以下のようなメソッドが定義されています。
public function bootstrap();
public function handle($request);
public function terminate($request, $response);
public function getApplication();
まず最初にbootstrap
メソッドを実行してbootstrapperを起動し、bootstrapperによる処理が終わるとミドルウェアを介してルーターにリクエストの処理を引き継ぎます。コントローラでの処理が終わりミドルウェアを介してレスポンスをクライアントに送信した後にアプリケーションの終了処理を行います。もし、ミドルウェアにterminate
メソッドが実装されている場合は、このタイミングでterminate
メソッドが実行されます。
次はルーターを見てみましょう。ルーター(Illuminate\Routing\Router
)が実装しているインターフェースは以下のようなメソッドが定義されています。
// ルートの登録・設定
public function get($uri, $action);
public function post($uri, $action);
public function put($uri, $action);
public function delete($uri, $action);
public function patch($uri, $action);
public function options($uri, $action);
public function match($methods, $uri, $action);
public function resource($name, $controller, array $options = []);
public function group(array $attributes, $routes);
// ルートパラメータのバインド
public function substituteBindings($route);
public function substituteImplicitBindings($route);
// ルートパラメータのバインド
public function bind($key, $binder);
public function getBindingCallback($key);
インターフェースを見て分かるように、ルーターの基本的な役割はルートの登録・設定、ルートパラメータの作成になります。まず、アプリケーション側のApp\Providers\RouteServiceProvider
によって指定されたルート定義ファイルroutes/web.php
とroutes/api.php
がルーターによってrequireされます。
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
ルート定義ファイルの中で以下のようにRouteファサードを使ってルート情報を登録していますので、ルート定義ファイルがrequireされたタイミングでルーターにルート情報が登録されます(ルート情報はIlluminate\Routing\Route
のコレクションで、それぞれのRouteが自分が担当するコントローラの実行を受け持つ)。
Route::get('foo', function () {
return 'Hello World';
});
ルーターはリクエストを受け取るとリクエストメソッドとURIから該当するルートを見つけ、コントローラ ミドルウェアが定義されている場合はミドルウェアを介してルートに処理を引き継ぎます。
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
そしてルートがコントローラを実行して結果をルーターに返すと、ルーターは受け取ったデータの型から適切なレスポンスを作成します。
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) {
$response = new JsonResponse($response, 201);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
もし、該当するルートが見つからなかった場合は404 HTTPステータスコードを返します。なお、フォールバックルートを設定することでルートが見つからなかった場合の動作をカスタマイズすることができます。
Route::fallback(function(){
return response()->view('errors.notFound', [], 404);
});
以上がルーティングの一連の流れになります。
コントローラ
[公式サイト]Controllers
https://laravel.com/docs/5.6/controllers
コントローラの実行はルートが担当します。ルートはApp\Providers\RouteServiceProvider
で指定した名前空間とルート定義ファイルで指定したコントローラ名@アクション名
をもとに実行するコントローラとメソッドを決定します。
Route::middleware('web')
->namespace($this->namespace) // App\Http\Controllers
->group(base_path('routes/web.php'));
Route::get('/home/', 'HomeController@index')->name('home');
もし、アクション名が指定されていない場合、クラスに__invoke
メソッドが定義されているかどうかをチェックし、メソッドが定義されている場合はそれを実行メソッドに決定します。そしてサービス コンテナを介してコントローラをインスタンス化し(サービス コンテナにコントローラも登録される)、メソッドを実行します。
アプリケーションの基底コントローラ(App\Http\Controllers\Controller
)は以下の3つのトレイトを使っています。
- Illuminate\Foundation\Validation\ValidatesRequests
- Illuminate\Foundation\Auth\Access\AuthorizesRequests
- Illuminate\Foundation\Bus\DispatchesJobs
ValidatesRequestsはコントローラに検証メソッドを提供します。
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|max:255',
'body' => 'required',
]);
}
これはリクエストオブジェクトのvalidateメソッドと同じ働きをします。
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
なお、Illuminate\Http\Request
自体にはvalidateメソッドは定義されていません。validateメソッドはFoundationServiceProvider
の中でmacroを使って後付けされています。
public function registerRequestValidate()
{
Request::macro('validate', function (array $rules, ...$params) {
validator()->validate($this->all(), $rules, ...$params);
return $this->only(collect($rules)->keys()->map(function ($rule) {
return str_contains($rule, '.') ? explode('.', $rule)[0] : $rule;
})->unique()->toArray());
});
}
AuthorizesRequestsはコントローラに認可メソッドを提供します。認可について公式サイトに記載がありますのでそちらをご確認ください。
[公式サイト]Authorization
https://laravel.com/docs/5.6/authorization
ただ、authorizeResource
メソッドの記載がないのでそこだけ簡単にご紹介します。これはリソースコントローラで認可処理を行う場合に利用できます。例えばコントローラのコンストラクタでメソッドを実行するとリソースコントローラのアクションに対して一括で認可処理を行うことができます。
public function __construct()
{
$this->authorizeResource(Photo::class);
}
リソースコントローラのアクションとポリシーメソッドの対応は以下の通りです。
アクション | ポリシーメソッド |
---|---|
show | view |
create | create |
store | create |
edit | update |
update | update |
destroy | delete |
DispatchesJobsはコントローラにジョブの実行メソッドを提供します。例えばartisanコマンドで同期ジョブを作成し、作成したジョブクラスのhandleメソッドに処理を実装します。
php artisan make:job SampleJob --sync
public function handle()
{
// do something
}
そして、コントローラ側でdispatchメソッドを使ってジョブを実行します。
$this->dispatch(app('App\Jobs\SampleJob'));
ビジネスロジックなどをジョブとして切り出すことで再利用性を高め、コントローラの肥大化も防ぐことができます。
ルートのキャッシュ
設定しているルートがコントローラベースのものだけであればルート設定をキャッシュすることにより、コントローラ実行までの時間を短縮することができます(クロージャベースのルートがある場合はキャッシュの作成不可)。
php artisan route:cache
キャッシュファイルはルート情報をシリアライズした形でbootstrap\cache\routes.php
に作られます。
app('router')->setRoutes(
unserialize(base64_decode('TzozNDoiSWxsdW.....'))
);
キャッシュについて少しだけ補足説明すると、ルートはSymfonyのRouteCompiler
によってコンパイルされてキャッシュされます。コンパイルを行うことで例えば以下のようなルートが正規表現に変換されます。
/password/reset -> #^/password/reset/(?P<token>[^/]++)$#su
Laravelではこの正規表現を使ってリクエストされたURLがマッチするかどうかを検証しています。ルート情報をキャッシュしていない場合は、ルート情報の登録に加えて、コンパイルしてルートが一致するかどうかの処理が毎回走るため、ルートの数が多ければ多いほどキャッシュの効果が大きくなります(ルートが100個あってリクエストURLが一番最後のルートに一致する場合、コンパイルが100回走ります)。
ビュー
[公式サイト]Views
https://laravel.com/docs/5.6/views
ビューの作成はviewヘルパーを使って行います。
Route::get('/', function () {
return view('welcome', $data);
});
ビューには名前空間を付けることができます。例えばAppServiceProviderでViewファサードを使って以下のように名前空間を付けます。
View::addNamespace('pc', resource_path('views/pc'));
View::addNamespace('sp', resource_path('views/sp'));
コントローラ側で名前空間を指定することでビューを切り替えることができます。
return view('sp::welcome');
では、もう少しビューが作成されるまでの処理を詳しく見てみましょう。viewヘルパーの内部ではサービス コンテナからViewの作成を行うFactoryクラスを取得してViewインスタンスの作成を行っています。Factoryクラスは指定されたビュー名からファイルのパスを取得し、ファイルの拡張子でレンダリングエンジンを決定します。
LaravelにはFileエンジン、PHPエンジン、Compilerエンジンの3つのレンダリングエンジンがあり、Fileエンジンは拡張子が.css
、PHPエンジンは拡張子が.php
、Compilerエンジンは.blade.php
となります。
PHPエンジンの場合はビューファイルがincludeされる際にviewヘルパーで渡したデータ配列がextract
関数で展開されるためファイル内でデータを参照することができます。Fileエンジンの方はfile_get_contents
関数でファイルを取得するだけなのでデータ配列を渡しても意味はありません。
FactoryクラスでViewインスタンスが作成されるとcreatingイベントがdispatchされます。Viewクリエイターを登録することで、このイベントをキャッチすることができます。例えば、ビューに結合したいデータがある場合、AppServiceProviderでViewクリエイターを登録すればViewインスタンスが作成される度にデータを結合することができます。
// クロージャではなくクラス名も指定可。クラスの場合はcreateメソッドを実装する
View::creator(['post', 'page'], function ($view) {
$view->with('status', 1);
});
コントローラ側でwithメソッドを使って、Viewクリエイターで結合したデータを上書きすることもできます。
return view('welcome')->with('status', 2);
Viewインスタンスが作成され、コントローラからルーターにViewインスタンスが返されると、ルーターはViewインスタンスをもとにResponseオブジェクトを作成します。
public static function toResponse($request, $response)
{
...
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
...
}
Viewクラスの__toString
でレンダリング処理が行われているため、ResponseオブジェクトにViewインスタンスを渡した時点でビューのレンダリングが始まります。
public function __toString()
{
return $this->render();
}
まず、レンダリング開始直前にcomposingイベントがdispatchされます。Viewコンポーザーを登録することで、このイベントをキャッチすることができます。
// クロージャではなくクラス名も指定可。クラスの場合はcomposeメソッドを実装する
View::composer(['post', 'page'], function ($view) {
$view->with('status', 1);
});
composingイベントの後、Viewインスタンスはレンダリングエンジンにビューファイルのパスとデータを渡します(Viewファサードのshareメソッドで共有されたデータもマージされる)。Compilerエンジンの場合、ビューファイルの最終更新日時とキャッシュファイル(storage\framework\views
)の最終更新日時を比較し、ビューファイルが更新されていればコンパイル処理を開始します。
コンパイラはtoken_get_all関数でビューファイルをPHPトークンに分割し、トークンIDがT_INLINE_HTML
のコンテンツをIlluminate\View\Compilers\Concerns
にある14個のコンパイルトレイトを使用してパースします。これを繰り返し全てのパースが終わって結果をマージし、ファイルをキャッシュフォルダに出力します(ファイル名はファイルパスをsha1でハッシュ化した値)。
キャッシュファイルはBladeの記法から普通のPHPの記法に変換されているので、最後にviewヘルパーで渡したデータ配列をextract
関数で展開してキャッシュファイルに適用しレンダリングが終了となります。
以上がビュー作成の大まかな流れになります。
おまけ
Laravelのソースを眺めていてパイプラインの処理が面白かったので紹介します。
Laravelではミドルウェアの処理をパイプラインを使って実装しています。以下がミドルウェアを通してルーターにリクエストを渡している処理を抜粋したものです。
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
send()->through()->then()
とメソッドチェーンで処理が繋がっていて、まさにパイプラインのような形になっています。では、Pipelineの実装を見てみましょう。
public function send($passable)
{
$this->passable = $passable;
return $this;
}
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes),
$this->carry(),
$this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
send
メソッドでリクエストをpassable
という変数に格納し、through
メソッドでミドルウェアの配列をpipes
という変数に格納しています。そして、then
メソッドではarray_reduceを使って反転したミドルウェアの配列の各要素に対してcarry()
というコールバック関数を適用し、返ってきたクロージャにリクエストを渡しています。prepareDestination()
は処理の最初で使用されますので、以下の順番でコールバック関数が適用されることになります。
prepareDestination() -> middleware3 -> middleware2 -> middleware1
prepareDestinationの実装は以下のように、リクエストをdispatchToRouter()
に受け渡すクロージャを返すようになっています。
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
return $destination($passable);
};
}
carryの実装は以下のようになっています。
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
}
// $this->method = 'handle'
return method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
};
};
}
$stack
に前回の反復処理の結果、$pipe
に現在の反復処理の値が入りますので、以下のような順番になります。
実行順 | $stackに入る値 | $pipeに入る値 |
---|---|---|
① | prepareDestination()のリターン | middleware3 |
② | ①のリターン(クロージャ) | middleware2 |
③ | ②のリターン(クロージャ) | middleware1 |
従ってリクエストの処理は③⇒②⇒①の順番で行われることになります。まず、$pipe
がmiddleware1になりますので1番目と2番目のif文はパスして$parameters = [$passable, $stack];
でパラメータにリクエストと②のリターンが格納されて、middleware1のhandleメソッドが実行されます。ミドルウェアの実装が以下のようになりますので、前処理が入った後に$next
つまり②のリターンにリクエストが渡されます。
class SampleMiddleware
{
public function handle($request, Closure $next)
{
/* 何か前処理が入る */
$response = $next($request);
/* 何か後処理が入る */
return $response;
}
}
そうすると今度は$stack
に①のリターン、$pipe
にmiddleware2が入りますので、またmiddleware2のhandleメソッドが実行されます。middleware2の前処理が終わると①のリターンにリクエストが渡り$stack
にprepareDestination()のリターン、$pipe
にmiddleware3が入ります。また、middleware3のhandleメソッドが実行され、middleware3の前処理が終わるとprepareDestination()のリターンにリクエストが渡ります。prepareDestination()のリターンはルーターにリクエストを渡すクロージャなので、つまりコントローラにリクエスト渡り、レスポンスが返って来ます。その後は、middleware3の後処理、middleware2の後処理、middleware1の後処理が行われてクライアントにミドルウェアの処理を通したレスポンスが返されることになります。
middleware1 -> middleware2 -> middleware3 -> コントローラ -> middleware3 -> middleware2 -> middleware1
send()->through()->then()
の流れが個人的に好きだったので取り上げてみました
まとめ
タイトルにDeep Diveと書きましたが、一連の流れを書きたかったのでそこまで深く書けなかったかもしれません。期待した方ゴメンナサイ
機会があれば個別の機能ごとにDeep Diveしてみたいと思います。皆さんも興味があればDeep Diveしてみてください。きっと今まで知らなかったLaravelの機能やPHPの関数、随所にちりばめられたテクニックの数々を発見できると思います。
(内容に間違いなどありましたらコメント・編集リクエストを頂けると幸いです)