Laravel Reading Routing

Modelで引っかかったところがあって先に読んだけどまず気になるのはやはりルーティング周りになるよね。

読んでみる

プロジェクトを作ると、app/routes.phpが作られる

初期状態だと

Route::get('/', function()
{
    return View::make('hello');
});

となっている。

Routeにgetやpostなどのメソッドが用意されてて、pathと処理を渡す感じになるんだなとわかる。

これはControllerではなく無名関数でやっちゃってる

Routeは何かなと見てみると

        'Route'           => 'Illuminate\Support\Facades\Route',

となってるのでlaravel/framework/src/illuminate/Support/Facades/Route.phpを見ると

class Route extends Facade {

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'router'; }

}

何もない・・・

これはFacadeを読むことになりそうな予感

親クラスのFacedeのgetFacadeAccessorだけoverrideして名前を返してる

こういう場合、マジックメソッドとか経由して実体のメソッドを呼ぶことになるはず。

laravel/framework/src/illuminate/Support/Facades/Facade.phpを見ると__callStaticが定義されている

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Facades/Facade.php#L198

instanceの取得はgetFacadeRoot

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Facades/Facade.php#L116

ここでサブクラスのgetFacadeAccessorを使っている

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Facades/Facade.php#L146

キャッシュとか色々しているが、結局返しているのがstatic::$app[$name]

$appって何よ?というとsetFacadeApplicationでセットしている

https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Facades/Facade.php#L188

これはどこで呼ばれとるんじゃ!

ということになるけど、laravel/framework/src/illuminate/Foundation/start.phpで呼ばれている

ではたまたこれは・・・で整理すると

public/index.php(エントリポイント)

$app = require_once __DIR__.'/../bootstrap/start.php';

bootstrap/start.php

$app = new Illuminate\Foundation\Application;
...
$framework = $app['path.base'].
                 '/vendor/laravel/framework/src';

require $framework.'/Illuminate/Foundation/start.php';

ここでrequireされてそのまま実行されている

なので、$appnew Illuminate\Foundation\Applicationそのものってことがわかる

普通のオブジェクトじゃん、でも$app[$name]って配列風のアクセス。ってことはArrayAccess Interfaceを実装しているところを見ればいい

今重要なのはoffsetGetなのでみると、$this->make($key)と単に自分のmakeというメソッドを呼び出している

makeは結構めんどくさい

最終的には解決したinstanceを返しているだけなんだけど

getConcreteでinstanceを取得しているっぽいが、中身は$this->bindingsの名前のキーを取得している

$this->bindingsに値をセットしているのはbindメソッド

bindが呼ばれるのはどこかーというとoffsetSetですよね。

これArrayAccessなので、呼び出し元は$app['hoge'] = $hogeとかで、メソッド呼び出しじゃないのでIDEを使っても追いにくいのが面倒なところ。適当にapp['router'] =で調べると出てきた

laravel/framework/src/illuminate/Routing/RoutingServiceProvider.php

 protected function registerRouter()
    {
        $this->app['router'] = $this->app->share(function($app)
        {
            $router = new Router($app['events'], $app);

            // If the current application environment is "testing", we will disable the
            // routing filters, since they can be tested independently of the routes
            // and just get in the way of our typical controller testing concerns.
            if ($app['env'] == 'testing')
            {
                $router->disableFilters();
            }

            return $router;
        });
    }

こういう感じでregisterっていうのがいろんなもので呼ばれているはずだ。

(こういうProviderとかServiceっていう言葉をどういう時にどういうものに使えばいいかいまいちわからないので1つの使い方として押さえておきたい)

とりあえずそれは後回してRouterについてだけ見ると、登録しているのは$this->app->share()にClosureを渡したもの・・・

また面倒な

Illuminate\Container\Container.phpContainerに定義されている

 public function share(Closure $closure)
    {
        return function($container) use ($closure)
        {
            // We'll simply declare a static variable within the Closures and if it has
            // not been set we will execute the given Closures to resolve this value
            // and return it back to these consumers of the method as an instance.
            static $object;

            if (is_null($object))
            {
                $object = $closure($container);
            }

            return $object;
        };
    }

これ、Closureを取ってClosureを返す関数というまた面倒な・・・ 引数を1つ取って元のClosureに適用したものを返す感じか

これどういう意味があるんだろう。わからないな

この場合やりたいことはunittestの時はfilterを無効にしたRoutingのインスタンスを返したいんだろうけど

これでやっとgetConcreteが終わり。

で、その後はbuildコンストラクタ取得してほげほげするなりそのままインスタンス化するなりしてinstanceを返している。

あとCallback登録しておくとこの後呼んでくれるらしい

こういうイベントとコールバックは今後もよく出てくる

これでやっと$app['router']が解決できた・・・

途中見落としかけたけど、結局のところ返されているのはnew Router()だ

Illuminate\Routing\Router

HTTPの各メソッド用の関数が用意されてあり、メソッド名を第1引数ともともとのurlとハンドラを渡してaddRouteを呼ぶ

ほうほう、それでそれぞれRouteを作ってRouterのメンバ変数のroutesにaddしている

さて、とりあえず登録するところまで呼んだので次にどう使われているかをある程度把握しておこうと思う

当然エントリポイントはpublic/index.php$app->run()

 public function run(SymfonyRequest $request = null)
    {
        $request = $request ?: $this['request'];

        $response = with($stack = $this->getStackedClient())->handle($request);

        $response->send();

        $stack->terminate($request, $response);
    }

with$stackに代入しつつ使うための小細工。

この後のhandleあたりがまたつらいので一旦区切る・・

ルーティングを調べるはずがほぼFacadeを読むことになってしまった。

最後にちょっと寄り道

けどFacadeって相変わらずよくわかっていないのでこれを気に寄り道してちゃんと確認しておこう

appに色々登録している箇所はこのくらいある。

Facade経由で呼び出されるものがたくさんあることがわかる。

これらをどうやって登録しているのかとかも後で読んでみる

grep -r "app\[.*\] = " .                                                                                                                                   [~/sandbox/.../vendor/laravel]
./framework/src/Illuminate/Auth/AuthManager.php:                $this->app['config']['auth.driver'] = $name;
./framework/src/Illuminate/Auth/AuthServiceProvider.php:                        $app['auth.loaded'] = true;
./framework/src/Illuminate/Cache/CacheManager.php:              $this->app['config']['cache.prefix'] = $name;
./framework/src/Illuminate/Cache/CacheManager.php:              $this->app['config']['cache.driver'] = $name;
./framework/src/Illuminate/Database/DatabaseManager.php:                $this->app['config']['database.default'] = $name;
./framework/src/Illuminate/Events/EventServiceProvider.php:             $this->app['events'] = $this->app->share(function($app)
./framework/src/Illuminate/Exception/ExceptionServiceProvider.php:              $this->app['exception'] = $this->app->share(function($app)
./framework/src/Illuminate/Exception/ExceptionServiceProvider.php:              $this->app['exception.plain'] = $this->app->share(function($app)
./framework/src/Illuminate/Exception/ExceptionServiceProvider.php:              $this->app['exception.debug'] = $this->app->share(function($app)
./framework/src/Illuminate/Exception/ExceptionServiceProvider.php:              $this->app['whoops'] = $this->app->share(function($app)
./framework/src/Illuminate/Exception/ExceptionServiceProvider.php:                      $this->app['whoops.handler'] = $this->app->share(function()
./framework/src/Illuminate/Exception/ExceptionServiceProvider.php:              $this->app['whoops.handler'] = $this->app->share(function()
./framework/src/Illuminate/Foundation/start.php:        $app['env'] = $env = $testEnvironment;
./framework/src/Illuminate/Mail/MailServiceProvider.php:                $this->app['swift.mailer'] = $this->app->share(function($app)
./framework/src/Illuminate/Mail/MailServiceProvider.php:                $this->app['swift.transport'] = $this->app->share(function($app) use ($config)
./framework/src/Illuminate/Mail/MailServiceProvider.php:                $this->app['swift.transport'] = $this->app->share(function($app) use ($config)
./framework/src/Illuminate/Mail/MailServiceProvider.php:                $this->app['swift.transport'] = $this->app->share(function()
./framework/src/Illuminate/Queue/QueueManager.php:              $this->app['config']['queue.default'] = $name;
./framework/src/Illuminate/Remote/RemoteManager.php:            $this->app['config']['remote.default'] = $name;
./framework/src/Illuminate/Routing/RoutingServiceProvider.php:          $this->app['router'] = $this->app->share(function($app)
./framework/src/Illuminate/Routing/RoutingServiceProvider.php:          $this->app['url'] = $this->app->share(function($app)
./framework/src/Illuminate/Routing/RoutingServiceProvider.php:          $this->app['redirect'] = $this->app->share(function($app)
./framework/src/Illuminate/Session/SessionManager.php:          $this->app['config']['session.driver'] = $name;
./framework/src/Illuminate/Session/SessionServiceProvider.php:                  $this->app['config']['session.driver'] = 'array';