読者です 読者をやめる 読者になる 読者になる

tohokuaikiのチラシの裏

技術的ネタとか。

Confluenceのプラグイン開発を承ります。ご連絡はこちらのホームページからお願いいたします。

LaravelのメソッドのTypeHintがどうやってパラメータをうまい具合にセットしているのか?

Routingクロージャや、Controllerメソッドで

<?php
Route::get('/', function (Authenticatable $user) {

とか

<?php
public function index(Authenticatable $user)

とした時に、どうやってこの$userを良い感じで\App\Userにしてくれるのかを追ってみた。

タイプヒンティングの取り方

Reflectionを使うところから知らなかったのでまずそこから。

Reflection使ってMethodやConstructorの情報を得るのは下記のようにする。参照

<?php
class Foo {
    public function __construct(Bar $bar, Foo $foo) {
    }
    public function methodA(Bar $bar, Foo $foo){
    }
}

class Bar {
    public function __construct() {
    }
    public function methodA(Bar $bar){
    }
}

getRef('Foo');
getRef('Bar');

function getRef($class)
{
    $reflection = new ReflectionClass($class);
    $params = $reflection->getConstructor()->getParameters();
    echo "Constructor params\n";
    foreach ($params AS $param) {
        echo "    ". $param->getClass()->name . "\n";
    }
    echo "---\n";
    
    $params = $reflection->getMethod('methodA')->getParameters();
    echo "methodA params\n";
    foreach ($params AS $param) {
        echo "    ". $param->getClass()->name . "\n";
    }
    echo "---\n";
}

というので、Reflectionを使うことでMethodが何のクラスを欲しがっているかを解決できる。

Laravelでは、Illuminate/Routing/RouteDependencyResolverTrait.phpのReflectionでどうこうやってるみたい。

Routingのクロージャを使った場合

<?php
Route::get('/', function (Authenticatable $user) {

この場合、Illuminate/Routing/Route.php のrunCallable()がコールされる。

<?php
    /**
     * Run the route action and return the response.
     *
     * @return mixed
     */
    protected function runCallable()
    {
        $parameters = $this->resolveMethodDependencies(
            $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
        );

        $callable = $this->action['uses'];

        return $callable(...array_values($parameters));
    }

このresolveMethodDependenciesにより、$this->action['uses']はタダのタイプヒントからObjectになっている。

RouteDependencyResolverTrait::resolveMethodDependencies()メソッドから・・・

  1. RouteDependencyResolverTrait::resolveMethodDependencies()
  2. RouteDependencyResolverTrait::transformDependency()
  3. Illuminate/Foundation/Application::make("Illuminate\Contracts\Auth\Authenticatable")
  4. Illuminate/Container/Container.php::make("Illuminate\Contracts\Auth\Authenticatable")
  5. "Illuminate\Contracts\Auth\Authenticatable"は、"Illuminate\Auth\AuthServiceProvider"のインスタンスが当該のObjectを作ってくれると判定。このAuthServiceProviderインスタンスにこのAuthenticatableクラスのインスタンスを作らせる。(GoFのBuilderパタン)

では、どうやってAuthenticatableはAuthServiceProviderが作ってくれると判明(設定)したのか?

Illuminate/Foundation/Application の$bindingプロパティに依る。

この$bindingにこのauthの対応を登録しているのは、Illuminate/Auth/AuthServiceProvider.php

<?php
    protected function registerUserResolver()
    {
        $this->app->bind(
            AuthenticatableContract::class, function ($app) {
                return call_user_func($app['auth']->userResolver());
            }
        );
    }

になる。

ここでようやく、$app['auth']がregisterCoreContainerAliases()でヒモづけられた'Illuminate\Auth\AuthManager'であり、そのAuthManagerがconfig/auth.phpのproviders/users/modelを参照しているということになる。

ここまで抽象化されるとわからんな。。。