かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

PHP 名前空間 (namespace)付きのメソッドを可変関数 (文字列) で呼び出したいハマる

PHPFastRoute を使って API のモックを作っていていました。

cf. GitHub - nikic/FastRoute: Fast request router for PHP

<?php
require 'vendor/autoload.php';

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
  $r->addRoute('GET', '/api', 'handler');
});
// Fetch method and URI from somewhere
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

// Strip query string (?foo=bar) and decode URI
if (false !== $pos = strpos($uri, '?')) {
  $uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
  case FastRoute\Dispatcher::NOT_FOUND:
    echo '404 Not Found';
    break;
  case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
    $allowedMethods = $routeInfo[1];
    echo '405 Method Not Allowed';
    break;
  case FastRoute\Dispatcher::FOUND:
    $handler = $routeInfo[1];
    $vars = $routeInfo[2];
    echo $handler($vars);
    break;
}

addRoute($method, $uri, $handler) という形でルーティングの設定をして、最後の switch文の FastRoute\Dispatcher::FOUND: 内で $handler が呼び出されるという感じです。

$handler をネームスペース付きのクラスのスタティックメソッドにしていて $handler の指定にハマってしまったのでメモ

文字列にしないとクラス変数を参照しようとしてしまう

()を付けるとそのまま呼び出されてしまうから、スタティックメソッド名を指定しようとしたら、クラス変数を探してしまってそんな値はないというエラーになってしまいました。

<?php
require_once './routes/user.php';
use MyApp\Route\User;

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
  $r->addRoute('GET', '/api/user/{id:\d+}', User::get_user);
  // -> get_user というクラス変数を参照するのでエラー
});

=> Fatal error: Uncaught Error: Undefined class constant 'get_user'
これは、まぁそうだよね。って感じ

$handler を文字列で指定する時、use で namespace を指定してもダメ

次に文字列で指定しようとした際に use をしているからよ次のようにしてみた所またしても エラー になってしまいました。

<?php
require_once './routes/user.php';
use MyApp\Route\User;

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
  $r->addRoute('GET', '/api/user/{id:\d+}', 'User::get_user');
});

=> Fatal error: Uncaught Error: Class 'User' not found in
Class User そのものが見つからない、つまり名前空間 (namespace) が解決されていのが原因っぽい

文字列をメソッドとして使用する、つまり可変関数の時は namsespace をフルで指定する必要がある

次に $hander の部分に namespace をフルで含めた指定にすると意図したとおりに動作しました。

<?php
require_once './routes/user.php';
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
  $r->addRoute('GET', '/api/user/{id:\d+}', 'MyApp\Route\User::get_user');
  // -> OK
});

これはつまり $handler は文字列を関数として呼び出しているので可変関数で、PHPの可変関数は名前空間 (namespace) をフルで指定する必要があるということのようです。

名前空間動的言語機能
完全修飾名 (クラス名に名前空間プレフィックスをつけたもの) を使う必要があります。 動的なクラス名、関数名あるいは定数名においては修飾名と完全修飾名に差はないので、 先頭のバックスラッシュはなくてもかまいません。
cf. PHP: 名前空間と動的言語機能 - Manual

所感

通ってるスクールが PHP って方針で超久々に PHP 触ってて、WordPress こテーマ作りまくってた頃書いてたはずなのに めちゃめちゃPHPのこと忘れてて、なんもわからん…ってなってました。


[参考]