PHP で Slim を使ったルーティング
開発環境
項目 | バージョン |
---|---|
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 |
Slim Framework - Slim Framework
test_slimディレクトリへ移動して、ビルトインサーバーを起動します。cd test_slim
php -S localhost:8885 -t public
もっと簡単に、こちらのコマンドでもサーバーを起動できます。
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"
}
}
Slimのビジネスロジック(モデル)
ところでビジネスロジックは、どこでどのように実装されているのでしょうか?ルーターの記述をもう一度確認してみます。
$app->group('/users', function (Group $group) {
$group->get('', ListUsersAction::class);
$group->get('/{id}', ViewUserAction::class);
});
...
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 クラスに実装されていました。InMemoryUserRepository は UserRepository を継承しており、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;
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を選ぶのもありかなと思います。