そろそろ核心………かなぁ?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」を見てみませう。
………さすがに長くなってきたので、次回。