はじめに
Web アプリケーションの開発をするにあたっては勉強しなければならないことは多く、どう勉強すれば良いかはなかなか難しい問題です。初心者向けの解説は比較的たくさんあるのでとりあえずやってみるくらいは何とかなるものの、実戦的な開発がどうなっているかという総合的な話は実務を経験しないとわからないことが多いことでしょう。
ということで、本記事では最近流行の Docker と、そこそこ名前は見かける PHP のマイクロフレームワークの Slim Framework を使って実戦的な Web アプリの開発をしてみる(開発環境を作ってみる)こととします。実装的には、ドメイン実装としてユーザー登録、ログイン、ユーザー情報取得の3つのAPIを実装するところまでを取り扱います。また、静的解析を最大限活用してユニットテストと併用しつつ実装をしていますので、堅牢なコーディング方法の参考にでもしていただけると幸いです。
本記事の想定読者は以下の通りです。
- 独学で Web アプリを一応は作れるようになったけど、実務で通用するかわからないので参考になるものが欲しい方
- 業務で Web アプリの既存のコードをいじることはできるようになってきたけど、1から開発環境を作る方法がわからない方
- マイクロフレームワークをセットアップしてみたい方
- 静的解析を用いた開発に馴染みのない方
- 詳細な解説よりも動くコードが見たい方
詳細な解説をしないため、Web アプリケーション開発を始めたばかりの方が見る記事としては不親切だと思います。わからない単語があれば各自で調べていただければ幸いです。一方で、経験豊富な方にとっては基礎的な話が多く得られるものは少ないかもしれません。
本記事で扱う技術スタックは下記の通りです。
- Linux
- NGINX
- MySQL
- PHP-FPM
- PHP8.1
- Docker / Docker Compose
- Slim Framework
- Doctrine ORM
- PHPUnit
- 静的解析(Psalm)
いわゆる LEMP スタックがベースになっています。
本記事に沿ってアプリケーションの構築を経験することで、以下の効果が得られる想定です。
- 使用している技術に対する理解が深まる
- Web アプリケーションフレームワークが何をしているかが何となくわかる
- 何か Web アプリを作りたいと思ったときにサクッと開発を始められるようになる
- 他のフレームワークを使うときに理解が早くなる
- 型と静的解析を使った実装の仕方がわかる
注意事項がいくつかあります。
- 本記事用に作成したリポジトリについて、本番環境での使用に耐える品質は保証していません
- 本番環境での使用を想定していない部分があります(特に Dockerfile などインフラ面)
- CI/CD も取り扱っていません
- フロントエンドとバックエンドが分離されているアプリケーションを想定しており、レスポンス形式は JSON としています
- 本来は OpenAPI などの IDL からリクエスト/レスポンス型の自動生成もしたいところですが、割愛しています
- その他、分量が当初の想定より多くなったのでいろいろ割愛しています
- 一連の流れを全てなぞってみるのが厳しい場合は、アプリ側だけやるとか、Slim じゃなくて Laravel を使ってみるとか、出来る範囲で難易度を下げて、まずは何かしら完成させることを優先するのも良いです
Slim Framework について
Slim は最近のPHPにおける代表的なマイクロフレームワークであり、最初から用意されている機能が最低限しかない代わりに、小さくてシンプルで、動作が軽量という特徴があります。昨今のフレームワーク事情としてはフルスタックフレームワークの Laravel 全盛という感じはありますが、今回は Slim を選択しました。
PHPの高速化など環境の変化に伴って、実行速度が求められることの多いソーシャルゲームのバックエンドでも Laravel の採用が増えてきているので、大体の場合で Laravel で良くないか?という雰囲気は実際に割とあります。Laravel によくあった激しい互換性破壊も減って安定してきていますし、Laravel Octane の登場でパフォーマンス面の課題も改善されつつありますしね。しかしながら、デカくて分厚い Laravel と比べて薄くて小さい Slim が優れている部分は十分にあります。規模が大きく複雑な Laravel を理解するのは難しく、少なくとも Web アプリケーションフレームワークを理解するための最初の教材としては Slim の方が向いている面があります。非常に素朴で小さいフレームワークなので、自分で用意しなければならない部品が多く、その選定や構築をすることそのものがかなり勉強になります。
本記事では Slim を使ってユーザーのログインができるようになるところまでを実装しますが、きちんと開発環境を整えつつ実装していくため手順がなかなか多くなっています。これを見るとやっぱ Laravel は偉大だなという気持ちになる一方で、逆に Laravel を使うとこの辺をしっかり考えないで使えてしまって学習機会が減ることも実感します。また、出来上がりを見ると Slim によってアプリが非常にシンプルに構成されていることに気づくはずで、Slim の良さも感じていただけると思います。
リポジトリ
下記でサンプル実装を公開しています。
https://github.com/infiniteloop-inc/slim-sample-project
補足として、上記リポジトリは作業順をわかりやすくするためにコミット履歴を過剰にきれいに整えてあります。実際の開発ではここまで無駄のない履歴になることは通常ありません。
セットアップ手順
概ね以下の順番で作業すると良いと思われますが、厳密に守る必要はありません。前述のリポジトリのコミット履歴は概ね下記手順に従って作業しています。
1. 開発環境を構築する
何はともあれまずは開発環境を作らないと始まりません。今回は Docker (Docker Compose) を使います。前述の通り LEMP で構成し、とりあえずは API リクエストを処理できるところまでを目指します。
Docker 周りの設定をする
ホストの環境を汚さないためにも、最初に手を付けるのが Docker 周りの設定です。Docker に不慣れな人は PHP-FPM を使わずに PHP のビルトインサーバーを使って PHP 用のコンテナだけでリクエストを処理できることを確認してから進むと良いでしょう。また、Dockerfile のベストプラクティスにも目を通しておくと良いです。
ポイント
- NGINX と PHP-FPM の連携で UNIX domain socket を使用する場合は、ソケットをコンテナ間で共有して連携しますが、TCP で連携した方が楽ではあります
- Composer 自体のインストールはマルチステージングビルドを使うと楽です
フレームワーク(今回は Slim)のインストール
PHP のコンテナに Composer を導入出来たらまずはフレームワークをインストールします。公式ドキュメントに従ってセットアップするだけです。一旦 Hello, world するくらいにして他の準備を進めます。
NGINX の設定
- 公式のドキュメントを見て設定します
- NGINX 公式にサンプルがあります
- いつのまにか Slim のドキュメントにも追加されていた模様
- ここまでで index にアクセスしてレスポンスが返ってくるようになればOK
2. psalm, phpcs のインストール
出来る限り早い段階で静的解析とコードフォーマッターを入れると良いです。静的解析ツールに馴染みがない人は少なくないかもしれませんが、手軽にバグを減らせるという点で導入効果が高いので、少なくとも新規開発においては導入すると非常にお得なツールです。ただし、ディレクトリが出揃ってない段階で導入すると、あとでディレクトリを追加した際に設定を忘れることがあるので、ここまで早く導入しなくても良いかもしれません。本格的に実装を始める前に導入してあれば大丈夫です。
- 導入は公式ドキュメントを見て行います
- 静的解析は psalm でも phpstan でもどちらでも良いです(大差はなくて、多少の得手不得手がある程度)
- psalm のエラー検出レベルはデフォルトの2が現実的なラインではないかと思われます
- レベル1にできるならした方が良いですが、mixed の検査が非常に厳しいので設計的に難しそうなら2でも十分に恩恵を享受できます
- phpcs はこだわりがなければ `–standard=PSR12` で別に困らないと思います
- 1行の長さについての警告が邪魔だったので `–exclude=Generic.Files.LineLength` の指定も追加していますが、実運用では適切な長さに設定したほうが良いかもしれません
- git の hook の pre-commit や pre-push で実行するように設定するのも選択肢に入ります
- 時間がかかるようになるとCIで実行するように移行していくのがお決まりのパターン
- このタイミングで PHPUnit を導入してもいいです
Makefile の用意
実際の開発において、よく使う長ったらしいコマンドというのは必ずあるものです。各自で alias で対応しても良いですが、コマンドそのものも暗黙知になりがちなのでいい感じに共有できるものが欲しいところです。言語や環境にあまり依存しないものが好まれるため、Makefile やシェルスクリプトがよく使われる印象です。Makefile は実行時に任意の引数を取れないのが非常に残念ですが、取り回しで煩雑な部分が少ないのが良いです。もちろん、何を使うかは好みで良いところなので、シェルスクリプトでも Ruby でも Python でも好きなものを使って大丈夫です。
3. フレームワークのセットアップ
phpdotenv のインストール
The Twelve-Factor App の Config の項目で紹介されているように、アプリのパラメータの取り扱いについては環境変数を使用することが推奨されています。環境変数を扱うライブラリとしては dotenv 系のライブラリがよく使われており、PHP の場合は vlucas/phpdotenv が該当します。
- バージョンによりAPIが変わったりしているので公式ドキュメントを見て使うことをお勧めします
- putenv および getenv に罠があるので注意
PHP-DI のインストール
昨今の開発では DI フレームワークは是非導入したいものになっています。導入しておけば疎結合な設計をしやすくなって、テストもしやすくなります。
- DI Container は PHP-DI が安牌かなと思われます
- Slim では下記のライブラリが PHP-DI 側のドキュメントで推されています
DIコンテナのセットアップコードを作成
DIの設定部分はコード量が多くなっていくので別ファイルに切り出します。この辺は Slim の公式ドキュメントを参考にしつつ作業します。最初に追加する設定としては、とりあえずロガーを導入すると良いでしょう。ロガーとしては Monolog がデファクトスタンダードです。
エラーハンドラの設定
ロガーを準備したらエラーハンドラを用意します。エラーハンドラは Slim 公式ドキュメントのものを丸っとコピーしてきつつ改造して使うと良いです。
4. セッションの実装
この辺からログイン実装の準備をしていきますが、セッションとDBのどちらを先に実装するかは好みです。
Redis 周りの実装
一定以上の規模のアプリケーションではセッション情報は Redis などのキャッシュサーバーに格納することが多いですが、ちゃんと扱うのは割と難しかったりします。少なくともコネクションをキャッシュする実装が欲しいところです。illuminate/redis や illuminate/cache の実装を参考にして自前で実装しても良いですが、これらのライブラリは(一応は)Laravel 本体から切り出されてはいるので、個別にインストールして使うこともできます。ただし、illuminate/redis は非常に残念なことに Laravel 本体への依存がわずかに残っているので、ここだけはちょっとだけいじる必要があったりします。
なお、セッションの保存に Redis を使わないで例えばローカルファイルを使うならここは省略可能です。学習用途なら省略した方が楽です。
セッションハンドラを実装
セッションもちゃんと実装するのは難しいので、今回は symfony/http-foundation を使っています。設定の参考としては以前は公式ドキュメントに詳細に載っていたのですが、刷新されて微妙になってしまったので古いドキュメントを探すか、テストや Symfony の実装を参考にする感じになるかなと。
セッションの設定が完了したら、セッションを開始するミドルウェアを実装します。動作確認はブラウザの開発者ツールでクッキーが付与されているかで確認できます。
学習用と割り切るならライブラリを使わずにPHPのリファレンスを参考にして自前で実装してみるのも良いでしょう。
5. データベースまわりの実装
いくつか前準備をしてから Doctrine を導入していきます。
PsySH
ちょっとコードを試したいというときに最適な REPL です。基本的に REPL を利用した方が開発時には便利なのですが、PHP のインタラクティブシェル(php -a)が貧弱で不便なので PsySH のようなライブラリを利用するのが良いです。
bootstrap コードにてDIコンテナにアクセスするヘルパー関数などを作っておくと便利です。
Chronos のインストール
ORM で時刻周りの実装をするのですが、PHP の組み込みの DateTime / DateTimeImmutable は少々使いにくいので、何かしらの時刻ライブラリを導入するのがおすすめです。Carbon が比較的有名ですが、Chronos の方が使いやすい部分が多いです。
- テスト用に時間を設定できる(これは Carbon でもできる)
- デフォルトの挙動が immutable
- 月またぎの計算がデフォルトで直感的な挙動をするように調整されている
Doctrine のインストール
ORM の選定ですが、一定以上の規模の開発を意識すると Eloquent は静的解析フレンドリーではない上に Active Record である点が扱いづらいため、Data Mapper スタイルである Doctrine を選択しています。Doctrine ORM は Doctrine Migrations と連携することでクラス定義とデータベースとの対応付けが簡単になり、定義が DRY になるのが嬉しいところです。以下細かい Tips。
- 公式ドキュメント通りにセットアップすればOK
- と言いたいところですが、バージョン更新で Slim のドキュメントが役に立たなくなっており、新しめのバージョンを使うなら Doctrine のドキュメントとにらめっこしながら頑張る必要があります
- Doctrine Migrations と ORM の連携はこの辺を参考にします
- Doctrine はバージョンが変わると色々とガラッと変わることが多いのでご注意ください
- 必須ではないけど便利なので NamingStrategy の設定をすることをお勧めします
- 自動で created_at や updated_at を付与する trait を作ったりすると便利です
6. PHPUnit のインストール
本格的に実装を始める前に導入します(もっと早く導入しても良い)。Slim で一番悩むのがテストで、Laravel が特に優位性を持っているポイントでもあります。ユニットテストだけなら問題ないのですが、インテグレーションテストを実施したい場合、DbUnit がもうメンテされていないので別の選択肢を探すことになります。fork された DbUnit を使うのもなくはないですが、Codeception や Pest などのテスティングフレームワークを検討するのが良いでしょう(とはいえお勧めできるものも特にない)。とりあえず今回は実装サンプルなので素直に PHPUnit を入れました。
7. 基本的なAPIの実装(ユーザー登録 / ログイン / ユーザー情報取得)
ここまで来たらあとは自由に実装して大丈夫です。一応、本リポジトリでの設計について掻い摘んで簡単に説明しておくと、 Clean Architecture 準拠のパッケージ設計を適用し、DDD のデザインパターンを使用しつつ CQRS に倣って読み取り処理はドメインオブジェクトを使わないなど手堅く作ってあります。比較的主流となっているプラクティスに沿った設計ではありますが、本リポジトリでは一部アレンジを加えてあるので、これもあくまで一例くらいの話です。ソフトウェアアーキテクチャ設計はアプリの規模や性質などに応じて検討すると良いでしょう。
動作確認については、POST のAPIの確認は curl だと面倒くさいので POSTMAN などのツールを利用するのがいいかもしれません。
最後に
ここまで一通り実装するのはなかなか時間のかかる作業になりますが、その代わりとして Web
アプリケーション開発に必要なものを一つ一つ自分で用意していくことになり、開発そのものについて理解を深めるには丁度よい題材となっています。本記事はあくまで一つの実装例でしかないですが、学習や実装に役立てていただければ幸いです。
また、弊社では PHP エンジニアの応募を絶賛お待ちしておりますので、どうぞよろしくお願いいたします。