Laravel実践入門! シンプルなREST APIを実装して学ぶ、多機能なPHPフレームワークの使い方
LaravelはPHPで実装されたオープンソースのWebアプリケーションフレームワークです。自由度が高く、幅広いニーズをサポートし、開発支援も充実し、多くのユーザに使われています。ここではサンプルアプリケーションとしてREST APIの実装を通して、Laravelを利用した開発を体験します。
はじめまして。Webアプリケーション開発や技術サポートを行っている新原(@shin1x1)です。本記事では、これからLaravelを使ってみたい方を対象に、シンプルなREST APIの実装を通じて、Laravelを利用した開発のイメージを紹介します。フレームワークの詳細には触れていないので、実際の開発で必要な情報は公式マニュアルや書籍、ブログなどを参照してください。
- Laravelの概要と特徴
- サンプル開発に必要な情報と環境の構築
- サンプル1. Hello APIの実装
- サンプル2. To-DoアプリケーションAPI
- サンプル2-1. タスク追加APIの実装
- サンプル2-2. タスク取得APIの実装
- まとめ ─ フレームワークを学ぶ上で大切なこと
Laravelの概要と特徴
Laravelは、PHPで実装されているオープンソースのWebアプリケーションフレームワークです。Taylor Otwell(@taylorotwell)氏を中心に、コミュニティによって開発されています。MITライセンスで公開されており、個人の非商用アプリケーションから企業による商用アプリケーションまで幅広く利用できます。
フルスタックフレームワークに分類されており、下記のようなWebアプリケーションで必要となる機能を豊富に持ちます。
- ルーティング
- HTTPリクエストハンドリング
- バリデーション
- ビューやJSONによるHTTPレスポンス生成
- データストア(データベース、KVS、ファイルなど)アクセス
- メール送信
- データベースマイグレーション
- テスト支援
- 通知
- キャッシュ
- メッセージキュー連携
- 外部API連携
PHPには、Laravel以外にも多くのフレームワークがあります。筆者もその中のいくつかを利用してきましたが、現在はLaravelをメインに利用しています。Laravelを利用していく中で筆者が感じる特徴には、下記のようなものがあります。
特徴1. 自由度が高い
Laravelは、アプリケーションに求める制約が少ないフレームワークです。アプリケーションコードディレクトリや名前空間は基本的には自由に変更できます。
また、他のフレームワークではコントローラにフレームワークのクラスを継承することが前提になっているものもありますが、LaravelではPOPO(Plain Old PHP Object)やクロージャで実装できます(Eloquentのように継承が前提のものもあります)。
フレームワークのコンポーネントについてもサービスコンテナ(DIコンテナ)で構成されているので、必要があればユーザが任意のコンポーネントを実装して差し替えることも可能です。
こうした自由度の高さにより、ユーザが好むアーキテクチャを採用できるのは大きなメリットです。一般的なMVC(MVC2)だけでなく、レイヤードアーキテクチャやクリーンアーキテクチャといったいろいろな構造でLaravelを利用できます。
一方で、このような自由度の高さがゆえにプロジェクトによって構成が異なっていたり、そもそもどのような構成を取るのがよいかという判断をユーザに委ねられているという面があります。
これは、フレームワークに何を求めるかというスタンスの問題です。デフォルトの構成は用意されているので、アーキテクチャなどにこだわりがなければ、それをそのまま利用すればよいでしょう。ただ構造の変更や拡張が容易な分、独自な構成となっている場合があるということは覚えておきましょう。
特徴2. 幅広いニーズをサポート
PHPでWebアプリケーションを開発するユーザは、実に多様です。単にデータベースから値を取得して、HTMLを組み立てることだけを実現できればよい人もいるでしょう。OOPなどを駆使して、複雑なビジネスアプリケーションやWebサービスを開発している人もいます。また、同じユーザであっても、プロジェクトによって求められる内容は変わるでしょう。
Laravelでは、こうした多様なニーズを満たすようになっています。同じ機能でも、ファサードと呼ばれるクラスメソッド呼び出し、ヘルパー関数による簡便な呼び出し方法、そしてDIでコンポーネントをインジェクトして利用する方法が用意されています。内部的にはファサードやヘルパー関数も同じコンポーネントを利用するようになっているので、どちらでも実現できることは同じです。
簡潔なコードで実現したいときは簡単に、必要なコンポーネントを明確にしてフレームワークへの依存をコントロールしたい時はそのようにできるようになっており、ユーザやプロジェクトによって求められる方法でコンポーネントを利用できます。
一方、これには上述した自由度の高さと似通った面があり、同じプロジェクト内でもコードを書く人によってファサードを使ったり、DIを使ったりが分かれてしまう場合があります。もちろん現場ではコードレビューなどで方法を統一したりするなどを行うのですが、制御できない場合は実現方法が混在して混乱してしまうということがあります。
特徴3. 開発支援の充実
Laravelには、冒頭で記述したWebアプリケーションを実行するための機能だけでなく、開発支援の機能が充実しています。コード生成機能、データベースマイグレーションによるテーブルスキーマ管理やテスト(とくにファンクショナルテスト)のサポート、.env
や環境変数による設定情報管理といったものは日常的な開発、運用において強力なサポートとなるでしょう。
他にもCLIアプリケーションを実装する機能を持っており、バッチ処理やワーカー処理、自動化ツールなどをこうした機能を用いて実装できます。
こうした開発支援の機能は、Laravel以外にオープンソースで公開されているものもあるので、そちらを利用することで同様のことは実現できます。ただ、フレームワークにはじめから同梱されているおかげで、ユーザはパッケージを探したり、連携方法を探ったりすることなく、すぐにこうした機能を利用できます。
多様な機能がオールインワンでインテグレートされているのも1つの特徴でしょう。
特徴4. ユーザが多い
これはLaravel自身の特徴というより、それを取り巻く環境の話ですが、ユーザが多いというのも1つの特徴です。GitHubのスター数は60,000を超えており、これはサーバーサイドのWebアプリケーションフレームワークとしては最多です(2020年9月時点)。
多くのユーザがいるおかげで、多くのノウハウがインターネットや書籍で公開されています。また、Laravelで活用することを前提としたパッケージも多く存在するので、それらを利用することで効率的な開発が可能です。
Laravelのバージョンと選び方
現在(2020年9月時点)、Laravelは6、7、8の3つのバージョンをサポートしています。それぞれのリリースで、初期バージョンがリリースされた日から一定期間、バグフィックスやセキュリティフィックスがサポートされます。
Laravel 6はLTS(Long-term Support)と呼ばれるリリースで、長期間サポートされます。Laravel 8が最新のバージョンですが、これは通常のリリースです。それぞれのサポート期限は下表のとおりです。
バージョン | リリース日 | バグフィックス サポート期限 |
セキュリティフィックス サポート期限 |
---|---|---|---|
6 (LTS) | 2019年9月3日 | 2021年10月5日 | 2022年9月3日 |
7 | 2020年3月3日 | 2020年10月6日 | 2021年3月3日 |
8 | 2020年9月8日 | 2021年4月6日 | 2021年9月8日 |
これからLaravelを利用するのであれば、どのバージョンを使えばよいでしょうか?
現在(2020年9月時点)の状況であれば、6もしくは8のいずれかを選択することになります。どちらを選ぶかはアプリケーションの要件次第です。長期間安定したバージョンを利用したいのであれば6を、最新機能を利用していきたければ8を選ぶことになります。
この選択はさらに、セキュリティフィックス期限が切れた後にも影響します。例えば6を選択した場合、おそらく長期間利用することになるので、次にバージョンを上げる際は最新バージョンとの差異が大きくなり、アップグレードに手間がかかる可能性があります。
一方、8を選択して最新バージョンに適宜アップグレードしていけば、頻度は増えますが、都度の手間は小さくなります。ご自身やチームの開発状況や方針などを鑑みて、どちらを選ぶか検討してください。
なお、Laravelは6以降、セマンティックバージョニングを採用しているので、メジャーバージョンが変わらない限り、後方互換性が壊れることはありません。マイナーバージョンやパッチバージョンは頻繁に上がりますが、それについては基本的には最新バージョンを利用するとよいでしょう。
サンプル開発に必要な情報と環境の構築
ここから、サンプルアプリケーションの実装を通じて、Laravelを利用した開発を体験してみましょう。
近年のWebアプリケーション開発では、SPA(Single Page Application)やモバイルアプリケーションのように、ユーザが利用するUIはJavaScriptやスマートフォンアプリで実装して、PHPが担うサーバ側はAPIのみを提供するというケースが多くあります。本記事でも、WebのUIは実装せず、REST APIのみをLaravelで実装します。
なお、本記事で実装するソースコードは下記のリポジトリにあります。実装の手順を確認したり、動作イメージを知りたい場合などに活用してください。
https://github.com/shin1x1/eh-laravel-sample
必要なシステム構成
本記事のサンプルアプリケーションを動作させるには、下記のシステム構成を必要とします。
- PHP:バージョン7.3以降
- Webサーバ:nginx
- データベースサーバ:PostgreSQL
ここでは、この構成をDockerを利用して構築します。Dockerはコンテナ技術を利用して、ホストマシン(PC)の中に仮想的な実行環境を構築できます。本記事では詳細には触れませんが、開発現場ではDockerを利用した開発環境の構築が一般的になりつつあります。
PHP自体もDockerコンテナのものを利用するので、PCにPHPをインストールする必要はありません。もしPCにインストールされているPHPのバージョンが古くても問題ありません。
REST APIクライアントツール
REST APIを実装するにあたって、動作確認を行うツールが必要です。本記事では実行例として、curl
コマンドを利用します。
また、GUIのREST APIクライアントツールもいくつか公開されているので、必要であればそちらを利用してください。主なGUIツールには以下のようなものがあります。
- Insomnia Core1
- Postman
- REST Client - Visual Studio Marketplace(Visual Studio Code)
- HTTP client in PhpStorm code editor(PhpStorm)
Docker Desktopのインストール
Dockerを利用するため、Docker Desktopをインストールします。すでにDockerが利用できる環境をお持ちの方は不要です。
Docker Desktop overview | Docker Documentation
MacもしくはWindows版がありますので、ご利用の環境に合わせてダウンロード、インストールしてください。本記事ではMac環境を想定していますが、Windows環境でも基本的な流れは同じです。
Docker Desktopがインストールできたら、ターミナルを開いてdocker --version
コマンドを入力してください。下記のようにDockerのバージョンが表示されればインストールが成功しています。バージョン番号やbuildの後ろの値はインストールを行ったタイミングによって変わっている可能性はあります。
$ docker --version Docker version 19.03.12, build 48a66213fe
サンプルアプリケーションを実行するには、PHP(php-fpm)、nginx、PostgreSQLという3つのDockerコンテナを利用するので、それらを統合して管理できるようにdocker-compose
を利用します。このコマンドはDocker Desktopに同梱されているので、下記のようにdocker-compose
コマンドも実行できるか確認しておきます。
$ docker-compose version docker-compose version 1.26.2, build eefe0d31 docker-py version: 4.2.2 CPython version: 3.7.7 OpenSSL version: OpenSSL 1.1.1g 21 Apr 2020
Laravelプロジェクトの作成
サンプルアプリケーションを実装するため、Laravelプロジェクトを生成します。
ComposerのDockerコンテナが公開されているので、これを利用して次のようにcomposer create-project
コマンドを実行します。
$ docker run --rm -v `pwd`:/opt -w /opt composer:1.10 create-project laravel/laravel todoApp
コマンドを実行すると、新しいLaravelプロジェクトがtodoApp
ディレクトリに生成されます。todoApp
ディレクトリの内容を確認すると、プロジェクトのディレクトリやファイルが生成されていることが分かります。
$ tree -L 1 todoApp todoApp ├── README.md ├── app ├── artisan ├── bootstrap ├── composer.json ├── composer.lock ├── config ├── database ├── package.json ├── phpunit.xml ├── public ├── resources ├── routes ├── server.php ├── storage ├── tests ├── vendor └── webpack.mix.js
なお、本記事執筆時は最新版であるLaravel 8のプロジェクトが生成されますが、下記のようにバージョンを指定することで任意のバージョンのプロジェクトを生成することもできます。
# Laravel 6 プロジェクトを生成 $ docker run --rm -v `pwd`:/opt -w /opt composer:1.10 create-project "laravel/laravel=^6.0" todoApp6
docker-compose.ymlのダウンロード
docker-composeで環境構築するため、構成ファイルであるdocker-compose.yml
を下記URLからダウンロードして、todoApp
ディレクトリに設置してください。
https://raw.githubusercontent.com/shin1x1/eh-laravel-sample/master/docker-compose.yml
$ cd todoApp $ wget https://raw.githubusercontent.com/shin1x1/eh-laravel-sample/master/docker-compose.yml $ ls docker-compose.yml docker-compose.yml
このdocker-compose.yml
には、以下のコンテナが含まれています。
- nginx:HTTPサーバ(nginx 1.18)
- php:PHP(php-fpm 7.4)
- db:データベース(PostgreSQL 12)
- db-test:テスト用データベース(PostgreSQL 12)
- composer:Composer
Laravelを実行するためのコンテナをdocker-compose up
コマンドで起動します。初回はコンテナのダウンロードを伴うので時間がかかります。
$ docker-compose up -d Creating network "todoapp_default" with the default driver Creating todoapp_composer_1 ... done Creating todoapp_db_1 ... done Creating todoapp_db-test_1 ... done Creating todoapp_php_1 ... done Creating todoapp_nginx_1 ... done
コンテナが起動できれば、Laravelの動作環境が整いました。
ブラウザでhttp://localhost:8000
にアクセスすると、下記のようにLaravelの初期画面が表示されます。
起動中のDockerコンテナを終了・破棄する場合は、docker-compose down
コマンドを実行します。
$ docker-compose down Stopping eh-laravel-sample_nginx_1 ... done Stopping eh-laravel-sample_php_1 ... done Stopping eh-laravel-sample_db-test_1 ... done Stopping eh-laravel-sample_db_1 ... done Removing eh-laravel-sample_nginx_1 ... done Removing eh-laravel-sample_php_1 ... done Removing eh-laravel-sample_composer_1 ... done Removing eh-laravel-sample_db-test_1 ... done Removing eh-laravel-sample_db_1 ... done Removing network eh-laravel-sample_default
サンプル1. Hello APIの実装
Laravelの開発環境が整ったので、下記のようなJSONを返すだけのシンプルなAPIを実装してみましょう。
-
GET /api/hello
- レスポンス
- ステータスコード:
200
- ボディ:
{"message": "Hello"}
- ステータスコード:
- レスポンス
APIを実装するには、URIパスに対応する処理をルーティングに登録します。ルーティングはroutes
ディレクトリ以下のファイルに記述します。APIはroutes/api.php
が対象となります。このファイルに下記のコードを追加します。
routes/api.php
Route::get('/hello', function () { $message = 'Hello'; return response()->json([ 'message' => $message ]); });
Route::get()
では、GETリクエストに対するルーティングを設定します。第一引数にはURIパスを指定します。プレフィックスの/api
はフレームワークのデフォルト設定で指定されているので、ここでは/hello
のみとします。第二引数には処理を行うハンドラを指定します。ハンドラには、クロージャやクラス、クラスのメソッドなどが指定できます。上記ではクロージャを指定しています。
ハンドラの戻り値がHTTPレスポンスとなるので、ここではresponse()->json()
を指定してJSON形式のレスポンスを返すようにしています。json()
の引数ではレスポンスボディとなるJSONの内容を記述します。上記では、message
キーに対してHello
という文字列を返すようになっています。
これでAPIが実装できました。curl
コマンドで/api/hello
にGETリクエストを送信すると、Hello
がJSONで返ってきます。
$ curl http://localhost:8000/api/hello {"message":"Hello"}
Hello APIのテストを実装
Laravelには、テストを支援する仕組みがあります。これを利用して、Hello APIのテストを実装してみましょう。
テストは、tests
ディレクトリ以下に配置します。tests
ディレクトリ以下には、Feature
ディレクトリとUnit
ディレクトリがあります。前者はAPIのような機能テスト、後者はクラスやメソッドなどの単体テストを配置します。ここでは、APIのテストを記述するのでFeature
ディレクトリを利用します。
デフォルトではtests/Feature
ディレクトリとtests/Unit
ディレクトリにサンプル用のテストファイルが含まれています。これらは開発には不要なので削除しておきます。
$ rm tests/Feature/ExampleTest.php tests/Unit/ExampleTest.php
テストクラスはartisan make:test
コマンドで雛形を生成できます。下記のようにコマンドの後ろにテストクラスのクラス名を指定して実行します。
$ docker-compose exec php ./artisan make:test GetHelloTest
Test created successfully.
生成したテストクラスはtests/Feature/GetHelloTest.php
になります。このファイルにHello APIのテストコードを追加したのが、下記のコードです。
tests/Feature/GetHelloTest.php
<?php namespace Tests\Feature; use Tests\TestCase; class GetHelloTest extends TestCase { public function testExample() { $response = $this->get('/api/hello'); $response->assertStatus(200); $response->assertJson([ 'message' => 'Hello', ]); } }
ここでは、/api/hello
にGETリクエストを送信して、そのレスポンスとしてステータスコードが200
であること、JSONの内容がmessage
キーにHello
という文字列が入っていることを確認しています。
LaravelアプリケーションのテストではPHPUnitを利用しているので、phpunit
コマンドでテストが実行できます。下記のように実行するとテストが通過し、想定どおりにHello APIが動いていることが確認できます。
$ docker-compose exec php ./vendor/bin/phpunit PHPUnit 9.3.8 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:01.300, Memory: 18.00 MB OK (1 test, 2 assertions)
ここまで、Laravelに新しいAPIを追加して、テストで動作を確認するという流れを見てきました。次は、これをベースにTo-DoアプリのAPIを実装していきましょう。
サンプル2. To-DoアプリケーションAPI
ここで作成する「To-Do」アプリケーションは、ToDoリストにタスクの追加と読込を行うシンプルなアプリケーションです。これらのAPI実装を通じて、Laravelとデータベースを利用した開発を体験してみましょう。実装するAPIは下記の2つです。
- タスク追加API
POST /api/tasks
- タスク取得API
GET /api/tasks/ID
タスクは、タスクの内容を示すタスクと作成日時、更新日時を持ちます。タスクモデルは下図のとおりです。
タスクはデータベースのtasks
テーブルに格納します。
データベースとの接続設定
はじめにデータベースとの接続設定を行います。docker-compose.yml
で起動しているデータベースの接続情報を、.env
の下記の箇所を書き換えて指定します。
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=
.env
(変更後)
DB_CONNECTION=pgsql DB_HOST=db DB_PORT=5432 DB_DATABASE=app DB_USERNAME=app DB_PASSWORD=pass
なお、.env
は.gitignore
に含まれており、Gitリポジトリには含まれていません。開発環境などで共通の接続情報を利用する場合は.env.example
などに記述しておくとよいでしょう(もちろん、本番用のデータベース接続情報はGitリポジトリに含めないほうがよいので別途管理が必要です)。
接続情報が正しいかを確認するために、artisan
コマンド(Laravelアプリケーション管理コマンド)でデータベースに接続してみましょう。
下記のようにartisan migrate:status
コマンドを実行して、Migration table not found.
というメッセージが出力されれば、データベースへ接続できていることが分かります(マイグレーションテーブルは後述のマイグレーションにて生成されるので、この時点では存在しません)。
$ docker-compose exec php ./artisan migrate:status
Migration table not found.
データベースマイグレーション
Laravelには、データーベーススキーマをPHPコードで記述できるマイグレーションという仕組みがあります。
テーブルの追加や削除、カラムの変更、インデックスの追加といった変更情報をマイグレーションで管理しておくことで、開発環境と本番環境やチームメンバーのそれぞれの環境といった異なる環境においても、同じデーターベーススキーマを構築できます。
tasks
テーブルを構築するためにマイグレーションファイルを生成します。マイグレーションファイルを作成するには、artisan make:migration
コマンドを利用します。下記では、コマンドに続けてマイグレーション名を指定しています。
$ docker-compose exec php ./artisan make:migration CreateTasksTable Created Migration: 2020_09_11_020221_create_tasks_table # 2020_09_11_020221 の部分は実行したタイミングで異なります $ ls database/migrations/2020_09_11_020221_create_tasks_table.php database/migrations/2020_09_11_020221_create_tasks_table.php
コマンドを実行するとdatabase/migrations/
以下にマイグレーションファイルが生成されます。生成されたマイグレーションファイルは下記のようになっています。
database/migrations/2020_09_11_020221_create_tasks_table.php
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTasksTable extends Migration { public function up() { Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->string('name'); // (1)name カラムのコードを追加 $table->timestamps(); }); } public function down() { Schema::table('tasks', function (Blueprint $table) { $table->dropIfExists(); }); } }
CreateTasksTable
クラスには、up
とdown
という2つのメソッドが定義されています。それぞれのメソッドは下記のような役割になっています。
-
up
メソッド: テーブル追加、更新、制約の追加などを指定 -
down
メソッド: 追加したテーブルを削除するなど、up
メソッドの逆の動きを指定
up
メソッドでは、task
テーブルを追加しています。Schema::create()
はテーブルを追加するメソッドで、第一引数にテーブル名、第二引数にクロージャを指定します。第二引数のクロージャでは引数にIlluminate\Database\Schema\Blueprint
のインスタンス($table
)が与えられます。このインスタンスにはカラムや制約などを指定するメソッドが多数用意されているので、これらを組み合わせテーブルスキームを設定します。
ここでは、(1)のコードを追加しており、id
、name
、そしてtimestamps()
によってcreated_at
とupdated_at
という4つカラムを指定しています。
down
メソッドでは、up
メソッドで追加したtasks
テーブルを削除するために、$table->dropIfExists()
を実行しています。これはtasks
テーブルが存在するときのみ、テーブルを削除します。
実装したマイグレーションファイルは、artisan migrate
コマンドにてデータベースに反映します。下記のようにコマンドを実行するとtasks
テーブルが生成されます。
$ docker-compose exec php ./artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (25.80ms) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (3.50ms) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (9.89ms) Migrating: 2020_09_11_020221_create_tasks_table Migrated: 2020_09_11_020221_create_tasks_table (6.48ms)
なお、create_users_table
とcreate_password_resets_table
、create_failed_jobs_table
は、Laravelプロジェクトにはじめから含まれるマイグレーションファイルです。これらは不要であれば削除してもかまいません。本記事では影響がないのでそのまま適用しています。
データベースにアクセスしてtasks
テーブルを確認してみましょう。下記ではdocker-compose
コマンドでdbサービスに対してpsql
コマンドを実行してtasks
テーブルのスキーマを表示しています。マイグレーションファイルで定義した内容でテーブルが生成されていることが分かるでしょう。
$ docker-compose exec db psql -Uapp app -c "\d tasks" Table "public.tasks" Column | Type | Collation | Nullable | Default ------------+--------------------------------+-----------+----------+----------------------------------- id | bigint | | not null | nextval('tasks_id_seq'::regclass) name | character varying(255) | | not null | created_at | timestamp(0) without time zone | | | updated_at | timestamp(0) without time zone | | | Indexes: "tasks_pkey" PRIMARY KEY, btree (id)
Eloquentを定義
Laravelアプリケーションからデータベースへアクセスするには、大きく分けて3つの方法があります。直にSQLを書く、クエリビルダ、そしてEloquentです。
本記事では、Laravelでよく利用されるEloquentを利用します。Eloquentは、Active RecordパターンのORM(Object Relational Mappging)実装です。テーブルレコードをオブジェクトに見立てて、メソッド呼び出しによってレコードを操作します。
tasks
テーブルを操作するEloquentを作ってみましょう。Eloquentを作るにはartisan make:model
コマンドを利用します。下記のようにコマンドの後にクラス名を指定します。クラス名はテーブル名の単数形を指定するとデフォルトの実装でそのテーブルを操作できます。
$ docker-compose exec php ./artisan make:model Task
Model created successfully.
コマンドを実行するとapp/Models/Task.php
に下記のようなEloquentクラスが生成されます。クラスの実装はありませんが、基底クラスに実装があるので、本記事の利用方法であればこのままで問題ありません。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Task extends Model { use HasFactory; }
サンプル2-1. タスク追加APIの実装
タスクを追加するAPIを実装していきます。本APIの仕様は下記のようになります。
-
POST /api/tasks
- リクエスト
- ボディ:
{"name": "タスク名"}
- ボディ:
- レスポンス
- 正常登録時
- ステータスコード:
201
- ボディ:
{"id": NEW_ID, "name": "タスク名"}
- ステータスコード:
- 正常登録時
- リクエスト
CreateTaskActionクラスの生成
APIは、上述したHello APIのようにルーティングファイル(routes/api.php
)にクロージャで実装してもよいのですが、ここではシングルアクションコントローラとして独立したクラスに実装します。
コントローラはartisan make:controller
コマンドで生成します。下記のようにコマンドの後にクラス名と指定し、さらに--invokable
オプションを指定するとシングルアクションコントローラとなります。
$ docker-compose exec php ./artisan make:controller CreateTaskAction --invokable Controller created successfully.
コントローラはapp/Http/Controllers/CreateTaskAction.php
に生成され、下記のようになっています。クラスは__invoke
メソッドのみを持っています。このメソッドがルータからリクエストを受けて処理を行います。
app/Http/Controllers/CreateTaskAction.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class CreateTaskAction extends Controller { public function __invoke(Request $request) { // } }
一般的なコントローラでは、1つのクラスが複数のリクエストを処理するメソッドを持ちますが、本クラスのように単一のルーティングのみ処理するようにすれば、クラスの責務が限定され、理解しやすいコードになります。もちろん、Laravelでは従来の複数のルーティングを処理するコントローラも実装できるので、必要に応じて選択するとよいでしょう。
__invoke
メソッドにタスクを追加する処理を追加していきます。まず必要なのが、リクエストされた値のバリデーションです。バリデーションは__invoke
メソッド内でも実装できますが、ここではフォームリクエストクラスを用意してそちらで行います。
CreateTaskRequestクラスの生成
バリデーションを行うフォームリクエストクラスを生成するには、下記のようにartisan make:request
コマンドを実行します。コマンド後にクラス名を指定します。
$ docker-compose exec php ./artisan make:request CreateTaskRequest
Request created successfully.
コマンドを実行すると、app/Http/Requests/CreateTaskRequest.php
が生成されます。これに必要なバリデーションを加えたのが下記のクラスです。authorize
メソッドはリクエストユーザにリクエストを認めるかを示すメソッドです。
app/Http/Requests/CreateTaskRequest.php
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class CreateTaskRequest extends FormRequest { public function authorize() { return true; // (1) ここでは認証、認可は不要なので true } public function rules() { return [ 'name' => 'required|max:100', // (2) バリデーションルールを追加 ]; } }
今回は認証、認可は実装しないのでtrue
を返しておきます。rules
メソッドでバリデーションルールを連想配列で返します。ここではname
キーに対するバリデーションルール(必須、100文字以内)を指定しています。
CreateTaskActionクラスの実装
CreateTaskAction
クラスに戻って、__invoke
メソッドに実装を追加していきましょう。
まず、メソッドの引数を、先程実装したApp\Http\Requests\CreateTaskRequest
に変更しておきます。このようにすると、__invoke
メソッド内に処理が移る前にバリデーションが行われます。つまり、メソッドに入った時点でバリデーションが通過していることになります。
メソッドの中では、生成したTaskというEloquentを利用して、送信されたタスクをtasks
テーブルに保存しています。保存処理が正常に完了すれば、レスポンスとしてステータスコード201
とレスポンスボディを返します。
app/Http/Controllers/CreateTaskAction.php
<?php namespace App\Http\Controllers; use App\Http\Requests\CreateTaskRequest; use App\Models\Task; class CreateTaskAction extends Controller { public function __invoke(CreateTaskRequest $request) { $task = new Task(); $task->name = $request->validated()['name']; // バリデーションを行った値のみ取得 $task->save(); // tasks テーブルに保存 return response()->json(['id' => $task->id, 'name' => $task->name], 201); } }
最後に、ルーティングでCreateTaskAction
とURIパスをマッピングします。
routes/api.php
// use文を追記 use App\Http\Controllers\CreateTaskAction; // 追加 Route::post('/tasks', CreateTaskAction::class);
CreateTaskAction
クラスはシングルアクションコントローラなので、クラス名のみ指定します。これで実装は完了です。
実装したAPIをcurl
コマンドで試してみましょう。下記のようにcurl
コマンドでnew task
という名前のタスクをPOSTリクエストで送信すると、tasks
テーブルにタスクが登録されてレスポンスが返ってきます。
$ curl http://localhost:8000/api/tasks -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{"name": "new task"}' -v (snip) < HTTP/1.1 201 Created < Server: nginx < Content-Type: application/json < Transfer-Encoding: chunked < Connection: keep-alive < X-Powered-By: PHP/7.4.7 < Cache-Control: no-cache, private < Date: Tue, 23 Jun 2020 08:35:20 GMT < X-RateLimit-Limit: 60 < X-RateLimit-Remaining: 58 < {"id":1,"name":"new task"}
データベースを確認すると送信されたレコードが登録されていることが分かります。
$ docker-compose exec db psql -Uapp app -c "SELECT * from tasks;" id | name | created_at | updated_at ----+----------+---------------------+--------------------- 1 | new task | 2020-09-11 05:35:10 | 2020-09-11 05:35:10 (1 rows)
バリデーションエラーが発生するパターンも確認しておきましょう。下記のようにname
キーの値を空文字でリクエストを送信すると422 Unprocessable Entity
がレスポンスとして返されます。
$ curl http://localhost:8000/api/tasks -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d '{"name": ""}' -v (snip) < HTTP/1.1 422 Unprocessable Entity (snip) {"message":"The given data was invalid.","errors":{"name":["The name field is required."]}}
この場合、バリデーションでエラーとなっているので、CreateTaskAction
のメソッドは実行されません。
タスク追加APIのテストを実装
タスクを追加するAPIのテストを実装してみましょう。
ここではデータベースを利用したテストを実装するので、開発用とテスト用でデータベースを分けるため、phpunit.xml
に下記を追加しておきます。テストではdb-test
コンテナを利用します。
phpunit.xml
<php> <!-- 下記を追加 --> <server name="DB_HOST" value="db-test"/> </php>
下記のように、テストクラスをartisan make:test
コマンドで追加します。
$ docker-compose exec php ./artisan make:test PostTaskTest
Test created successfully.
追加したテストクラスは、tests/Feature/PostTaskTest.php
に生成されます。生成されたファイルに必要なコードを追加したのが下記です。
tests/Feature/PostTaskTest.php
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class PostTaskTest extends TestCase { use RefreshDatabase; // (1)データベースをリセット public function testPostTask() { $name = 'new task'; $response = $this->postJson('/api/tasks', [ // (2)JSON を POST 'name' => $name, ]); $id = $response->json(['id']); $response->assertStatus(201); // (3)レスポンスの検証 $response->assertJson([ 'id' => $id, 'name' => $name, ]); $this->assertDatabaseHas('tasks', [ // (4)データベースの検証 'id' => $id, 'name' => $name, ]); } }
(1)では、RefreshDatabase
トレイトをuse
しています。これは、テスト実行時に変更したデータベースの状態をリフレッシュするトレイトです。このトレイトをuse
すると、テスト実行ごとにマイグレーションを実行した状態で始めることができます。
(2)では、タスク追加APIを実行するため、postJson
メソッドにて、JSONをPOSTリクエストで送信しています。第二引数の連想配列の内容がJSONとして送信されます。
(3)では、レスポンスを確認しています。assertStatus
メソッドでステータスコードを、assertJson
メソッドでレスポンスボディのJSONをチェックしています。
(4)では、API実行後にデータベースのtasks
レコードに想定されたレコードが存在するか(タスクが追加されているか)を確認しています。assertDatabaseHas
メソッドは、第一引数で指定したテーブルに第二引数で指定した値を持つレコードが存在するかどうかを確認するメソッドです。
このテストにより、APIを実行して、レスポンスの検証、データベースの検証を行います。
テストを実行してみましょう。下記のようにphpunit
コマンドを実行すると、テストがパスすることが確認できました。
$ docker-compose exec php ./vendor/bin/phpunit PHPUnit 9.3.8 by Sebastian Bergmann and contributors. .. 2 / 2 (100%) Time: 00:02.307, Memory: 26.00 MB OK (2 tests, 5 assertions)
実際の開発では、さらにバリデーションエラーを引き起こすテストなど、異常系のテストを追加していくことになりますが、ここでは正常系のテストにとどめておきます。GitHubのサンプルコードには異常系のテストも含まれているので参照してください。
サンプル2-2. タスク取得APIの実装
次に、タスクを取得するAPIを実装します。本APIの仕様は下記のようになります。
-
GET /api/tasks/ID
- レスポンス
- 正常時
- ステータスコード:
200
- ボディ:
{"id": ID, "name":"task"}
- ステータスコード:
- 正常時
- レスポンス
タスクを追加するAPIと同様に、コントローラを下記のように生成します。
$ docker-compose exec php ./artisan make:controller GetTaskAction --invokable Controller created successfully.
生成したコントローラはapp/Http/Controllers/GetTaskAction.php
にあります。このクラスに必要な処理が加えたのが下記です。
app/Http/Controllers/GetTaskAction.php
<?php namespace App\Http\Controllers; use App\Models\Task; class GetTaskAction extends Controller { public function __invoke(int $id) { $task = Task::query()->findOrFail($id); return response()->json([ 'id' => $task->id, 'name' => $task->name, ]); } }
__invoke
メソッドの仮引数では$id
を受け取ります。これはタスクIDを示すもので、後ほどルーティングにて定義します。メソッド内では、この$id
に合致するレコードをtasks
テーブルからTaskクラス(Eloquent)を利用して取得します。findOrFail
メソッドはid
が引数に合致するレコードを取得します。もし合致するレコードが存在しなければ例外をスローします。
最後に取得したレコード(Eloquentインスタンス)の値からレスポンスボディを生成して、それを返します。
ルーティングにGetTaskAction
を追加します。下記では、/tasks/{id}
へのGETリクエストに対してGetTasksAction
クラスを割り当てています。
routes/api.php
<?php // use文に追加 use App\Http\Controllers\GetTaskAction; // 追加 Route::get('/tasks/{id}', GetTaskAction::class)->where('id', '[0-9]+');
{id}
の箇所は、プレースホルダとして任意のパタメータとして扱うことができます。このid
は割り当ててたアクション(クロージャやコントローラメソッド)の仮引数と対応しており、メソッドの仮引数$id
に指定された値が与えられます。例えば、/tasks/1
へのリクエストであれば仮引数$id
の値は1
となります。
続くwhere
メソッドでは、プレースホルダで指定したパラメータの形式を指定しています。パラメータの形式は、正規表現で指定することができます。リクエストのパラメータが合致しない場合は例外がスローされ、アクションの処理は実行されません。ここでは任意の整数を想定しています。
実装したAPIをcurl
コマンドで試してみましょう。下記のようにcurl
コマンドで/api/tasks/1
にGETリクエストを送信すると、合致するレコードの値がレスポンスとして返ってきます。
$ curl http://localhost:8000/api/tasks/1 -H 'Accept: application/json' {"id":1,"name":"new task"}
存在しないIDや形式が異なるIDが送信された場合は、404 Not Found
が返されます。
# 存在しないID $ curl http://localhost:8000/api/tasks/999 -H 'Accept: application/json' -I HTTP/1.1 404 Not Found (snip) # 形式が異なるID $ curl http://localhost:8000/api/tasks/a -H 'Accept: application/json' -I HTTP/1.1 404 Not Found (snip)
タスク取得APIのテストを実装
タスクを取得するAPIのテストを実装しましょう。本APIはtasks
テーブルに存在するレコードを取得するので、あらかじめテーブルにレコードを登録しておく必要があります。このようにテストに必要なレコードを登録する機能としてファクトリが利用できます。
ファクトリを生成するにはartisan make:factory
コマンドを利用します。下記のようにコマンドの後にファクトリ名と--model
オプションで利用するEloquentを指定して実行すると、database/factories/TaskFactory.php
が生成されます。
$ docker-compose exec php ./artisan make:factory TaskFactory --model=Task Factory created successfully.
生成されたファクトリに必要なコードを追加したのが以下です。
database/factories/TaskFactory.php
<?php namespace Database\Factories; use App\Models\Task; use Illuminate\Database\Eloquent\Factories\Factory; class TaskFactory extends Factory { protected $model = Task::class; // (1)Task を指定 public function definition() { return [ 'name' => $this->faker->name, // (2)レコードに登録する値を指定 ]; } }
(1)では、先程--model
で指定したEloquentが指定されています。
(2)では、連想配列でTaskクラスの値(tasks
テーブルの値)を指定します。$this->faker->name
は、名前とおぼしき文字列を自動生成する機能です。このファクトリを利用することで、name
に名前と思われる文字列を含んだインスタンス(レコード)を生成できます。
APIをテストするテストクラスを、下記のように生成します。
$ docker-compose exec php ./artisan make:test GetTaskTest Test created successfully.
生成したテストクラスは、tests/Feature/GetTaskTest.php
に生成されます。これに必要なコードを追加したのが下記です。
tests/Feature/GetTaskTest.php
<?php namespace Tests\Feature; use App\Models\Task; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class GetTaskTest extends TestCase { use RefreshDatabase; public function testGetTask() { $task = Task::factory()->create(); // (1)ファクトリでレコード追加 $response = $this->get('/api/tasks/' . $task->id); // (2)GET リクエスト $response->assertStatus(200); // (3)レスポンスの検証 $response->assertJson([ 'id' => $task->id, 'name' => $task->name, ]); } }
(1)では、上記で実装したファクトリを利用しています。ここでは、create
メソッドにて生成したTaskインスタンスをtasks
テーブルに保存しています。
(2)では、ファクトリで生成したインスタンスのid
を利用して、タスク取得APIを実行しています。
(3)では、レスポンスを検証しています。ここでもファクトリで生成したインスタンスを利用して、レスポンスボディのJSONの値を検証しています。
テストを実行してみましょう。下記のようにphpunit
コマンドを実行すると、テストがパスすることが確認できました。
$ docker-compose exec php ./vendor/bin/phpunit PHPUnit 9.3.8 by Sebastian Bergmann and contributors. ... 3 / 3 (100%) Time: 00:02.607, Memory: 28.00 MB OK (3 tests, 7 assertions)
まとめ ─ フレームワークを学ぶ上で大切なこと
シンプルなREST APIの実装を通じて、Laravelを利用したWebアプリケーション開発の流れを見てきました。Laravelには多彩な機能があり、ここで紹介した機能はごく一部です。
フレームワークとしてアプリケーションの基盤となる部分だけではなく、データベースマイグレーションや、artisan
コマンドによるコード生成、テスト支援といった仕組みが日々の開発を助けてくれるということを感じていただけたのではないでしょうか。
最後に、フレームワークを学ぶ上で、筆者が大切だと感じていることを2つ紹介します。
フレームワークの動作イメージを持つ
1つは、フレームワークの動作イメージを持つということです。
HTTPリクエストが来て、index.phpからフレームワークが起動して、アプリケーションを実行する。そしてアプリケーションが生成したレスポンスを返す。こうした一連の流れを朧気(おぼろげ)ながらでもイメージしておくことで、フレームワークの理解がよりいっそう進みます。
動作イメージを知る一歩としては、公式ドキュメントのRequest Lifecycleを参照するとよいでしょう。さらに詳細を知りたい場合はフレームワークのコードを順に読むと、そのイメージが鮮明になります。
フレームワークは何か特別なものではなく、PHPコードで書かれた1つのアプリケーションに過ぎません。ブラックボックスにせず、その動きを掴んでみてください。
大事なのは「何を」作りたいか
もう1つは、アプリケーションが主でフレームワークは従であるということです。
フレームワークを学びはじめると、多くの機能に圧倒されてしまい、ともすればフレームワークに習熟することに主眼を置きがちです。もちろんフレームワークを学ぶことも大切なのですが、それより大事なのは、作りたいアプリケーションがあり、それを実現するためにフレームワークがあるということです。
極端なことを言えば、作りたいアプリケーションにとって不要な機能を学ぶ必要はありません。フレームワークを覚えるのではなく、どう利用するか、活用するかという主体的な視点で見ていくとよいでしょう。
本記事が、あなたが開発するWebアプリケーションにLaravelを活用するきっかけになれば幸いです。
新原雅司(しんばら・まさし) @shin1x1
このサイトにはInsomnia DesignerとInsomnia Coreがありますが、REST APIクライアントツールはInsomnia Coreです。↩