実装サンプル

Naifyを最小構成で動かすためのコード例を紹介します。ブートストラップ、ルート定義、BaseController、コントローラー の順に見れば、最初の1本を組み立てられます。

最小Hello World

Hello Worldのファイル配置と公開入口を使い、プロジェクト直下の SampleAppBootstrap.php でNaifyを起動します。以下のControllerは app/Controllers にクラス名と同じ名前のPHPファイルで置きます。

// SampleAppBootstrap.php(プロジェクト直下)
use Naify\Naify;

require __DIR__ . '/libs/Naify/Naify.php';

Naify::boot();
Naify::autoload('App', __DIR__ . '/app');

$config = Naify::createConfig()
    ->setAppId('sample-app')
    ->setSystemDir(__DIR__)
    ->setPublicDir(__DIR__ . '/public')
    ->setConfigDir(__DIR__ . '/config')
    ->setBasePath($_SERVER['SCRIPT_NAME'] ?? '/');

$app = Naify::createWebApp($config);
$app->loadRoute('routes/routes.php');
$app->run();

ルートの書き方

routes/routes.php はPHPコードとして定義します。createRouteFactory() でコントローラーの名前空間を指定しておきます。

use Naify\Router\Router;

return static function (Router $router): void
{
    $to = $router->createRouteFactory('App\\Controllers\\');

    $router->any('/', $to('HelloWorldController::index'));
    $router->any('/hello/{name}', $to('HelloWorldController::index'));
    $router->any('/users', $to('UserController::index'));
    $router->any('/users/{id}', $to('UserController::detail'));
};
Naifyでは、設定ファイルもPHPで記述します。
IDEのコード補完や構文チェック、クラス・メソッドの定義への移動を活用でき、設定の内容と呼び出す処理を確認しやすくするためです。

BaseController戦略

共通処理はBaseControllerに薄く集約します。認証、共通初期化、描画、JSON出力などの入口をここに置きます。

namespace App\Controllers;

use Naify\Router\RequestContext;
use Naify\Router\RequestContextHandlerInterface;

abstract class BaseController implements RequestContextHandlerInterface
{
    protected RequestContext $context;

    public function handle(RequestContext $context, ?string $action): void
    {
        $this->context = $context;
        $method = ($action ?? 'index') . 'Action';

        $this->onActionBefore();
        $this->{$method}();
        $this->onActionAfter();
    }

    protected function onActionBefore(): void
    {
        // 認証や共通初期化を置く
    }

    protected function onActionAfter(): void
    {
        // View描画や共通出力を置く
    }

    protected function outputJson(array $payload): void
    {
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($payload, JSON_UNESCAPED_UNICODE);
    }
}
BaseControllerが神クラスにならないよう、規模や機能に応じてファイルを分けるのが望ましいです。Web用、API用、認証済み画面用など、役割ごとに薄く分けると追いやすいです。

BaseControllerは必須ではなく、共通処理のまとめやすさと見通しのよさから、この実装例で採用している構成です。アプリケーションの規模や要件に応じて、より小さな構成や役割ごとに分けた実装を選べます。

参考:小さく始める

HelloWorldコントローラー

BaseControllerで基本処理が行われるので、アプリケーション層の実装はシンプルになります。ここではURLパラメータの {name} を取得して、そのまま画面へ返します。

namespace App\Controllers;

final class HelloWorldController extends BaseController
{
    public function indexAction(): void
    {
        $name = $this->context->getUrlParam('name', 'Naify');

        echo '<h1>Hello, ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '!</h1>';
    }
}

URLパラメータを使うコントローラー

ルートで定義した {id} は RequestContext から取得できます。

namespace App\Controllers;

final class UserController extends BaseController
{
    public function indexAction(): void
    {
        $this->outputJson([
            'users' => [
                ['id' => 1, 'name' => 'Alice'],
                ['id' => 2, 'name' => 'Bob'],
            ],
        ]);
    }

    public function detailAction(): void
    {
        $id = $this->context->getUrlParam('id');

        $this->outputJson([
            'id' => $id,
            'name' => 'Alice',
        ]);
    }
}