gallu’s blog

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

解析その3

そろそろ核心………かなぁ?w


前回出てきた、本命くさい
vendor/slim/slim/Slim/App.php
の__invoke()メソッド。

        // Get the route info
        $routeInfo = $request->getAttribute('routeInfo');

なんとない想像はできるんだけど、躊躇なくvar_dumpして確認w

NULL

………うを?
ちと先に、斜めにgetAttribute()を確認してみませう。


vendor/slim/slim/Slim/Http/Request.php

    public function getAttributes()
    {
        return $this->attributes->all();
    }
    protected $attributes;
    public function __construct(
 -中略-
        $this->attributes = new Collection();

ふむ………

    public function withAttribute($name, $value)
    {
        $clone = clone $this;
        $clone->attributes->set($name, $value);

        return $clone;
    }

とかはあるんだけど、大本に対して、いわゆる「setAttribute」的なのはないっぽいなぁ。
なんだろ?
まぁいいや。


あらためて
vendor/slim/slim/Slim/App.php

        // If router hasn't been dispatched or the URI changed then dispatch
        if (null === $routeInfo || ($routeInfo['request'] !== [$request->getMethod(), (string) $request->getUri()])) {
            $request = $this->dispatchRouterAndPrepareRoute($request, $router);
            $routeInfo = $request->getAttribute('routeInfo');
        }

ふむ……ここのif通った後の$routeInfoはどうなるんだろ?

array(4) {
[0]=>
int(1)
[1]=>
string(6) "route0"
[2]=>
array(1) {
["name"]=>
string(4) "gallu"
}
["request"]=>
array(2) {
[0]=>
string(3) "GET"
[1]=>
string(35) "http://dev.example.com:8080/hello/gallu"
}
}

あぁこっちで予想通りのが入ってきた。
………なんとなく「URIにパラメタがあるから」??
ちと実験してみよう。


public/index.php

$app = new \Slim\App;
$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name\n");

    return $response;
});
$app->get('/hoge', function (Request $request, Response $response, array $args) {
    $response->getBody()->write("Hello, hoge\n");

    return $response;
});

hoge、を追加。
……やっぱりNULLか。違うのか。
ちと、ここの深追いは一旦、止めておこう。


あとは直前のdispatchRouterAndPrepareRoute()が軽く気になるので、軽い目に閲覧。
ちと不明なところが多いので、一旦、まず体当たり。

    protected function dispatchRouterAndPrepareRoute(ServerRequestInterface $request, RouterInterface $router)
    {
        $routeInfo = $router->dispatch($request);
var_dump($routeInfo);

        if ($routeInfo[0] === Dispatcher::FOUND) {
echo "into if\n";
            $routeArguments = [];
            foreach ($routeInfo[2] as $k => $v) {
                $routeArguments[$k] = urldecode($v);
            }
var_dump($routeArguments);

            $route = $router->lookupRoute($routeInfo[1]);
            $route->prepare($request, $routeArguments);

            // add route to the request's attributes in case a middleware or handler needs access to the route
            $request = $request->withAttribute('route', $route);
        }

        $routeInfo['request'] = [$request->getMethod(), (string) $request->getUri()];
var_dump($routeInfo);
exit;

        return $request->withAttribute('routeInfo', $routeInfo);
    }

array(3) {
[0]=>
int(1)
[1]=>
string(6) "route0"
[2]=>
array(1) {
["name"]=>
string(4) "gallu"
}
}
into if
array(1) {
["name"]=>
string(4) "gallu"
}
array(4) {
[0]=>
int(1)
[1]=>
string(6) "route0"
[2]=>
array(1) {
["name"]=>
string(4) "gallu"
}
["request"]=>
array(2) {
[0]=>
string(3) "GET"
[1]=>
string(35) "http://dev.example.com:8080/hello/gallu"
}
}

ふむ……そこそこ予想込みなんだけど
・「$router->dispatch($request);」で、ヒットするrouteを取ってくる
・routeArgumentsに引数をパッキングしてる(多分、実働する関数の第三引数に使う用途じゃないかなぁ)
・$routeInfoのkey: 'request'に、メソッド名と実際のURIを付け足す
・requestのAttributeに「routeInfo」を付けたインスタンスをcloseして渡す(closeしているのはwithAttribute()の中)
って感じか。


だとすると
・ヒットしないとき
・二個以上ヒットするとき
の動きがそれぞれ興味あるなぁ。実験。


public/index.php

$app = new \Slim\App;
$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name\n");

    return $response;
});
$app->get('/hoge', function (Request $request, Response $response, array $args) {
    $response->getBody()->write("Hello, hoge\n");

    return $response;
});
$app->get('/hoge', function (Request $request, Response $response, array $args) {
    $response->getBody()->write("Hello, hoge2\n");

    return $response;
});

まずは重複パターン。
「Slim Application Error」ををすげぇがっつりエラーw

Message: Cannot register two routes matching "/hoge" for method "GET"
File: /home/gallu/slim_test_plain/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php
Line: 86
Trace: #0 /home/gallu/slim_test_plain/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php(30): FastRoute\DataGenerator\RegexBasedAbstract->addStaticRoute('GET', Array, 'route2')
#1 /home/gallu/slim_test_plain/vendor/nikic/fast-route/src/RouteCollector.php(44): FastRoute\DataGenerator\RegexBasedAbstract->addRoute('GET', Array, 'route2')
#2 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/Router.php(227): FastRoute\RouteCollector->addRoute(Array, '/hoge', 'route2')
#3 /home/gallu/slim_test_plain/vendor/nikic/fast-route/src/functions.php(25): Slim\Router->Slim\{closure}(Object(FastRoute\RouteCollector))
#4 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/Router.php(238): FastRoute\simpleDispatcher(Object(Closure), Array)
#5 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/Router.php(191): Slim\Router->createDispatcher()
#6 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/App.php(582): Slim\Router->dispatch(Object(Slim\Http\Request))
#7 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/App.php(506): Slim\App->dispatchRouterAndPrepareRoute(Object(Slim\Http\Request), Object(Slim\Router))
#8 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/MiddlewareAwareTrait.php(117): Slim\App->__invoke(Object(Slim\Http\Request), Object(Slim\Http\Response))
#9 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/App.php(405): Slim\App->callMiddlewareStack(Object(Slim\Http\Request), Object(Slim\Http\Response))
#10 /home/gallu/slim_test_plain/vendor/slim/slim/Slim/App.php(314): Slim\App->process(Object(Slim\Http\Request), Object(Slim\Http\Response))
#11 /home/gallu/slim_test_plain/public/index.php(24): Slim\App->run()
#12 {main}
View in rendered output by enabling the "displayErrorDetails" setting.

ふむ
vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php

    private function addStaticRoute($httpMethod, $routeData, $handler)
    {
        $routeStr = $routeData[0];

        if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
            throw new BadRouteException(sprintf(
                'Cannot register two routes matching "%s" for method "%s"',
                $routeStr, $httpMethod
            ));
        }

ここか。
とりあえずガードされている事がわかったので安心。


なかった場合。

array(1) {
[0]=>
int(0)
}
array(2) {
[0]=>
int(0)
["request"]=>
array(2) {
[0]=>
string(3) "GET"
[1]=>
string(28) "http://dev.example.com:8080/foo"
}
}

うんrouteが空のままなのね。了解。


んで、改めて次。
vendor/slim/slim/Slim/App.php
__invoke()の

        if ($routeInfo[0] === Dispatcher::FOUND) {
            $route = $router->lookupRoute($routeInfo[1]);
            return $route->run($request, $response);
        } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
            if (!$this->container->has('notAllowedHandler')) {
                throw new MethodNotAllowedException($request, $response, $routeInfo[1]);
            }
            /** @var callable $notAllowedHandler */
            $notAllowedHandler = $this->container->get('notAllowedHandler');
            return $notAllowedHandler($request, $response, $routeInfo[1]);
        }

この辺。
先に気になったんでDispatcher::FOUNDを確認

int(1)

ほぉ。
なんとなし「isset($routeInfo[1])」でもいいんじゃね? とか思わなくもないけど、まぁ気にしない。………ダメか。「あるかないか」以外のstatusもあるぽいし。
気になったのが「Dispatcher::METHOD_NOT_ALLOWED」。
ちなみに値そのものは「int(2)」。
methodだしなぁ、で、可能性。


public/index.php

$app = new \Slim\App;
$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name\n");

    return $response;
});
$app->post('/hoge', function (Request $request, Response $response, array $args) {
    $response->getBody()->write("Hello, hoge\n");

    return $response;
});

vendor/slim/slim/Slim/App.php

        if ($routeInfo[0] === Dispatcher::FOUND) {
            $route = $router->lookupRoute($routeInfo[1]);
            return $route->run($request, $response);
        } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
echo "Trap 1\n";
            if (!$this->container->has('notAllowedHandler')) {
echo "Trap 2\n";
exit;
                throw new MethodNotAllowedException($request, $response, $routeInfo[1]);
            }
            /** @var callable $notAllowedHandler */
            $notAllowedHandler = $this->container->get('notAllowedHandler');
echo "Trap 3\n";
var_dump($notAllowedHandler);
exit;
            return $notAllowedHandler($request, $response, $routeInfo[1]);
        }

Trap 1
Trap 3
object(Slim\Handlers\NotAllowed)#40 (1) {
["knownContentTypes":protected]=>
array(4) {
[0]=>
string(16) "application/json"
[1]=>
string(15) "application/xml"
[2]=>
string(8) "text/xml"
[3]=>
string(9) "text/html"
}
}

ふむ。
・$this->container->has('notAllowedHandler')、は、デフォルトで作ってたと思うから、まぁ普通はtrue
・Slim\Handlers\NotAllowedクラスをcall
てな感じか。


軽く Slim\Handlers\NotAllowed を確認。
vendor/slim/slim/Slim/Handlers/NotAllowed.php
ふむ………renderHtmlNotAllowedMessage()にHTMLが直書きしたるのか。
まぁ普通に納得。どうにかしたきゃ
・継承してrenderHtmlNotAllowedMessage()メソッド上書き
・containerに突っ込みなおし
でいいだろうし。


ついでに
vendor/slim/slim/Slim/Handlers/NotFound.php
うん大体、同じ作りだわかりやすいwww


本題に戻して。
URIルーティング、あるけどmethodちげぇよ、な処理
は見えた。
その次。見つからないときパターンだね。

        if (!$this->container->has('notFoundHandler')) {
            throw new NotFoundException($request, $response);
        }
        /** @var callable $notFoundHandler */
        $notFoundHandler = $this->container->get('notFoundHandler');
        return $notFoundHandler($request, $response);

大まかには
・$this->container->has('notFoundHandler') ないならデフォのNotFoundException。でもまぁあるよねぇ
・コンテナにあるんならそいつ使う。 vendor/slim/slim/Slim/Handlers/NotFound.php だべさ
って感じ。


さて、全ての枝葉を切り取って、本丸中の本丸

        if ($routeInfo[0] === Dispatcher::FOUND) {
            $route = $router->lookupRoute($routeInfo[1]);
            return $route->run($request, $response);
        } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {

ここの、trueの処理。
とりあえず
$route
$route->run
が気になるので引っ張り出してみる。
$routeはまぁ当然のごとく「object(Slim\Route)」。
んでは、runメソッドを紐解いてみませう。


vendor/slim/slim/Slim/Route.php

    public function run(ServerRequestInterface $request, ResponseInterface $response)
    {
        // Finalise route now that we are about to run it
        $this->finalize();

        // Traverse middleware stack and fetch updated response
        return $this->callMiddlewareStack($request, $response);
    }

あらシンプル。

    public function finalize()
    {
        if ($this->finalized) {
            return;
        }

        $groupMiddleware = [];
        foreach ($this->getGroups() as $group) {
            $groupMiddleware = array_merge($group->getMiddleware(), $groupMiddleware);
        }

        $this->middleware = array_merge($this->middleware, $groupMiddleware);

        foreach ($this->getMiddleware() as $middleware) {
            $this->addMiddleware($middleware);
        }

        $this->finalized = true;
    }

ふむ………ということは。

var_dump( $this->finalized );
        $this->finalize();
var_dump( $this->finalized );
exit;

bool(false)
bool(true)

うん予想通り。「二重に通るのを防いでる」感じか。
初期値

    private $finalized = false;

こんなんだしね。


middlewareは、多分今は設定してないからなぁ。

        $groupMiddleware = [];
        foreach ($this->getGroups() as $group) {
echo "groupMiddleware\n";
            $groupMiddleware = array_merge($group->getMiddleware(), $groupMiddleware);
        }

        $this->middleware = array_merge($this->middleware, $groupMiddleware);

        foreach ($this->getMiddleware() as $middleware) {
echo "addMiddleware\n";
            $this->addMiddleware($middleware);
        }
exit;

うん出力されない。
まぁここは一旦放置、で。


戻って、run()。

        // Traverse middleware stack and fetch updated response
        return $this->callMiddlewareStack($request, $response);

ここ、か。
………あら、ない。
継承元? use?

    use MiddlewareAwareTrait;


vendor/slim/slim/Slim/MiddlewareAwareTrait.php

    public function callMiddlewareStack(ServerRequestInterface $request, ResponseInterface $response)
    {
        if (is_null($this->tip)) {
            $this->seedMiddlewareStack();
        }
        /** @var callable $start */
        $start = $this->tip;
        $this->middlewareLock = true;
        $response = $start($request, $response);
        $this->middlewareLock = false;
        return $response;
    }

あったあった。
………あぁなんか見たなぁこれ。ここに戻ってくるのか。
ふむ………実験………の前に、プレ実験。

$obj = new stdClass();
$obj2 = new stdClass();

var_dump( spl_object_hash($obj), $obj );
var_dump( spl_object_hash($obj2), $obj2 );

string(32) "000000003df3aa3d0000000044a939ab"
object(stdClass)#1 (0) {
}
string(32) "000000003df3aa3e0000000044a939ab"
object(stdClass)#2 (0) {
}

悪い、とは言わんが、ちょっと「軽く一目で比較する」には、面倒だなぁ。上述だって「32文字中、31文字が一緒」とか、何の間違い探しだよw

$obj = new stdClass();
$obj2 = new stdClass();

var_dump( spl_object_id($obj), $obj );
var_dump( spl_object_id($obj2), $obj2 );

int(1)
object(stdClass)#1 (0) {
}
int(2)
object(stdClass)#2 (0) {
}

うんこっちのほうが閲覧性よいねぇ。
ただ、なんでか、spl_object_idって「(PHP 7 >= 7.2.0)」なので、ご注意のほどを。
普通に、5.3系くらいから居座ってもよさそうなもんだろうにねぇ。


で、本題に戻して。
callMiddlewareStackが「どーゆー風に(誰から、どんな$thisの文脈で)」callされてるかを確認。


vendor/slim/slim/Slim/MiddlewareAwareTrait.php

    public function callMiddlewareStack(ServerRequestInterface $request, ResponseInterface $response)
    {
var_dump( get_class($this) );
var_dump( spl_object_id($this) );

string(8) "Slim\App"
int(3)
string(10) "Slim\Route"
int(21)

まぁobject_id取るまでもなかったよねぇってのは置いといて。
・初回はApp
・次回はRoute
から呼ばれてる。何となし
・初回は「全体」から「1つのRouteを選ぶ」ために流れて
・次回は「処理の本丸を流す」ために流れる
のかなぁ?
なんで共通にしているのか、は、謎。


まぁ、心おきなく「Routeの__invoke」を見てみませう。
………さすがに長くなってきたので、次回。