サイトロゴ

PHP で Slim を使ったルーティング

著者画像
Toshihiko Arai

開発環境

項目 バージョン
macOS 14.2
php 8.3.2
composer 2.6.6

Slimのインストールと動作確認

下記コマンドでSlimをインストールします。

composer create-project slim/slim-skeleton test_slim 
項目 バージョン
slim/slim 4.13.0

https://www.slimframework.com/

test_slimディレクトリへ移動して、ビルトインサーバーを起動します。

cd test_slim
php -S localhost:8885 -t public

curl http://localhost:8885/ を実行すると Hello world! が返却されるはずです。

もっと簡単に、こちらのコマンドでもサーバーを起動できます。

composer start
> php -S localhost:8080 -t public

ポート番号を変えるには composer.json ファイルで以下の部分を修正します。

....
"scripts": {
    "start": "php -S localhost:8885 -t public",
    "test": "phpunit"
}

Slimを使ってみる

ルーターの設定

プロジェクトの app/routes.php をのぞくと、ルーターの設定が記述されていました。

...
return function (App $app) {
    $app->options('/{routes:.*}', function (Request $request, Response $response) {
        // CORS Pre-Flight OPTIONS Request Handler
        return $response;
    });

    $app->get('/', function (Request $request, Response $response) {
        $response->getBody()->write('Hello world!');
        return $response;
    });

    $app->group('/users', function (Group $group) {
        $group->get('', ListUsersAction::class);
        $group->get('/{id}', ViewUserAction::class);
    });
};

HTTPリクエストして動作確認

サンプルとして users APIが実装されていることが分かります。ここに記述されている通り、APIへリクエストしてみます。

$ curl http://localhost:8885/users

次のようなJSONデータが返ってきました。

{
    "statusCode": 200,
    "data": [
        {
            "id": 1,
            "username": "bill.gates",
            "firstName": "Bill",
            "lastName": "Gates"
        },
...
        {
            "id": 5,
            "username": "jack.dorsey",
            "firstName": "Jack",
            "lastName": "Dorsey"
        }
    ]
}

ユーザーIDでリクエスト

さらにこの中から、id=5 のユーザー情報をリクエストしてみます。

$ curl http://localhost:8885/users/5

{
    "statusCode": 200,
    "data": {
        "id": 5,
        "username": "jack.dorsey",
        "firstName": "Jack",
        "lastName": "Dorsey"
    }
}

id=5のユーザーだけフィルタリングされて返ってきました。見事にAPIとして機能してますね!

Slimのビジネスロジック(モデル)

ところでビジネスロジックは、どこでどのように実装されているのでしょうか?ルーターの記述をもう一度確認してみます。

$app->group('/users', function (Group $group) {
    $group->get('', ListUsersAction::class);
    $group->get('/{id}', ViewUserAction::class);
});

ListUsersAction::classViewUserAction::classにロジックが実装されている予感。PhpStormでプロジェクトを開いて、ViewUserActionクラスへ飛んでみます。

...
class ViewUserAction extends UserAction
{
    /**
     * {@inheritdoc}
     */
    protected function action(): Response
    {
        $userId = (int) $this->resolveArg('id');
        $user = $this->userRepository->findUserOfId($userId);

        $this->logger->info("User of id `${userId}` was viewed.");

        return $this->respondWithData($user);
    }
}

ビジネスロジックを探す

$this->userRepository->findUserOfId($userId); この部分で、ユーザーIDによるフィルタリングを行なっていることが分かります。

ちなみに ViewUserAction クラスは、抽象クラス Action を継承した UserAction を継承して実装されていました。Action クラスでは、HTTPリクエストやJSON処理などが実装されています。また UserAction クラスでは、DIによる UserRepository が実装されます。本来は UserRepository クラスでデータベースを扱いますが、サンプルなのでハードコーディングによるモックデータになります。 今回表示されたユーザーデータの本体は InMemoryUserRepository クラスに実装されていました。InMemoryUserRepositoryUserRepository を継承しており、public function findUserOfId(int $id): User 関数が実装されています。ロジックの正体はここにありました。

Slimの依存ライブラリ一覧

Slimをインストールすると一緒にインストールされるライブラリの一覧をまとめてみました。これらの情報は composer show を実行すると得られます。説明はChatGPT翻訳によるものなので、おかしな文章もありますがご容赦ください。

ライブラリ名 バージョン 説明
doctrine/deprecations 1.1.3 trigger_error(E_USER_DEPRECATED) または PSR-3 ロギングの上に小さなレイヤーを提供し、すべてを無効にするオプションがあります…
doctrine/instantiator 2.0.0 コンストラクタを呼び出さずに PHP でオブジェクトをインスタンス化するための小さく軽量なユーティリティ
fig/http-message-util 1.1.5 PSR-7 (psr/http-message) を使用するためのユーティリティクラスと定数
jangregor/phpstan-prophecy 1.0.0 phpspec/prophecy のための phpstan/phpstan 拡張機能を提供します
laravel/serializable-closure v1.3.3 Laravel Serializable Closure は PHP でクロージャをシリアライズするための簡単で安全な方法を提供します。
monolog/monolog 2.9.2 ログをファイル、ソケット、受信箱、データベースおよび様々なウェブサービスに送信します
myclabs/deep-copy 1.11.1 オブジェクトのディープコピー(クローン)を作成します
nikic/fast-route v1.3.0 PHPのための高速リクエストルーター
nikic/php-parser v5.0.1 PHPで書かれたPHPパーサー
phar-io/manifest 2.0.4 PHPアーカイブ(PHAR)からphar.ioマニフェスト情報を読み取るためのコンポーネント
phar-io/version 3.2.1 バージョン情報および制約を扱うためのライブラリ
php-di/invoker 2.3.4 汎用的かつ拡張可能な呼び出し可能インボーカー
php-di/php-di 6.4.0 人間のための依存性注入コンテナ
php-di/phpdoc-reader 2.2.1 PhpDocReaderはPHPドックブロック内の@varと@paramの値を解析します(名前空間付きクラス名をサポートしています)
phpdocumentor/reflection-common 2.2.0 コード構造を反映するためにphpdocumentorによって使用される共通のリフレクションクラス
phpdocumentor/reflection-docblock 5.3.0 このコンポーネントを使用すると、ライブラリはDocBlocksを介してアノテーションをサポートしたり、それ以外の方法で…
phpdocumentor/type-resolver 1.8.2 クラス名、タイプ、および構造要素名のPSR-5ベースのリゾルバー
phpspec/prophecy v1.19.0 PHP 5.3+のための非常に意見が分かれるモッキングフレームワーク
phpspec/prophecy-phpunit v2.2.0 ProphecyモッキングライブラリをPHPUnitテストケースに統合します
phpstan/extension-installer 1.3.1 PHPStan拡張機能の自動インストールのためのComposerプラグイン
phpstan/phpdoc-parser 1.26.0 Nullable、交差およびジェネリックタイプをサポートするPHPDocパーサー
phpstan/phpstan 1.10.59 PHPStan - PHP静的解析ツール
phpunit/php-code-coverage 9.2.31 PHPコードカバレッジ情報の収集、処理、およびレンダリング機能を提供するライブラリ
phpunit/php-file-iterator 3.0.6 リストの接尾辞に基づいてファイルをフィルタリングするFilterIterator実装
phpunit/php-invoker 3.1.1 タイムアウト付きでcallableを呼び出す
phpunit/php-text-template 2.0.4 シンプルなテンプレートエンジン
phpunit/php-timer 5.0.3 タイミング用のユーティリティクラス
phpunit/phpunit 9.6.17 PHPのユニットテストフレームワーク
psr/container 1.1.2 共通コンテナインターフェース (PHP FIG PSR-11)
psr/http-factory 1.0.2 PSR-7 HTTPメッセージファクトリ用の共通インターフェース
psr/http-message 1.1 HTTPメッセージ用の共通インターフェース
psr/http-server-handler 1.0.2 HTTPサーバーサイドリクエストハンドラの共通インターフェース
psr/http-server-middleware 1.0.2 HTTPサーバーサイドミドルウェアの共通インターフェース
psr/log 3.0.0 ロギングライブラリの共通インターフェース
ralouphie/getallheaders 3.0.3 getallheadersのポリフィル
sebastian/cli-parser 1.0.2 CLIオプションを解析するためのライブラリ
sebastian/code-unit 1.0.8 PHPのコードユニットを表す値オブジェクトのコレクション
sebastian/code-unit-reverse-lookup 2.0.3 コードの行がどの関数またはメソッドに属するかを調べる
sebastian/comparator 4.0.8 PHPの値を等価性で比較する機能を提供
sebastian/complexity 2.0.3 PHPのコードユニットの複雑さを計算するためのライブラリ
sebastian/diff 4.0.6 Diffの実装
sebastian/environment 5.1.5 HHVM/PHPの環境を扱う機能を提供
sebastian/exporter 4.0.6 PHP変数を視覚化するためのエクスポート機能を提供
sebastian/global-state 5.0.7 グローバルステートのスナップショット
sebastian/lines-of-code 1.0.4 PHPソースコードのコード行を数えるためのライブラリ
sebastian/object-enumerator 4.0.4 配列構造とオブジェクトグラフをトラバースして参照されているすべてのオブジェクトを列挙
sebastian/object-reflector 2.0.4 継承されたものや非公開のものを含む、オブジェクト属性のリフレクションを可能にする
sebastian/recursion-context 4.0.5 PHP変数を再帰的に処理する機能を提供
sebastian/resource-operations 3.0.3 リソースを操作するPHPの組み込み関数のリストを提供
sebastian/type 3.2.1 PHPの型システムの型を表す値オブジェクトのコレクション
sebastian/version 3.0.2 GitでホストされているPHPプロジェクトのバージョン番号を管理するのに役立つライブラリ
slim/psr7 1.6.1 厳格なPSR-7実装
slim/slim 4.13.0 Slimは、簡単かつパワフルなWebアプリケーションおよびAPIを迅速に作成するのに役立つPHPマイクロフレームワークです
squizlabs/php_codesniffer 3.9.0 PHP, JavaScript, CSSファイルをトークン化し、定義されたコーディング規約の違反を検出します
symfony/polyfill-php80 v1.29.0 いくつかのPHP 8.0+機能をより低いPHPバージョンにバックポートするSymfony polyfill
theseer/tokenizer 1.2.3 トークン化されたPHPソースコードをXMLやその他のフォーマットに変換するための小さなライブラリ
webmozart/assert 1.11.0 メソッドの入力/出力を検証するためのアサーションで、わかりやすいエラーメッセージを提供します

RESTfulでCREATE(POST)を実装する

RESTfulなAPIを作成するために、CREATEメソッドをPOSTで実装してみます。

ルーターを調整

routes.php にURIを追加します。

$app->group('/users', function (Group $group) {
    $group->get('', ListUsersAction::class);
    $group->get('/{id}', ViewUserAction::class);
    $group->post('', CreateUserAction::class); // ←追記
});

Actionの作成

CreateUserAction.php を次の内容で新規作成します。

<?php

declare(strict_types=1);

namespace App\Application\Actions\User;

use Psr\Http\Message\ResponseInterface as Response;
use App\Domain\User\User;

class CreateUserAction extends UserAction
{
    /**
     * {@inheritdoc}
     */
    protected function action(): Response
    {
        // リクエストオブジェクトからパースされたボディデータを取得
        $data = $this->request->getParsedBody();

        // ユーザーデータを元にUserオブジェクトを作成
        $user = new User(
            null,
            $data['username'],
            $data['firstname'],
            $data['lastname']
        );

        $newUser = $this->userRepository->create($user);

        $this->logger->info("Created new user.");

        return $this->respondWithData($newUser);
    }
}

リポジトリの調整

UserRepository インターフェースに、メソッドを追加します。

...
public function create(User $user): User;

UserRepository クラスを実装した InMemoryUserRepository クラスに、ロジックを実装します。

public function create(User $user): User
{
    $newId = max(array_keys($this->users)) + 1;
    $user->setId($newId);
    $this->users[$newId] = $user;
    return $user;
}

以上でCREATEの実装は終わりです。curlでPOSTリクエストしてみます。

cURLでのリクエストテスト

POSTリクエストでJSONデータを送信します。

$ curl -X POST http://localhost:8885/users -H "Content-Type: application/json" -d '{"username":"Toshihiko Arai", "firstname":"Toshihiko", "lastname":"Arai"}'

次の通りレスポンスが返ってくれば成功です!

{
    "statusCode": 200,
    "data": {
        "id": 6,
        "username": "toshihiko arai",
        "firstName": "Toshihiko",
        "lastName": "Arai"
    }
}

今回はメモリにデータを追加したので、永続保存されません。次のリクエストでは、追加したデータは消えて元に戻ってしまいます。とりあえず、Slimの使い方を把握するための試験ということで実装してみました。

まとめ

FastRouterよりは高機能で複雑、Laravelよりはシンプルで分かりやすかったです。ビューを必要としないRESTful APIを開発するにはちょうど良さそうな感じがします。Slimプロジェクトを作成すると、デフォルトでアーキテクチャが構成されていて、それに従って実装すれば管理しやすいプロジェクトになりそうです。ADR(アクションドメインレスポンダ)パターンと呼ぶのでしょうか?LaravelのMVC(モデル・ビュー・コントローラー)パターンとは少し違うので注意が必要です。Laravelに使い慣れている方なら、Lumenを選ぶのもありかなと思います。

関連記事