Laravelのサービスコンテナについて内部構造を理解する(part2 コードリーディング頻出の処理編)
はじめに
N番煎じですが、自分の理解をまとめるために書き記しておきます。
laravelのコードリーディングをする際にコンテナ絡みでこのconfigとかauthってどうコンテナで登録されてるのだろうと迷子になったのでメモ。
前回
Part2の目標
laravelのコードリーディングで頻出する、app()->make("keyword")の"keyword"部分に対応する具体的なクラスはどこで登録するか調べる、例として今回はauthとconfigを調べる。
サービスコンテナとは
サービスコンテナ自体の説明は下記がとてもわかりやすいです。
authの本体
auth()->user()のauth()は何をやっているのでしょうか。
auth()の定義を調べると下記です。
vendor/laravel/framework/src/Illuminate/Foundation/helper.php
use Illuminate\Contracts\Auth\Factory as AuthFactory;
function auth($guard = null)
{
if (is_null($guard)) {
return app(AuthFactory::class);
}
return app(AuthFactory::class)->guard($guard);
}
とあるのでAuthFactory=Auth\Factoryを指定してその中身を調べればよさそうですね。
app()がApplicationを指すことがわかったので、引き続きregisterを見ていきます。
Applicationの_constructor内のregisterCoreContainerAliasesを見ます。
public function registerCoreContainerAliases()
{
foreach ([
...
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
...
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
part1で見たaliasが出てきましたね。
Containerのaliasesは下記のようになります。
aliases[\Illuminate\Auth\AuthManager::class] = "auth"
aliases[\Illuminate\Contracts\Auth\Factory::class] = "auth"
abstractAliases["auth"] = [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class]
なので\Illuminate\Contracts\Auth\Factory::class => "auth"と繋がるので後はauthを登録しているところを探しましょう。
Part1で触れたregisterをたくさんしているところから探せて、AuthServiceProviderのregisterAuthenticatorでやっています。
Illuminate\Auth\AuthServiceProvider.php
protected function registerAuthenticator()
{
$this->app->singleton('auth', fn ($app) => new AuthManager($app));
$this->app->singleton('auth.driver', fn ($app) => $app['auth']->guard());
}
なのでauth()はAuthManagerを呼んでいることがわかります。
configの本体
コードリーディングをしていると、$this->app['config']['auth.defaults.guard']みたいなコードに出くわすことがあります。
内容的にconfigファイルを読み込んでApplicationに登録していそうです。
この辺りはどう登録しているか調べましょう。
Applicationのregister系にはセットしているところはなかったので、index.phpまで戻ります。
public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
このKernel::classはIlluminate\Contracts\Http\Kernel.phpでinterfaceなので本体を探す必要があります。
bootstrap/app.php
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
となるので、先に進むためにはApp\Http\Kernel->handleを見ればいいことになります。
kernel->handleではrouting関連やmiddlewareを扱っているのですが、今回はconfigをセットしているところだけ追っていきます。
Illuminate\Contracts\Http\Kernel.phpはHttpKernelとして下記を継承していて、HttpKernelにhandleメソッドがあります。
use Illuminate\Foundation\Http\Kernel as HttpKernel
Illuminate\Foundation\Http\Kernel.php
public function handle($request)
{
...
try {
...
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
...
}
...
return $response;
}
protected function sendRequestThroughRouter($request)
{
...
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
sendRequestThroughRouterのbootstrapを見ていきます。
bootstrapの後のreturnでmiddlewareとroutingを扱っていますが今回は省略。
Illuminate\Foundation\Http\Kernel.php
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
protected function bootstrappers()
{
return $this->bootstrappers;
}
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
となっていて$bootsrappersのLoadConfigurationでconfigをロードしていそうです。
$this->appは Illuminate\Foundation\Applicationです。
なのでIlluminate\Foundation\Application->bootstrapWithを見ましょう。
Illuminate\Foundation\Application.php
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
...
$this->make($bootstrapper)->bootstrap($this);
...
}
}
となっているので、LoadConfigurationのbootstrapを見ます。
Illuminate\Foundation\Bootstrap\LoadConfiguration.php
public function bootstrap(Application $app)
{
$items = [];
...
$app->instance('config', $config = new Repository($items));
if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}
...
}
ここでapp["config"]とすれば、\Illuminate\Config\Repository::classが返ってくるようになっています。
loadConfigurationFilesでRepositoryの$itemsに値をセットしています。
Illuminate\Foundation\Bootstrap\LoadConfiguration.php
protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
{
$files = $this->getConfigurationFiles($app);
...
foreach ($files as $key => $path) {
$repository->set($key, require $path);
}
}
protected function getConfigurationFiles(Application $app)
{
$files = [];
$configPath = realpath($app->configPath());
foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) {
$directory = $this->getNestedDirectory($file, $configPath);
$files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();
}
ksort($files, SORT_NATURAL);
return $files;
}
config/以下のphpファイルの情報を取って来て、repositoryの$itemsにセットしています。
例えば、config/app.phpだったら
$repository->set("app",require config/app.phpのフルパス)
config/app.phpのフルパスはrealpathで取得し,requireしているわけですが、ファイルの先頭以外でrequireを使っていて変な感じですね。
requireの返り値は対象のファイルにreturnがあればそれを、なければ1を返します。
ここでapp.phpを見てみると下記のようにreturnがありますね。これがitemsのvalueになるわけです。
config/app.php
return [
...
'name' => env('APP_NAME', 'Laravel'),
...
];
よってRepositoryの$items["app"] = [
"name" => env('APP_NAME', 'Laravel')
]
という関係です。
実際にアクセスするときはapp["config"]['auth.defaults.guard']となるわけですが、
これはapp[config]がRepositoryで、Repository['auth.defaults.guard']となります。
クラスに対して配列みたいにアクセスしています。
これはクラスがInterfaceであるArrayAccessを実装しているからできることです。
Class["key"]とすると、Class.offsetGet("key")が呼ばれ、
Class["key"] = valueとするとClass.offsetSet(key,value)が呼ばれます。
ArrayAccessの参考
Illuminate\Config\Repository.php
public function offsetGet($key): mixed
{
return $this->get($key);
}
public function get($key, $default = null)
{
if (is_array($key)) {
return $this->getMany($key);
}
return Arr::get($this->items, $key, $default);
}
Arr::getはドット区切りでアクセスできるようになるメソッドで、
第一引数に$keyをドットで区切って順々にアクセスします。
例えばitems['auth.defaults.guard']だったら、items["auth"]["defaults"]["guard"]という感じです。
おわりに
以上でコードリーディングで頻出する際に結構困るauthとconfigの登録箇所を読みました。
Part1と比べるととても短い分量になりましたが、auth,config以外の他のところも同じように呼んでいけば見つかるはずです。
ちょくちょく読んではは処理内容忘れるので良かったら皆様もメモ代わりに使ってやってください。
Discussion