gallu’s blog

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

Slim docsの解析; Routing

https://www.slimframework.com/docs/v3/objects/router.html
まごうことなき大物。
腰据えていきませう。……下手したら二分割かも。


How to create routes。
うん、ここはまぁOK。
おいちゃん的には

$app = new \Slim\App();
$app->get('/books/{id}', クラス名::clss . ':メソッド名');

しか書かないつもりだけど(ルーティングファイルに処理書くとか以下検閲削除。確認用のちょろりテストは例外)。


あ、一応。
そもそもSlim自体が「PHP5.5+」なのであんまり関係ないんだけど。
class キーワードでクラス名の解決ができる( http://php.net/manual/ja/language.oop5.basic.php )のはPHP 5.5 以降なので、念のために留意。


あとは

$app->any('/books/{id}', クラス名::clss . ':メソッド名');
$app->map(['GET', 'POST'], '/books/{id}', クラス名::clss . ':メソッド名');

辺りの書き方を把握しておけば、かなぁ。

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

の実装見れば、大体ピンとくると思う。
個人的に納得したのが、別のところに書いてあったんだけど

$app->map(['GET', 'POST', 'PUT', 'DELETE'], '/', クラス名::clss . ':メソッド名');

って書き方。あぁ確かに / って「どのメソッドで来てもいい」ってのは、たしかにあるなぁ、と。
まぁこの辺はスタンスと主義主張とお好みで、任意に。


Route callbacks。
RequestとResponseはいいよね。
Argumentsは「3番目の引数は、現在のルートの名前付きプレースホルダの値を含む連想配列です。(機械翻訳)」。
ふむ……ちょいと別件で試したいこともあるので、試してみませう。
まずは単純に「中身の確認」から。


/hoge/gallu

$app = new \Slim\App;
$app->get('/hoge/{name}', function(Request $request, Response $response, array $args) {
    ob_start();
    var_dump($args);
    $s = ob_get_clean();

    $response->getBody()->write($s);
    return $response;
});
array(1) {
  ["name"]=>
  string(5) "gallu"
}

まぁ予想通り。


次。「前方にパラメタ」だと、どうなるんだろ?

$app = new \Slim\App;
$app->get('/{name}/hoge/{age}', function(Request $request, Response $response, array $args) {
    ob_start();
    var_dump($args);
    $s = ob_get_clean();

    $response->getBody()->write($s);
    return $response;
});
array(2) {
  ["name"]=>
  string(5) "gallu"
  ["age"]=>
  string(3) "100"
}

あ、のった。ふむイケルのか。


次、ちょっとひねった確認。

$app = new \Slim\App;
$app->get('/{name}/hoge/{age}', function(Request $request, Response $response, array $args) {
    ob_start();
    var_dump($args);
    $s = ob_get_clean();
    $response->getBody()->write($s);
    return $response;
});
$app->get('/admin/hoge/{age}', function(Request $request, Response $response, array $args) {
    ob_start();
    var_dump($args);
    $s = ob_get_clean();
    $response->getBody()->write($s);
    return $response;
});

http://example.com:8080/admin/hoge/100

array(2) {
  ["name"]=>
  string(5) "admin"
  ["age"]=>
  string(3) "100"
}

ふむ。

$app = new \Slim\App;
$app->get('/admin/hoge/{age}', function(Request $request, Response $response, array $args) {
    ob_start();
    var_dump($args);
    $s = ob_get_clean();
    $response->getBody()->write($s);
    return $response;
});
$app->get('/{name}/hoge/{age}', function(Request $request, Response $response, array $args) {
    ob_start();
    var_dump($args);
    $s = ob_get_clean();
    $response->getBody()->write($s);
    return $response;
});

http://example.com:8080/admin/hoge/100

array(1) {
  ["age"]=>
  string(3) "100"
}

ふむふむ。


とりあえず「先出有効」、と。
ただ、これ、規定の挙動とも限らんだろうしなぁ。ちと、方策等は、しばらく考えておきませう*1


Writing content to the response。
「First, you can simply echo() content from the route callback.」ふへ??? そなの?

$app = new \Slim\App;
$app->get('/', function(Request $request, Response $response, array $args) {
    echo "hoge test\n";
});

あ、ほんとだ動いた。
「Second, you can return a Psr\Http\Message\ResponseInterface object.」は、うん。
とはいえ………$response->write()とかあるし、object returnのほうが、個人的には「よりよい」ように感じるなぁ。
まぁ「ちょっとしたデバッグ用と」で、覚えておきませう。
これだと「response statusとか変えた時」とか、対応が難しそうなケースもあるだろうしね。


Closure binding。
「ルートコールバックとしてClosureインスタンスを使用すると、クロージャの状態はContainerインスタンスにバインドされます。 つまり、$ thisキーワードを使用してClosure内のDIコンテナインスタンスにアクセスできます。(機械翻訳)」。
うん、これはコードでも見てた。
まぁ「Closure渡す」をしない予定なので、あんまり気にならないんだけど。
ちなみに「クラスで動かす」場合は、コンストラクタの引数にContainer instanceがわたってくるざんす。
……今度つくる「おいちゃんskeleton」には、基底クラス、書いておきますかねぇ。


Redirect helper

$app = new \Slim\App();
$app->redirect('/books', '/library', 301);

身もふたもないw
直近お仕事では使わない気がするんだけど、なんかの時に使いそうなので、ほんのりと記憶しておきませう。
「コンテンツの引っ越し込みの改修」とかで使いそうな気がするんだ。


Route strategies
ふむ……「URIに入ってくるパラメタ引数」まわりのお話か。

$c = new \Slim\Container();
$c['foundHandler'] = function() {
    return new \Slim\Handlers\Strategies\RequestResponseArgs();
};

$app = new \Slim\App($c);
$app->get('/hello/{name}', function ($request, $response, $name) {
    return $response->write($name);
});

http://example.com:8080/hello/gallu

gallu


$c = new \Slim\Container();
$c['foundHandler'] = function() {
    return new \Slim\Handlers\Strategies\RequestResponseArgs();
};

$app = new \Slim\App($c);
$app->get('/hello/{name}/{age}', function ($request, $response, $name, $age) {
    return $response->write($name . ':' . $age);
});

http://example.com:8080/hello/gallu/100

gallu:100

と。ふむ。
なんだろうなぁ「好みの問題」な気もするなぁ割と。
……多分、使わない予感w


Route placeholders。
うん散々使ってるw
ただ、この書式は今まで出てこなかったから、ここだけチェックかな。

$app->get('/users[/{id}]', function ($request, $response, $args) {
    // responds to both `/users` and `/users/123`
    // but not to `/users/`
});
$app->get('/news[/{year}[/{month}]]', function ($request, $response, $args) {
    // reponds to `/news`, `/news/2016` and `/news/2016/03`
});

この辺はまぁ「あってもなくても」な記述だね。理解。


ちょいと試したいのが、これ。

$app->get('/news[/{params:.*}]', function ($request, $response, $args) {
    $params = explode('/', $args['params']);

    // $params is an array of all the optional segments
});

ふむ。
実験。

$app = new \Slim\App;
$app->get('/news[/{params:.*}]', function ($request, $response, $args) {
    $params = explode('/', $args['params']);
    var_dump($args, $params);

    // $params is an array of all the optional segments
});

http://example.com:8080/news/

array(1) {
  ["params"]=>
  string(0) ""
}
array(1) {
  [0]=>
  string(0) ""
}

http://example.com:8080/news/hoge

array(1) {
  ["params"]=>
  string(4) "hoge"
}
array(1) {
  [0]=>
  string(4) "hoge"
}

http://example.com:8080/news/hoge/100

array(1) {
  ["params"]=>
  string(8) "hoge/100"
}
array(2) {
  [0]=>
  string(4) "hoge"
  [1]=>
  string(3) "100"
}

あぁ面白い。


Regular expression matching
で、やぱりこれもあるのか。………ってか上述の「.*」って、正規表現か。理解。
なんてのは置いといて、軽くサンプルコード。

$app = new \Slim\App();
$app->get('/users/{id:[0-9]+}', function ($request, $response, $args) {
    // Find user identified by $args['id']
});

どれくらいの正規表現が使えるのかしらん??
そもそも「正規表現あんまり使わない」から不慣れ中の不慣れ、ではあるんだけど。まぁこの辺つかうと、いろいろと便利にできそうでございます。
まぁ不慣れなので、必要になってから調べるとしましょう*2


Route names
あぁうんこれは「リダイレクトする時に名前でリダイレクトさせたい」用途だよねぇそれ以外ってあるのかしらん??

$app = new \Slim\App;
$app->get('/', function(Request $request, Response $response, array $args) {
    echo "hoge test\n";
})->setName('top');
$app->get('/hoge', function(Request $request, Response $response, array $args) {
   echo $this->get('router')->pathFor('top');
});

http://example.com:8080/hoge

/

うん、予想通り。
これ、リダイレクトのメソッドで勝手に解決できたりすると楽そうだなぁ、とか、考えてみたりもする。
実装するときに、ちょっとだけ、考えてみませう。


Route groups
いわゆるグルーピング。

$app = new \Slim\App();
$app->group('/users/{id:[0-9]+}', function () {
    $this->map(['GET', 'DELETE', 'PATCH', 'PUT'], '', function ($request, $response, $args) {
        // Find, delete, patch or replace user identified by $args['id']
    })->setName('user');
    $this->get('/reset-password', function ($request, $response, $args) {
        // Route for /users/{id:[0-9]+}/reset-password
        // Reset the password for user identified by $args['id']
    })->setName('user-password-reset');
});

「グループパターンのプレースホルダ引数は、最終的にネストされたルートで使用できるようになります。(機械翻訳)」あたりが、ポイントっちゃぁポイントかしらん。これが「使えない」だと、いろいろと暴れだすと思うが(笑


あとは、Middlewareあたりを使うのに

$app->group('', function() {
    $this->get('/billing', function ($request, $response, $args) {
        // Route for /billing
    });
    $this->get('/invoice/{id:[0-9]+}', function ($request, $response, $args) {
        // Route for /invoice/{id:[0-9]+}
    });
})->add( new SharedMiddleware() );

なんてのもあるぽいね。


ポイントは「groupsの中では、$appじゃなくて$thisが使われている」あたり。
「Note inside the group closure, $this is used instead of $app. Slim binds the closure to the application instance for you, just like it is the case with route callback binds with container instance.」だって。
この辺はまぁ「ちょろっと気を付けて」いきませう。
まぁ、groupの中で$appつかったら怒られるからすぐに気づくんだろうけど、さ(笑
この辺揃えるなら

$app->group('', function() use($app) {
    $app->get('/billing', function ($request, $response, $args) {
        // Route for /billing
    });
    $app->get('/invoice/{id:[0-9]+}', function ($request, $response, $args) {
        // Route for /invoice/{id:[0-9]+}
    });
});

って書き方を推奨しておいても、よいのかもなぁ、的な。
どっちかなぁ? 悩む。


Route middleware
「You can also attach middleware to any route or route group. Learn more.*3」ほへ。
URIなのか。
したら、別稿で、確認しますか。


Router caching
これも別URI。ただ、設定するのは「ファイル名」だけ、っぽいんだよね。
設定は「routerCacheFile」。
そのうち試してみませう。


Container Resolution
ふむ……
・container_key:method
・Class:method
・An invokable class
・container_key
あるなぁ。まぁ「メソッド名のデフォが__invokable」だったので、三番目はなんとなくわかるんだが。


さて、コード例。

$app->get('/', '\HomeController:home');
$app->get('/', \HomeController::class . ':home');

うん。なんとなし、二番目のほうが「より好み」かなぁ。


で……「container_key」。これ、たぶん「クラス名」をそのままkeyとして入れてるんだよねぇ。
「根拠lessなんだけど微妙に不安の匂いがよぎった」あたりの実装。
まぁ多分、ほとんどの場合では「特に問題ない」んだとおもうので、挙動だけ覚えておきませう。
おそらく「動かすまえに少しインスタンスに小細工したい」時に使える方法なんだろうなぁ、って思う。

$container = $app->getContainer();
$container['HomeController'] = function($c) {
    $view = $c->get("view"); // retrieve the 'view' from the container
    return new HomeController($view);
};

これで

$app->get('/', 'HomeController:home');

って呼べば、いけるはず。
まぁこの辺も「実際に必要になった」ら、検証してみませう。


Allow Slim to instantiate the controller
「Alternatively, if the class does not have an entry in the container, then Slim will pass the container’s instance to the constructor. You can construct controllers with many actions instead of an invokable class which only handles one action.」うんコード読んだ。
だから

class BaseController
{
   protected $container;
   // constructor receives container instance
   public function __construct(ContainerInterface $container) {
       $this->container = $container;
   }
}

ってのを作ると扱いやすいんだろうなぁ、って思ってる。


Using an invokable class
については、ど〜すっかなぁ、程度。

$app->get('/', \HomeAction::class);

でいけるのはメリットがあるように思われるので、「1Controllerクラスあたり1Action」でよいなら、これもありなのかもなぁ、とは思う、など。


思ったより常識的な長さで解析が終わりました………Middlewareが別URIだったしね(笑

*1:後述しますが、正規表現使うのが、多分、一番確実

*2:なんてことやってるからいつまでも不慣れなのだが

*3:https://www.slimframework.com/docs/v3/concepts/middleware.html