2
1

More than 3 years have passed since last update.

routes/web.phpが何をしているのかを読み解く

Last updated at Posted at 2020-05-06

まえがき

追記

輪読会にてバシバシに間違っている点指摘もらったので、5/7訂正、追記を行いました。

前提

https://qiita.com/carrotRakko/items/3e6d19a7bd9427ab6479
上記の記事を読んであることを前提に話を進めます

また、筆者はプログラミング初心者であり、前回、前々回ではsingletonやfacade,kernel等が出てきて概念というよりは用語自体に???となったレベルです

背景

上記の記事にもありますがLarevel輪読会のレジュメです
前回Illuminate\Container\Containerresolve()メソッドが呼び出されている所までたどり着きました。

今回の目標

今回はその後を追っていき、実際にweb.php

web.php
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

実際にこの処理で結果的に何が行われるかまで追えれば最高です(全く自信ありませんが)

resolve()は何をやっているのか

resolve()メソッドは以下です

vendor/laravel/framework/src/Illuminate/Container/Container.php
/**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @param  bool  $raiseEvents
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        $abstract = $this->getAlias($abstract);

        $concrete = $this->getContextualConcrete($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        if (is_null($concrete)) {
            $concrete = $this->getConcrete($abstract);
        }

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

よくわからないので上から追っていくと
$abstract = $this->getAlias($abstract);

vendor/laravel/framework/src/Illuminate/Container/Container.php
/**
     * Get the alias for an abstract if available.
     *
     * @param  string  $abstract
     * @return string
     */
    public function getAlias($abstract)
    {
        if (! isset($this->aliases[$abstract])) {
            return $abstract;
        }

        return $this->getAlias($this->aliases[$abstract]);
    }

よりaliasesは空配列なのでaliasesのキーにrouterはないのでそのまま$abstractを返します

vendor/laravel/framework/src/Illuminate/Container/Container.phpから抜粋
/**
     * Get the contextual concrete binding for the given abstract.
     *
     * @param  string  $abstract
     * @return \Closure|string|array|null
     */
    protected function getContextualConcrete($abstract)
    {
        if (! is_null($binding = $this->findInContextualBindings($abstract))) {
            return $binding;
        }

        // Next we need to see if a contextual binding might be bound under an alias of the
        // given abstract type. So, we will need to check if any aliases exist with this
        // type and then spin through them and check for contextual bindings on these.
        if (empty($this->abstractAliases[$abstract])) {
            return;
        }

        foreach ($this->abstractAliases[$abstract] as $alias) {
            if (! is_null($binding = $this->findInContextualBindings($alias))) {
                return $binding;
            }
        }
    }

今回はどこにも該当しないのでnullが帰ってきます
ここでresolve()に戻ると$needsContextualBuildfalseとなります
次のif文はすっ飛ばして$this->with[] = $parameters;に行きますがこれは
$parametersが空配列なので特に関係ないです
よって次の

if (is_null($concrete)) {
            $concrete = $this->getConcrete($abstract);
        }

が何をしてるのかを追います。getConcrete

vendor/laravel/framework/src/Illuminate/Container/Container.php
/**
     * Get the concrete type for a given abstract.
     *
     * @param  string  $abstract
     * @return mixed
     */
    protected function getConcrete($abstract)
    {
        // If we don't have a registered resolver or concrete for the type, we'll just
        // assume each type is a concrete name and will attempt to resolve it as is
        // since the container should be able to resolve concretes automatically.
        if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

        return $abstract;
    }

ここでbindings???何それってなります
なのでbindingsに何かを入れているとこを探します

bindingsとはなんぞや

前回の内容を踏まえて最初から追っていくと

bootstrap/app.php

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

ここっぽいなと思い追ってみることにしました。newしてるので__constructを追います

Illuminate\Foundation\Application
/**
     * Create a new Illuminate application instance.
     *
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();
        $this->registerBaseServiceProviders();
        $this->registerCoreContainerAliases();
    }

ここから全部をみるべきなんですが、今回router登録しているのはこれです
$this->registerBaseServiceProviders();
そして同クラス内にあります

    /**
     * Register all of the base service providers.
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

というわけでまず、newされている方を追いましょうか。router関係のものがコンストラクトされているのはこれですnew RoutingServiceProvider($this)
use Illuminate\Routing\RoutingServiceProvider;とあるので該当クラスに飛びます
registerを追いましょう。これも同クラス内にあって

Illuminate\Routing\RoutingServiceProvider.php
/**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerRouter();
        $this->registerUrlGenerator();
        $this->registerRedirector();
        $this->registerPsrRequest();
        $this->registerPsrResponse();
        $this->registerResponseFactory();
        $this->registerControllerDispatcher();
    }

今回routerを登録しているのは
$this->registerRouter();これです。同クラス内にあって

    /**
     * Register the router instance.
     *
     * @return void
     */
    protected function registerRouter()
    {
        $this->app->singleton('router', function ($app) {
            return new Router($app['events'], $app);
        });
    }

(一部ですが)bindingsの中身がわかったとこで
getConcreteに戻ると

if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

とあるので先ほど作成した連想配列の $absteact['concrete']を追加したものを返します次は

// We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

ここを追います。同クラスにありました

/**
     * Determine if the given concrete is buildable.
     *
     * @param  mixed  $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

以上のようにsingleton
ここで$concrete)はClosureのインスタンスなので
$object = $this->build($concrete);となります
よってbuildを追います
これまた、同クラス内にあって一部抜粋すると

vendor/laravel/framework/src/Illuminate/Container/Container.phpのbuild()
 // If the concrete type is actually a Closure, we will just execute it and
        // hand back the results of the functions, which allows functions to be
        // used as resolvers for more fine-tuned resolution of these objects.
        if ($concrete instanceof Closure) {
           return $concrete($this, $this->getLastParameterOverride());
        }

とあるので
$object = $concrete($this, $this->getLastParameterOverride())となります
よって今回の$concreteにはrouterという文字列が入っているので
先ほど登録したクロージャが呼ばれて
$objectにはnew Routerが返されます。
以上よりweb.phpにてRouteが呼ばれたときは
Routerのインスタンスが返されることが無事追い切れました。
次回以降は

・Illuminate\Routing\RouteServiceProvider クラスの registerRouter() メソッドで $this->app->singleton() の第2引数に渡している Closure が実行されたときの処理
・routes/web.php で Route::get(‘/’, function () { … }) が実行されたとき、すなわち Illuminate\Routing\Router クラスのシングルトンの get(‘/’, function () { … }) メソッドが実行されたときの処理

を追っていきます。

singletonって何してるの?

少し深掘りになりますがsingletonについて追ってみました
Illuminate\Foundation\Applicationを追ってみてもsingletonは無かったので
継承元のIlluminate\Container\Containerに戻ってみました。

vendor/laravel/framework/src/Illuminate/Container/Container.php
/**
     * Register a shared binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

ありました。
というわけでbindを追います

/**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);

        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

と、同クラス上にありました。とりあえずここです

// If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        if (! $concrete instanceof Closure) {

            $concrete = $this->getClosure($abstract, $concrete);
        }

これまた同クラス上にありました

    /**
     * Get the Closure to be used when building a type.
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->resolve(
                $concrete, $parameters, $raiseEvents = false
            );
        };
    }

$this->bindings[$abstract] = compact('concrete', 'shared');です。
これにより $this->bindings[$abstract]'concrete''shared'をキーと変数に持った連想配列が入っています。

後書き

PHPの知見が浅い筆者の当日の発表はボロボロでしたが、エンジニアの先輩方に教えてもらい
結果的には一通り追い切ることができました。感謝感謝です。
ここが輪読会のメリットであり面白いところなのではと感じました。
精力的にやっていきたいですね。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1