Yahoo! JAPAN

Laravel実践入門! シンプルなREST APIを実装して学ぶ、多機能なPHPフレームワークの使い方

エンジニアHub

はじめまして。Webアプリケーション開発や技術サポートを行っている新原(@shin1x1)です。本記事では、これからLaravelを使ってみたい方を対象に、シンプルなREST APIの実装を通じて、Laravelを利用した開発のイメージを紹介します。フレームワークの詳細には触れていないので、実際の開発で必要な情報は公式マニュアルや書籍、ブログなどを参照してください。

Laravelの概要と特徴特徴1. 自由度が高い特徴2. 幅広いニーズをサポート特徴3. 開発支援の充実特徴4. ユーザが多いLaravelのバージョンと選び方サンプル開発に必要な情報と環境の構築必要なシステム構成REST APIクライアントツールDocker DesktopのインストールLaravelプロジェクトの作成docker-compose.ymlのダウンロードサンプル1. Hello APIの実装Hello APIのテストを実装サンプル2. To-DoアプリケーションAPIデータベースとの接続設定データベースマイグレーションEloquentを定義サンプル2-1. タスク追加APIの実装CreateTaskActionクラスの生成CreateTaskRequestクラスの生成CreateTaskActionクラスの実装タスク追加APIのテストを実装サンプル2-2. タスク取得APIの実装タスク取得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のスター数は60000を超えており、これはサーバーサイドの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日72020年3月3日2020年10月6日2021年3月3日82020年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 Core1PostmanREST 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環境でも基本的な流れは同じです。

Mac版Windows版

Docker Desktopがインストールできたら、ターミナルを開いてdocker --versionコマンドを入力してください。下記のようにDockerのバージョンが表示されればインストールが成功しています。バージョン番号やbuildの後ろの値はインストールを行ったタイミングによって変わっている可能性はあります。

$ docker--versionDocker version19.03.12 build 48a66213fe

サンプルアプリケーションを実行するには、PHP(php-fpm)、nginx、PostgreSQLという3つのDockerコンテナを利用するので、それらを統合して管理できるようにdocker-composeを利用します。このコマンドはDocker Desktopに同梱されているので、下記のようにdocker-composeコマンドも実行できるか確認しておきます。

$ docker-compose version docker-compose version1.26.2 build eefe0d31 docker-py version:4.2.2CPython version:3.7.7OpenSSL version: OpenSSL1.1.1g21Apr2020

Laravelプロジェクトの作成

サンプルアプリケーションを実装するため、Laravelプロジェクトを生成します。

ComposerのDockerコンテナが公開されているので、これを利用して次のようにcomposer create-projectコマンドを実行します。

$ docker run--rm-v`pwd`:/opt-w/opt composer:1.10create-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.10create-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-dCreating network"todoapp_default"with the default driver Creating todoapp_composer_1 ...doneCreating todoapp_db_1 ...doneCreating todoapp_db-test_1 ...doneCreating todoapp_php_1 ...doneCreating todoapp_nginx_1 ...done

コンテナが起動できれば、Laravelの動作環境が整いました。

ブラウザでhttp://localhost:8000にアクセスすると、下記のようにLaravelの初期画面が表示されます。

起動中のDockerコンテナを終了・破棄する場合は、docker-compose downコマンドを実行します。

$ docker-compose down Stopping eh-laravel-sample_nginx_1 ...doneStopping eh-laravel-sample_php_1 ...doneStopping eh-laravel-sample_db-test_1 ...doneStopping eh-laravel-sample_db_1 ...doneRemoving eh-laravel-sample_nginx_1 ...doneRemoving eh-laravel-sample_php_1 ...doneRemoving eh-laravel-sample_composer_1 ...doneRemoving eh-laravel-sample_db-test_1 ...doneRemoving eh-laravel-sample_db_1 ...doneRemoving 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ディレクトリにサンプル用のテストファイルが含まれています。これらは開発には不要なので削除しておきます。

$rmtests/Feature/ExampleTest.php tests/Unit/ExampleTest.php

テストクラスはartisan make:testコマンドで雛形を生成できます。下記のようにコマンドの後ろにテストクラスのクラス名を指定して実行します。

$ docker-composeexecphp ./artisan make:test GetHelloTest Test created successfully.

生成したテストクラスはtests/Feature/GetHelloTest.phpになります。このファイルにHello APIのテストコードを追加したのが、下記のコードです。

tests/Feature/GetHelloTest.php

<?phpnamespaceTests\Feature;useTests\TestCase;classGetHelloTestextendsTestCase{publicfunctiontestExample(){$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-composeexecphp ./vendor/bin/phpunit PHPUnit9.3.8by Sebastian Bergmann and contributors..1/1(100%)Time: 00:01.300 Memory:18.00MB OK(1test2assertions)

ここまで、Laravelに新しいAPIを追加して、テストで動作を確認するという流れを見てきました。次は、これをベースにTo-DoアプリのAPIを実装していきましょう。

サンプル2. To-DoアプリケーションAPI

ここで作成する「To-Do」アプリケーションは、ToDoリストにタスクの追加と読込を行うシンプルなアプリケーションです。これらのAPI実装を通じて、Laravelとデータベースを利用した開発を体験してみましょう。実装するAPIは下記の2つです。

タスク追加APIPOST /api/tasksタスク取得APIGET /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-composeexecphp ./artisan migrate:status Migration table not found.

データベースマイグレーション

Laravelには、データーベーススキーマをPHPコードで記述できるマイグレーションという仕組みがあります。

テーブルの追加や削除、カラムの変更、インデックスの追加といった変更情報をマイグレーションで管理しておくことで、開発環境と本番環境やチームメンバーのそれぞれの環境といった異なる環境においても、同じデーターベーススキーマを構築できます。

tasksテーブルを構築するためにマイグレーションファイルを生成します。マイグレーションファイルを作成するには、artisan make:migrationコマンドを利用します。下記では、コマンドに続けてマイグレーション名を指定しています。

$ docker-composeexecphp ./artisan make:migration CreateTasksTable Created Migration: 2020_09_11_020221_create_tasks_table# 2020_09_11_020221 の部分は実行したタイミングで異なります$lsdatabase/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

<?phpuseIlluminate\Database\Migrations\Migration;useIlluminate\Database\Schema\Blueprint;useIlluminate\Support\Facades\Schema;classCreateTasksTableextendsMigration{publicfunctionup(){Schema::create('tasks'function(Blueprint$table){$table->id();$table->string('name');// (1)name カラムのコードを追加$table->timestamps();});}publicfunctiondown(){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-composeexecphp ./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-composeexecdb psql-Uappapp-c"\dtasks"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)withouttimezone | | | updated_at | timestamp(0)withouttimezone | | | 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-composeexecphp ./artisan make:model Task Model created successfully.

コマンドを実行するとapp/Models/Task.phpに下記のようなEloquentクラスが生成されます。クラスの実装はありませんが、基底クラスに実装があるので、本記事の利用方法であればこのままで問題ありません。

<?phpnamespaceApp\Models;useIlluminate\Database\Eloquent\Factories\HasFactory;useIlluminate\Database\Eloquent\Model;classTaskextendsModel{useHasFactory;}

サンプル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-composeexecphp ./artisan make:controller CreateTaskAction--invokableController created successfully.

コントローラはapp/Http/Controllers/CreateTaskAction.phpに生成され、下記のようになっています。クラスは__invokeメソッドのみを持っています。このメソッドがルータからリクエストを受けて処理を行います。

app/Http/Controllers/CreateTaskAction.php

<?phpnamespaceApp\Http\Controllers;useIlluminate\Http\Request;classCreateTaskActionextendsController{publicfunction__invoke(Request$request){//}}

一般的なコントローラでは、1つのクラスが複数のリクエストを処理するメソッドを持ちますが、本クラスのように単一のルーティングのみ処理するようにすれば、クラスの責務が限定され、理解しやすいコードになります。もちろん、Laravelでは従来の複数のルーティングを処理するコントローラも実装できるので、必要に応じて選択するとよいでしょう。

__invokeメソッドにタスクを追加する処理を追加していきます。まず必要なのが、リクエストされた値のバリデーションです。バリデーションは__invokeメソッド内でも実装できますが、ここではフォームリクエストクラスを用意してそちらで行います。

CreateTaskRequestクラスの生成

バリデーションを行うフォームリクエストクラスを生成するには、下記のようにartisan make:requestコマンドを実行します。コマンド後にクラス名を指定します。

$ docker-composeexecphp ./artisan make:request CreateTaskRequest Request created successfully.

コマンドを実行すると、app/Http/Requests/CreateTaskRequest.phpが生成されます。これに必要なバリデーションを加えたのが下記のクラスです。authorizeメソッドはリクエストユーザにリクエストを認めるかを示すメソッドです。

app/Http/Requests/CreateTaskRequest.php

<?phpnamespaceApp\Http\Requests;useIlluminate\Foundation\Http\FormRequest;classCreateTaskRequestextendsFormRequest{publicfunctionauthorize(){returntrue;// (1) ここでは認証、認可は不要なので true}publicfunctionrules(){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

<?phpnamespaceApp\Http\Controllers;useApp\Http\Requests\CreateTaskRequest;useApp\Models\Task;classCreateTaskActionextendsController{publicfunction__invoke(CreateTaskRequest$request){$task=newTask();$task->name=$request->validated()['name'];// バリデーションを行った値のみ取得$task->save();// tasks テーブルに保存returnresponse()->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.1201Created<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: Tue23Jun202008:35:20 GMT<X-RateLimit-Limit:60<X-RateLimit-Remaining:58<{"id":1"name":"new task"}

データベースを確認すると送信されたレコードが登録されていることが分かります。

$ docker-composeexecdb psql-Uappapp-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(1rows)

バリデーションエラーが発生するパターンも確認しておきましょう。下記のように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.1422Unprocessable 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><!-- 下記を追加 --><servername="DB_HOST"value="db-test"/></php>

下記のように、テストクラスをartisan make:testコマンドで追加します。

$ docker-composeexecphp ./artisan make:test PostTaskTest Test created successfully.

追加したテストクラスは、tests/Feature/PostTaskTest.phpに生成されます。生成されたファイルに必要なコードを追加したのが下記です。

tests/Feature/PostTaskTest.php

<?phpnamespaceTests\Feature;useIlluminate\Foundation\Testing\RefreshDatabase;useTests\TestCase;classPostTaskTestextendsTestCase{useRefreshDatabase;// (1)データベースをリセットpublicfunctiontestPostTask(){$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-composeexecphp ./vendor/bin/phpunit PHPUnit9.3.8by Sebastian Bergmann and contributors. ..2/2(100%)Time: 00:02.307 Memory:26.00MB OK(2tests5assertions)

実際の開発では、さらにバリデーションエラーを引き起こすテストなど、異常系のテストを追加していくことになりますが、ここでは正常系のテストにとどめておきます。GitHubのサンプルコードには異常系のテストも含まれているので参照してください。

サンプル2-2. タスク取得APIの実装

次に、タスクを取得するAPIを実装します。本APIの仕様は下記のようになります。

GET /api/tasks/IDレスポンス正常時ステータスコード:200ボディ:{"id": ID "name":"task"}

タスクを追加するAPIと同様に、コントローラを下記のように生成します。

$ docker-composeexecphp ./artisan make:controller GetTaskAction--invokableController created successfully.

生成したコントローラはapp/Http/Controllers/GetTaskAction.phpにあります。このクラスに必要な処理が加えたのが下記です。

app/Http/Controllers/GetTaskAction.php

<?phpnamespaceApp\Http\Controllers;useApp\Models\Task;classGetTaskActionextendsController{publicfunction__invoke(int$id){$task=Task::query()->findOrFail($id);returnresponse()->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文に追加useApp\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-composeexecphp ./artisan make:factory TaskFactory--model=TaskFactory created successfully.

生成されたファクトリに必要なコードを追加したのが以下です。

database/factories/TaskFactory.php

<?phpnamespaceDatabase\Factories;useApp\Models\Task;useIlluminate\Database\Eloquent\Factories\Factory;classTaskFactoryextendsFactory{protected$model=Task::class;// (1)Task を指定publicfunctiondefinition(){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

<?phpnamespaceTests\Feature;useApp\Models\Task;useIlluminate\Foundation\Testing\RefreshDatabase;useTests\TestCase;classGetTaskTestextendsTestCase{useRefreshDatabase;publicfunctiontestGetTask(){$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-composeexecphp ./vendor/bin/phpunit PHPUnit9.3.8by Sebastian Bergmann and contributors. ...3/3(100%)Time: 00:02.607 Memory:28.00MB OK(3tests7assertions)

まとめ ─ フレームワークを学ぶ上で大切なこと

シンプルなREST APIの実装を通じて、Laravelを利用したWebアプリケーション開発の流れを見てきました。Laravelには多彩な機能があり、ここで紹介した機能はごく一部です。

フレームワークとしてアプリケーションの基盤となる部分だけではなく、データベースマイグレーションや、artisanコマンドによるコード生成、テスト支援といった仕組みが日々の開発を助けてくれるということを感じていただけたのではないでしょうか。

最後に、フレームワークを学ぶ上で、筆者が大切だと感じていることを2つ紹介します。

フレームワークの動作イメージを持つ

1つは、フレームワークの動作イメージを持つということです。

HTTPリクエストが来て、index.phpからフレームワークが起動して、アプリケーションを実行する。そしてアプリケーションが生成したレスポンスを返す。こうした一連の流れを朧気(おぼろげ)ながらでもイメージしておくことで、フレームワークの理解がよりいっそう進みます。

動作イメージを知る一歩としては、公式ドキュメントのRequest Lifecycleを参照するとよいでしょう。さらに詳細を知りたい場合はフレームワークのコードを順に読むと、そのイメージが鮮明になります。

フレームワークは何か特別なものではなく、PHPコードで書かれた1つのアプリケーションに過ぎません。ブラックボックスにせず、その動きを掴んでみてください。

大事なのは「何を」作りたいか

もう1つは、アプリケーションが主でフレームワークは従であるということです。

フレームワークを学びはじめると、多くの機能に圧倒されてしまい、ともすればフレームワークに習熟することに主眼を置きがちです。もちろんフレームワークを学ぶことも大切なのですが、それより大事なのは、作りたいアプリケーションがあり、それを実現するためにフレームワークがあるということです。

極端なことを言えば、作りたいアプリケーションにとって不要な機能を学ぶ必要はありません。フレームワークを覚えるのではなく、どう利用するか、活用するかという主体的な視点で見ていくとよいでしょう。

本記事が、あなたが開発するWebアプリケーションにLaravelを活用するきっかけになれば幸いです。

新原雅司(しんばら・まさし)@shin1x1

PHPをメインにWebシステムの開発や技術サポートを主業務としている。技術イベントでの講演や「PHPの現場」というPodcastの運営を行っている。主な著書に『Laravelリファレンス』『Laravel Web アプリケーション開発』などがある。
Blog:Shin x Blog
Podcast:PHPの現場

このサイトにはInsomnia DesignerとInsomnia Coreがありますが、REST APIクライアントツールはInsomnia Coreです。↩

【関連記事】

おすすめの記事