さて。本丸のrun()メソッド。
多分、ここからが長丁場な予感(笑
vendor/slim/slim/Slim/App.php
public function run($silent = false) { $response = $this->container->get('response'); try { ob_start(); $response = $this->process($this->container->get('request'), $response); } catch (InvalidMethodException $e) { $response = $this->processInvalidMethod($e->getRequest(), $response); } finally { $output = ob_get_clean(); } if (!empty($output) && $response->getBody()->isWritable()) { $outputBuffering = $this->container->get('settings')['outputBuffering']; if ($outputBuffering === 'prepend') { // prepend output buffer content $body = new Http\Body(fopen('php://temp', 'r+')); $body->write($output . $response->getBody()); $response = $response->withBody($body); } elseif ($outputBuffering === 'append') { // append output buffer content $response->getBody()->write($output); } } $response = $this->finalize($response); if (!$silent) { $this->respond($response); } return $response; }
あ、シンプル。
………引数の$silentが気になるなぁ。まぁ置いといて。
ふむ………基本的には
・$this->process
・$output = ob_get_clean();
・$response = $response->withBody($body); または $response->getBody()->write($output);
・$response = $this->finalize($response);
・$this->respond($response);
こんな感じか。
・$response->getBody()->isWritable()
・$this->container->get('settings')['outputBuffering'];
あたりが個別に気になるなぁ。………なんとなく、初手の「$response = $this->container->get('response');」あたりが気がかりになるんだけど(このタイミングではfalseとかじゃないんだろうか?)。
まぁ。順繰りに追いかけてみませう。
まずは「$this->process」の解析が先決かな。
大まかには「process(request, response)」なので、大体予想がつきそうな処理ではある、し、元々「一番見たかった」場所でもありそうだ。
public function process(ServerRequestInterface $request, ResponseInterface $response) { // Ensure basePath is set $router = $this->container->get('router');
引数でちゃんと型固定、ふむふむ好み。
次、routerインスタンスの取り出し。
if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) { $router->setBasePath($request->getUri()->getBasePath()); }
ふむ………少し理解不能。
まず「$request->getUri()」をvar_dump()。
object(Slim\Http\Uri)#18 (9) {
["scheme":protected]=>
string(4) "http"
["user":protected]=>
string(0) ""
["password":protected]=>
string(0) ""
["host":protected]=>
string(12) "domain.com"
["port":protected]=>
int(8080)
["basePath":protected]=>
string(0) ""
["path":protected]=>
string(11) "/hello/furu"
["query":protected]=>
string(0) ""
["fragment":protected]=>
string(0) ""
}
あぁうん、普通にURIの情報か。
ふむ………少し一足飛びに「ifの中の二つのis_callableのreturnを確認。
bool(true)
bool(true)
trueか………少し細かくほじくってみよう………あぁそうか単純に「[0]のインスタンスに[1]のメソッドがあるか」の確認か。
method_existsと何が違うんだろ………あ、そか。念のため実験。
<?php class hoge { public function aaa() {} protected function bbb() {} } $obj = new hoge(); var_dump( method_exists($obj, 'aaa') ); var_dump( method_exists($obj, 'bbb') ); var_dump( is_callable([$obj, 'aaa']) ); var_dump( is_callable([$obj, 'bbb']) );
bool(true)
bool(true)
bool(true)
bool(false)
ふむ予想通り。
method_existsは「メソッド存在の有無」、is_callableは「呼べるメソッドか?」なのか。
はぁこの辺、作りが丁寧だなぁ。少し見習いましょう。
話を戻して。
if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) { $router->setBasePath($request->getUri()->getBasePath()); }
はおおざっぱに
・RouterインスタンスにBasePath情報をセット
してるのか………ここがfalseだったらどうすんだべ? とか思うんだけど、まぁ気にせず。
念のため、Routerのほうを軽く確認。
vendor/slim/slim/Slim/Router.php
public function setBasePath($basePath) { if (!is_string($basePath)) { throw new InvalidArgumentException('Router basePath must be a string'); } $this->basePath = $basePath; return $this; }
はいありがとうございます予想どおり(笑
念のため値を確認。
string(0) ""
………あら? ちょいと予想からはずれた。
気になるというよりは興味があるので、軽く探ってみませう。
vendor/slim/slim/Slim/Http/Request.php
public function getUri() { return $this->uri; }
シンプル。
ここで返ってくるのが「Slim\Http\Uri」だったので。
vendor/slim/slim/Slim/Http/Uri.php
public function getBasePath() { return $this->basePath; }
あら、まんま。
grep -R setBasePath vendor/slim/slim/Slim/*
grep -R setBasePath *
で捜索しても、あんまり出てこない………なんとなく「ドメイン直下じゃなくて、どこかディレクトリ掘ってWebアプリケーションを置く」ような時に使う、全体用の子、っぽくみえるなぁ。
まぁ、幾分気になるものの*1、一旦、次に進めてみましょう。
vendor/slim/slim/Slim/App.php
// Dispatch the Router first if the setting for this is on if ($this->container->get('settings')['determineRouteBeforeAppMiddleware'] === true) { // Dispatch router (note: you won't be able to alter routes after this) $request = $this->dispatchRouterAndPrepareRoute($request, $router); }
多分ここが本命中の本命だろうなぁ。
……まず「settings」ってなんじゃらほい?
object(Slim\Collection)#20 (1) { ["data":protected]=> array(7) { ["httpVersion"]=> string(3) "1.1" ["responseChunkSize"]=> int(4096) ["outputBuffering"]=> string(6) "append" ["determineRouteBeforeAppMiddleware"]=> bool(false) ["displayErrorDetails"]=> bool(false) ["addContentLengthHeader"]=> bool(true) ["routerCacheFile"]=> bool(false) } }
あぁなんか「基本的な設定が入ってる」ぽいなぁ。
vendor/slim/slim/Slim/DefaultServicesProvider.php
をチェック……あれ? newしてる箇所がない。
vendor/slim/slim/Slim/Container.php
public function __construct(array $values = []) { parent::__construct($values); $userSettings = isset($values['settings']) ? $values['settings'] : []; $this->registerDefaultServices($userSettings); }
ふむ「指定されてたらそれを、指定されてなかったら空の配列を」………
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); }
あぁ「defaultSettingsとuserSettingsをいい感じに足しっぱなしなCollectionを作る」のかなるほど。
array_mergeだから「上書き系」なので「defaultSettingsに値があってもuserSettingsにも設定があったらuserSettingsが優先になる」んだねぇ、というあたりを読み取っていくのが基本w
ついでだから defaultSetting についても軽く覗いてみよう。
private $defaultSettings = [ 'httpVersion' => '1.1', 'responseChunkSize' => 4096, 'outputBuffering' => 'append', 'determineRouteBeforeAppMiddleware' => false, 'displayErrorDetails' => false, 'addContentLengthHeader' => true, 'routerCacheFile' => false, ];
………ふぁ?
あぁそうか「デフォルト」だからいいのか。これ、上書きできないw
てっきりsetDefaultSettings的なメソッドを期待していたんだけど、それやりたいなら「userSettingsで指定しろよ」って話だから不要なのか。理解。
デフォルトの挙動を変えたい場合は
・Containerクラスをnewする時、引数に$values['settings']のhash配列として値をいれてやる
・このContainerクラスをAppのnewん時に渡してやる
………ふむ、前に書いてた「Appを一旦デフォでnewしてから後で上書き」と少しずれるなぁ。
やり口としては
・Appをデフォでnew
・その後「$app->container->get('settings')['hoge'] = 'fuga';」とか
のほうが楽かもしらん…… slim_test/src/settings.php になんかあるし、そっち見て詳しいところは把握するんだろうなぁ。
あと。Collectionクラス、なんか見なくてもよさそうだけど、一応。
vendor/slim/slim/Slim/Collection.php
……ほいほい。なんとなし「arrayObjectではダメなん?」とか思うんだけど、なんかあるんだろうなぁ(未確認)。
とりあえず「いわゆる配列をObjectで扱えるクラス」的な感じなので、一旦その辺で掘り込みstop。
ってなわけで、現状は放置w
改めて
vendor/slim/slim/Slim/App.php
// Dispatch the Router first if the setting for this is on if ($this->container->get('settings')['determineRouteBeforeAppMiddleware'] === true) { // Dispatch router (note: you won't be able to alter routes after this) $request = $this->dispatchRouterAndPrepareRoute($request, $router); }
………あれ? 値がfalse。
あぁ「routeの前にMiddleware アプリケーションが定められていれば」フラグ、か。
多分、Middlewareの設定があるときに使われるんだな。
んじゃ、後回しw ………
次。
try { $response = $this->callMiddlewareStack($request, $response); } catch (Exception $e) { $response = $this->handleException($e, $request, $response); } catch (Throwable $e) { $response = $this->handlePhpError($e, $request, $response); }
って事は、callMiddlewareStack が本命かな??
………むぅメソッドがない。
use MiddlewareAwareTrait;
かぁ。
findしてファイル位置特定(この辺、手抜きw)。
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; }
いたいた。
………ふむよぉ分からんなぁ。
とりあえず、直線的にいきますか。
「$this->tip」は……nullか。したらseedMiddlewareStackですな。
protected function seedMiddlewareStack(callable $kernel = null) { if (!is_null($this->tip)) { throw new RuntimeException('MiddlewareStack can only be seeded once.'); } if ($kernel === null) { $kernel = $this; } $this->tip = $kernel; }
………ふむぅ何かを入れている。っつか何かが「$this」だから、今読んでいる文脈だと「Slim\App」が入るのか。
$start = $this->tip; $this->middlewareLock = true; $response = $start($request, $response); $this->middlewareLock = false;
………ん?
ってことは「Appのインスタンスを関数のようにcallしている」??
えと………__invoke、か。
うん、Appにあったあった。
vendor/slim/slim/Slim/App.php
public function __invoke(ServerRequestInterface $request, ResponseInterface $response) { // Get the route info $routeInfo = $request->getAttribute('routeInfo'); /** @var \Slim\Interfaces\RouterInterface $router */ $router = $this->container->get('router'); // 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[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]); } if (!$this->container->has('notFoundHandler')) { throw new NotFoundException($request, $response); } /** @var callable $notFoundHandler */ $notFoundHandler = $this->container->get('notFoundHandler'); return $notFoundHandler($request, $response); }
ここが本気の本命っぽいなぁ。
ちぃと長くなったので、以降は次回w
*1:どこで設定するん? とかあるし