CodeIgniter4でREST APIを作成する
この記事は CodeIgniter Advent Calendar 2020 - Qiita の20日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。
今日は、CodeIgniter4 で REST API を作成してみます。
動作確認環境
- CodeIgniter 4.0.5 開発版
- PHP 7.4.13
- MySQL 5.7.32
- macOS 10.15.7
MySQL データベースの準備
データベースとユーザを作成します。公式チュートリアル用に作成したもの と同じです。
$ mysql -uroot -p
開発用データベース
開発用データベース ci4tutorial
を作成します。
CREATE DATABASE ci4tutorial DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ユーザ dbuser
を作成します。
GRANT ALL PRIVILEGES ON ci4tutorial.* TO dbuser@localhost IDENTIFIED BY 'dbpassword';
テスト用データベース
テスト用データベース ci4tutorial_test
も用意しておきましょう。
CREATE DATABASE ci4tutorial_test DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
データベースユーザ dbuser
に権限を付与します。
GRANT ALL PRIVILEGES ON ci4tutorial_test.* TO dbuser@localhost;
作成したユーザで MySQL にログインできることを確認します。
$ mysql -u dbuser -p ci4tutorial
Enter password:
テーブル作成と初期データの挿入
テーブル news
を作成します。
以下の記事に従い、マイグレーションファイルを作成し、実行します。
$ php spark migrate
作成したテーブルに初期データを挿入します。
以下の記事に従い、シーダーファイルを作成し、実行します。
$ php spark db:seed NewsSeeder
これで、テーブル news
に以下のデータが挿入されました。
URI 設計
URI とコントローラの対応は以下のようにします。 これは、CodeIgniter4 の Resource Routes を使うと自動的に設定されるルート(の一部)です。
+--------+---------------+--------------------------------------+
| Method | Route | Handler |
+--------+---------------+--------------------------------------+
| GET | api/news | \App\Controllers\Api\News::index |
| GET | api/news/(.*) | \App\Controllers\Api\News::show/$1 |
| POST | api/news | \App\Controllers\Api\News::create |
| PATCH | api/news/(.*) | \App\Controllers\Api\News::update/$1 |
| PUT | api/news/(.*) | \App\Controllers\Api\News::update/$1 |
| DELETE | api/news/(.*) | \App\Controllers\Api\News::delete/$1 |
+--------+---------------+--------------------------------------+
なお、今回は PATCH メソッドは実装しません。
ルーティング設定
ルーティング設定ファイルに以下を追加します。これで上記のようにルーティングされます。
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -32,6 +32,11 @@ $routes->setAutoRoute(true);
// route since we don't have to scan directories.
$routes->get('/', 'Home::index');
+$routes->resource(
+ 'api/news',
+ ['only' => ['index', 'show', 'create', 'update', 'delete']]
+);
+
$routes->match(['get', 'post'], 'news/create', 'News::create');
$routes->get('news/(:segment)', 'News::view/$1');
$routes->get('news', 'News::index');
PATCH メソッドは実装しないですが、$routes->resource()
でルートを設定しないようにする方法はありませんでした。
どうしてもルート設定したくない場合は、$routes->resource()
は使わずに、個別にルートを 1つずつ設定すれば可能です。
フィルタの設定
CSRF フィルタが有効になっている場合は、API の URI 以下だけ除外しておきます。
--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -16,7 +16,7 @@ class Filters extends BaseConfig
public $globals = [
'before' => [
//'honeypot'
- 'csrf',
+ 'csrf' => ['except' => 'api/*'],
],
'after' => [
'toolbar',
なお、本番環境では CSRF 対策が必要になる場合もあります。
CodeIgniter4 の CSRF フィルタは POST メソッドしかフィルタしませんので、必要な場合は独自に CSRF フィルタを実装してください。
NewsModel の作成
モデルは公式チュートリアルの NewsModel そのままで OK です。
getNews()
メソッドが公式チュートリアルにはありますが、なくても構いません。
app/Models/NewsModel.php
<?php
namespace App\Models;
use CodeIgniter\Model;
class NewsModel extends Model
{
protected $table = 'news';
protected $allowedFields = ['title', 'slug', 'body'];
public function getNews($slug = false)
{
if ($slug === false)
{
return $this->findAll();
}
return $this->asArray()
->where(['slug' => $slug])
->first();
}
}
$table
にこのモデルが操作するテーブル名を、$allowedFields
にこのモデルから変更可能なカラム名を設定します。
News コントローラの作成
News コントローラは CodeIgniter4 の ResourceController を継承し作成します。 以下のメソッドが必要になります。
app/Controllers/Api/News.php
<?php
namespace App\Controllers\Api;
use CodeIgniter\RESTful\ResourceController;
use App\Models\NewsModel;
class News extends ResourceController
{
protected $modelName = NewsModel::class;
protected $format = 'json';
/**
* @var NewsModel
*/
protected $model;
/**
* GET api/news
*/
public function index()
{
}
/**
* GET api/news/{id}
*/
public function show($id = null)
{
}
/**
* POST api/news
*/
public function create()
{
}
/**
* PUT api/news/{id}
*/
public function update($id = null)
{
}
/**
* DELETE api/news/{id}
*/
public function delete($id = null)
{
}
}
$modelName
に使用するモデルのクラス名を、$format
に出力フォーマットを設定します。
今回は JSON で出力します。
GET api/news
全ての記事を取得する API です。
News コントローラに以下の index() メソッドを追加します。
/**
* GET api/news
*
* @return mixed
*/
public function index()
{
$response = [
'status' => 200,
'error' => null,
'news' => $this->model->findAll(),
];
return $this->respond($response);
}
http://localhost:8080/api/news
に GET リクエストを送信してみましょう。
ここでは Postman を使います。
全ての記事が JSON で返りました。
GET api/news/{id}
指定の記事を取得する API です。
News コントローラに以下の show() メソッドを追加します。
/**
* GET api/news/{id}
*
* @param string|null $id
* @return mixed
*/
public function show($id = null)
{
if ($id === null) {
return $this->failNotFound('No news found');
}
$news = $this->model->find($id);
if ($news === null) {
return $this->failNotFound('No news found');
}
$response = [
'status' => 200,
'error' => null,
'news' => $news,
];
return $this->respond($response);
}
(2022-12-30 追記)
$id
が null の場合のエラー処理を追加しました。
http://localhost:8080/api/news/1
に GET リクエストを送信してみましょう。
指定した記事が JSON で返りました。
存在しない ID を指定すると、404 が返ります。
POST api/news
記事を新規作成する API です。
News コントローラに以下の create() メソッドを追加します。 データは Form で送信される前提のコードです。
/**
* POST api/news
*
* @return mixed
* @throws \ReflectionException
*/
public function create()
{
if (!$this->validate(
[
'title' => 'required|min_length[3]|max_length[255]',
'body' => 'required',
]
)) {
$errors = implode(' ', $this->validator->getErrors());
return $this->failValidationError($errors);
}
$this->model->insert(
[
'title' => $this->request->getPost('title'),
'slug' => url_title(
$this->request->getPost('title'),
'-',
true
),
'body' => $this->request->getPost('body'),
]
);
$response = [
'status' => $this->codes['created'],
'error' => null,
'messages' => [
'success' => 'News successfully created',
]
];
return $this->respondCreated($response);
}
http://localhost:8080/api/news
に POST リクエストを送信してみましょう。
記事が作成されました。
PUT api/news/{id}
記事を更新する API です。
News コントローラに以下の update() メソッドを追加します。 POST と同じくデータは Form で送信される前提のコードです。
/**
* PUT api/news/{id}
*
* @param string|null $id
* @return mixed
* @throws \ReflectionException
*/
public function update($id = null)
{
if ($this->request->getMethod() === 'patch') {
return $this->fail('PATCH is not implemented');
}
if ($id === null) {
return $this->failNotFound('No news found');
}
if (!$this->validate(
[
'title' => 'required|min_length[3]|max_length[255]',
'body' => 'required',
]
)) {
$errors = implode(' ', $this->validator->getErrors());
return $this->failValidationError($errors);
}
$news = $this->model->find($id);
if ($news === null) {
return $this->failNotFound('No news found');
}
$input = $this->request->getRawInput();
$data = [
'title' => $input['title'],
'slug' => url_title(
$input['title'],
'-',
true
),
'body' => $input['body']
];
$this->model->update($id, $data);
$response = [
'status' => $this->codes['updated'],
'error' => null,
'messages' => [
'success' => 'News successfully updated',
]
];
return $this->respond($response);
}
(2022-12-30 追記)
$id
が null の場合のエラー処理を追加しました。
http://localhost:8080/api/news/4
に PUT リクエストを送信してみましょう。
記事が更新されました。
DELETE api/news/{id}
記事を削除する API です。
News コントローラに以下の delete() メソッドを追加します。
/**
* DELETE api/news/{id}
*
* @param string|null $id
* @return mixed
*/
public function delete($id = null)
{
if ($id === null) {
return $this->failNotFound('No news found');
}
$news = $this->model->find($id);
if ($news === null) {
return $this->failNotFound('No news found');
}
$this->model->where('id', $id)->delete($id);
$response = [
'status' => $this->codes['deleted'],
'error' => null,
'messages' => [
'success' => 'News successfully deleted',
]
];
return $this->respondDeleted($response);
}
(2022-12-30 追記)
$id
が null の場合のエラー処理を追加しました。
http://localhost:8080/api/news/4
に DELETE リクエストを送信してみましょう。
記事が削除されました。
これでニュース記事の CRUD が一通り作成できました。
CodeIgniter4のREST APIのテストを書く へ続きます。
この記事は CodeIgniter Advent Calendar 2020 - Qiita の20日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。
参考
Date: 2020/12/20