gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

解析その1

一瞬「解析シリーズ」とかやってみようかしらん? とか、思ってみたりみなかったり。


おいといて。


さて、ゆるゆると解析開始。
まぁ大概のWebアプリケーション、最近は「ここから始まる」1点があるので、そこを確認。
よっぽどヒネてない限り、大概は「DocumentRoot直下のindex.php」が、その1点になります。


とりあえず流れを把握したいので。
ざっくりと public/index.php の最後に

var_dump( debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS) );

を入れてみる……うんダメだ出てこない出力されない。最近このパターン多いなぁ*1ついでに色々と解明しよう後日。いやまぁ予想はしてた。


次。

ob_start();
var_dump( debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS) );
$s = ob_get_clean();
file_put_contents(__DIR__ . '/log', $s);

………うんこれもだめ。空配列が返ってくる。やっぱり「奥地でcallしないと」全部を追えるわけじゃないのか。


一応実験(slim_test_plainにて)。

$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];

ob_start();
var_dump( debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS) );
$s = ob_get_clean();

    $response->getBody()->write("Hello, $name\n" . $s);


    return $response;
});

うん、これなら出る。………ただ、ちょっとやっぱりボリューミーだなぁ。読むのにへこたれそうだw


んじゃまぁ、横着せず、頭から読んでいきますかねぇ。
どっちでも(このレベルでは)差異はないと思われるので、 slim_test_plain を基準に一旦読み込んでみる。シンプルで読みやすいだろうし。
public/index.phpから直接読み取れるのは
・new \Slim\App;
・getメソッド
 →第三引数のcallableの引数で「Request, Response」
 →ResponseのgetBody()からのwrite()
 →Responseインスタンスをreturn
・run()メソッド


とりあえず\Slim\Appクラスの
・getメソッドを軽く眺めてから
・runメソッドを一旦追いかける
くらい、が、手掛かりの初手かなぁ。
まぁ、まずはgetメソッドをほじくってみませう。


vendor/slim/slim/Slim/App.php

    public function get($pattern, $callable)
    {
        return $this->map(['GET'], $pattern, $callable);
    }

ふむぅ。第二引数「$callable」なんだ。確か「クラス+メソッド名」って渡し方ができるはずなので。どこかで分解しているのかねぇ。
ちなみにその下に

    public function post($pattern, $callable)
    {
        return $this->map(['POST'], $pattern, $callable);
    }

とかあってPUTとかPATCH(うわぉ)とかDELETEとかOPTIONS(ほほぉ)とかあるので、処理一緒ぽ。

    public function any($pattern, $callable)
    {
        return $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $pattern, $callable);
    }

これもあってまぁわかりやすい。


本体はmapぽいので、mapを軽く閲覧。

        if ($callable instanceof Closure) {
            $callable = $callable->bindTo($this->container);
        }

あぁなるほど。無名関数形式で呼ばれたら「containerってやつのクラス」にbindするんだ。面白い。
……$this->containerってなんじゃらほい?

        $route = $this->container->get('router')->map($methods, $pattern, $callable);
        if (is_callable([$route, 'setContainer'])) {
            $route->setContainer($this->container);
        }

        if (is_callable([$route, 'setOutputBuffering'])) {
            $route->setOutputBuffering($this->container->get('settings')['outputBuffering']);
        }

        return $route;

あたりみても、$this->containerが前面っつか全面に出てる感じぽ。こいつのget()とかmap()とかあたりがkeyぽい。
ので、軽く確認。

    public function __construct($container = [])
    {
        if (is_array($container)) {
            $container = new Container($container);
        }
        if (!$container instanceof ContainerInterface) {
            throw new InvalidArgumentException('Expected a ContainerInterface');
        }
        $this->container = $container;
    }

ふむぅ……

use Psr\Container\ContainerInterface;

はあるんだけど、Containerってクラスは見当たらんなぁ。
require 'vendor/autoload.php'; との合わせ技で考えると一番高い可能性は「同じ名前空間の中にクラスがある」可能性………ほらあった。
vendor/slim/slim/Slim/Container.php


ただ、これって「Psr\Container\ContainerInterface」で予想される通り「PSR-11 Container Interface」なんだよねおそらく。
つまり「依存性注入コンテナの共通インタフェース」なので、突き詰めると「色々なインスタンスを抱え込んでるだけの子」。
いや仕組みとしてはぶっちゃけると「パクりたくなるくらい面白い」んだけど、今回の場合「そのインスタンスの中身」を知りたいので、ってことは「$this->container->get('router')」で取得できるクラスの情報が欲しいんだよね。


ちゃんと追いかけてもよいんだけど、ちょいとずるしながら追いかけてみる。
vendor/slim/slim/Slim/App.php

    public function map(array $methods, $pattern, $callable)
    {
        if ($callable instanceof Closure) {
            $callable = $callable->bindTo($this->container);
        }
var_dump( get_class($this->container->get('router')) ) ;
exit;

string(11) "Slim\Router"

うんやっぱりそこか。


軽く確認。

grep "new Router" vendor/slim/slim/Slim/*

vendor/slim/slim/Slim/DefaultServicesProvider.php: $router = (new Router)->setCacheFile($routerCacheFile);

DefaultServicesProviderですか気になるなぁ。

grep "DefaultServicesProvider" vendor/slim/slim/Slim/*

vendor/slim/slim/Slim/Container.php: $defaultProvider = new DefaultServicesProvider();
vendor/slim/slim/Slim/DefaultServicesProvider.php:class DefaultServicesProvider


vendor/slim/slim/Slim/Container.php

    private function registerDefaultServices($userSettings)
    {
        $defaultSettings = $this->defaultSettings;

        /**
         * This service MUST return an array or an
         * instance of \ArrayAccess.
         *
         * @return array|\ArrayAccess
         */
        $this['settings'] = function () use ($userSettings, $defaultSettings) {
            return new Collection(array_merge($defaultSettings, $userSettings));
        };

        $defaultProvider = new DefaultServicesProvider();
        $defaultProvider->register($this);
    }
    public function __construct(array $values = [])
    {
        parent::__construct($values);

        $userSettings = isset($values['settings']) ? $values['settings'] : [];
        $this->registerDefaultServices($userSettings);
    }

ふむ、大体つながったざっくりだけど。
あと、DefaultServicesProviderのregisterだけ、覗き見。
………長いなぁ。
要約すると「設定されていなければ、デフォルトのクラスを紐づけておく」感じ。
environment
request
response
router
foundHandler
phpErrorHandler
errorHandler
notFoundHandler
notAllowedHandler
callableResolver
概ね「newするくらい」なんだけど、クラスによっては「ちょっと色々やってる(responseとか)」ので、軽く覗いてみると面白いかも。


あと。
Appのコンストラクタに

    public function __construct($container = [])
    {
        if (is_array($container)) {
            $container = new Container($container);
        }

ってあるから「なんか追加したかったら、Appのコンストラクタに渡せ」って感じだよね。
或いは「newしたあと、動作させる前に上書き」でもよいだろうし。


上書きは、実際
../slim_test/public/index.php
経由
../slim_test/src/dependencies.php
に処理が書いてあるので、参考になるんだと思う。
「newするときにコンストラクタに渡す」のと「newした後で$app->getContainer()で受け取ったインスタンスに書き込む」のとどっちがよいのかなぁ? とは思うんだけど、まぁ「newはAppにしてもらう」ほうが、楽は楽なのやもしれぬ。


さて。
盛大に回り道をしたので、元に戻してRoute周りを改めて。
発端は
vendor/slim/slim/Slim/App.php

    public function map(array $methods, $pattern, $callable)
    {
        if ($callable instanceof Closure) {
            $callable = $callable->bindTo($this->container);
        }

        $route = $this->container->get('router')->map($methods, $pattern, $callable);
        if (is_callable([$route, 'setContainer'])) {
            $route->setContainer($this->container);
        }

        if (is_callable([$route, 'setOutputBuffering'])) {
            $route->setOutputBuffering($this->container->get('settings')['outputBuffering']);
        }

        return $route;
    }

改めて「map」についてみてみる。
vendor/slim/slim/Slim/Router.php
のmapメソッド。
色々面白いんだけどとりあえずcreateRouteに焦点。

    protected function createRoute($methods, $pattern, $callable)
    {
        $route = new Route($methods, $pattern, $callable, $this->routeGroups, $this->routeCounter);
        if (!empty($this->container)) {
            $route->setContainer($this->container);
        }

        return $route;
    }

Routeクラスか。………確かあったな。
vendor/slim/slim/Slim/Route.php

    public function __construct($methods, $pattern, $callable, $groups = [], $identifier = 0)
    {
        $this->methods  = is_string($methods) ? [$methods] : $methods;
        $this->pattern  = $pattern;
        $this->callable = $callable;
        $this->groups   = $groups;
        $this->identifier = 'route' . $identifier;
    }

はいありがとうございます身もふたもないw
まぁ、恐らく後でまたもう一度、ちゃんと読みに来ることでしょう。


っつわけでとりあえず、$appのgetとかpostとかのいわゆる「ルーティングを突っ込むところ」では
・Containerの中に色々なインスタンスが入っている中のRouterクラスで処理する
 →Containerは、デフォルトで「DefaultServicesProvider」ってクラスがいろんなインスタンスを突っ込んでおいてくれている。使っても使わなくても。
 →Containerは、Appクラスのコンストラクタで。引数にあればそれ使うし、なかったら内部で適宜作成してくれる
・Routerで色々処理をするけど、概ね「1ルーティング」が「1Routeクラスインスタンス」ん中に情報突っ込まれている


ところまでが判明しました。
さて、次回は、本番である「run()」メソッドの切込みにはいります。
………何回くらいになるのかなぁ。

*1:Responseクラスの解析あたり周りくらい、でやると思いますっていう予想