「PHPでWEB APIを開発することになったけど、簡単に作れないかな?」
「APIで取得したいデータはクライアント側からある程度自由に決められないかな?」
こんにちは、カフーブログのタカフです。普段は主にWEB系開発をしています。
Webアプリ開発でもネイティブアプリ開発でも、切っても切れない腐れ縁となるのがWebAPIによるデータ取得ですよね。
そんなAPIを開発していて最近思う事があります。
「僕は何回同じコード書いてるんだ!?」と。
WebAPIって、クライアントからのリクエストに応じてサーバー側でDBのテーブルを読み取って、最近の主流ではjson形式で返却したりしますが、
単一のテーブルから単純に取ってくるだけの処理のコードって毎回同じですよね。
APIを開発する度にまた同じ様なコード書くのか、と思うわけです。
で、このようにWebAPIで単純なデータをやりとりするだけのPHPライブラリってないのかなって探したら、
ありました。
php-crud-api というものです。
このphp-crud-apiがどんな事をしてくれるかと言うと、クライアントから欲しいデータをパラメータ付きでリクエストするとその内容をjson形式で返してくれるという素晴らしすぎるライブラリです。
例えば、以下のようなリクエストを出すと、
https://www.example.com/api.php/records/users/?filter=name,eq,suzuki&include=age,gender&order=id,desc&page=2,10
「usersテーブルから、nameカラムがsuzukiであるレコードの、ageとgenderカラムのデータを取得し、それをidの降順に並び替えしたうちの、10件ずつのデータ区切りで2ページ目をjsonとして返却」 とかをやってくれます。
えっ!?このライブラリ最高じゃないですか!?
要するに、クライアント側から欲しいデータ内容をデザインできるわけです。
同じ様な設計思想でGraphQLというWeb APIの規格があるのですが、GraphQLはサーバー側の実装コストが少し難しく敷居も高い印象でして、そもそもNoSQLとかPHP以外の言語を強いられることもあるのに対して、
このライブラリは実装コストは非常に低いので使いやすいです。
また、既にMySQLやPHPを得意とした会社さんとかでしたら、その強みを活かしてこのようなライブラリを活用するのもいい選択だと僕は考えます。
本記事では、このphp-crud-apiの実際の開発に使える使い方を説明していきます。
※因みにCRUDとは、データベースを操作する時のCreate・Read・Update・Deleteの頭文字を取った略語の事です。
php-crud-apiのインストール方法
このライブラリ自体はGitHubから直接ダウンロードしてシステムに設置するか、もしくはcomposer経由でインストールするかです。
composerの場合は以下のコマンドでインストールしてautoload.phpを読み込むだけです。
composer require mevdschee/php-crud-api
php-crud-apiをリクエストする為の準備
php-crud-apiは、使う環境に制限はなく、ピュアなPHPだけの環境でも、PHPフレームワークの環境にも、組み込むことが出来ます。
ピュアPHPな環境でphp-crud-apiを使う準備
上記の方法により、php-crud-apiをGitHubかcomposerのどちらかからダウンロードすると、その中にapi.phpというファイルがあります。
このapi.phpというファイルをドキュメントルート配下の任意の場所にコピーします。
そしてファイルを開いて、ソースコードの最下部にデータベースの接続情報が書いてあるのでそこを変更します。
namespace Tqdev\PhpCrudApi {
use Tqdev\PhpCrudApi\Api;
use Tqdev\PhpCrudApi\Config;
use Tqdev\PhpCrudApi\RequestFactory;
use Tqdev\PhpCrudApi\ResponseUtils;
$config = new Config([
'username' => '[db user name]',
'password' => '[db password]',
'database' => '[db scheme name]',
'address' => '[db hostname]',
]);
$request = RequestFactory::fromGlobals();
$api = new Api($config);
$response = $api->handle($request);
ResponseUtils::output($response);
}
設置準備は以上です。あとは普通にこのapi.phpをクライアントから呼び出すだけです。
https://www.example.com/api.php/records/users/
というURLでusersテーブルを読み出すAPIを呼び出すことが出来ます。
address
はデータベースサーバーのホスト名のことで、指定しない場合はデフォルト値のlocalhost
となるので、ファイルを配置して最短3行の変更だけでAPIが開発完了となります。
タイトルにある最短3行で開発完了というのはこういう事です。
しかし、この状態ではこのdatabase
で指定したスキーマの全てのテーブルに対して作成・読取・変更・削除が可能となるので非常に危険な状態となります。
後述するテーブル別に操作を限定する処理を絶対に入れてください。
限られたネットワークからしか使わないならこのままでも良いかもしれませんが基本推奨はしません。
PHPフレームワークでphp-crud-api使う準備
一般的なPHPフレームワークに組み込む事も出来ます。
Laravelに組み込む場合は、php-crud-apiのドキュメントにも記述してあるので、そちらを読めばOKそうです。
ただ、僕はWebAPI開発用にLaravelを使うとそれだけで処理時間がかかってしまうのであまり好ましくないと考えます。
シンプルならPHPフレームワークなら僕はいまだに高速フレームワークであるCodeIgniterをオススメします。
ネットでよくみるPHPフレームワークランキングだけでPHPフレームワークを決めてはいけないな。
どの記事もLaravelが1位だけど、LaravelよりCodeIgniterの方が圧倒的に軽くて速い。僕は中規模案件までならCodeIgniter推しだ。 pic.twitter.com/c6LjMoyjF1
— スズキタカフミ@欲しがり屋さん (@kahoo365) October 1, 2019
ということで、ここではCodeIgniterに組み込む場合のサンプルコードです。
といっても結局はコントローラの処理のところにphp-crud-apiを呼び出すコードを書くだけです。
<?php
require_once "path/to/your/vendor/autoload.php";
use Tqdev\PhpCrudApi\Api;
use Tqdev\PhpCrudApi\Config;
use Tqdev\PhpCrudApi\RequestFactory;
use Tqdev\PhpCrudApi\ResponseUtils;
/**
* APIコントローラー
*/
class Api extends CI_Controller {
public function crud(){
// Request取得
$request = RequestFactory::fromGlobals();
// Api構成
$config = new Config([
'username' => '[db user name]',
'password' => '[db password]',
'database' => '[db scheme name]',
'basePath' => '/api/crud', // このメソッドまでのパスを指定する
]);
$api = new Api($config);
// Resopnse取得
$response = $api->handle($request);
// Response出力
ResponseUtils::output($response);
}
}
PHPフレームワークなので、大抵composerを使っていると思います。
なので、autoload.phpをロードして、コントローラーの部分でphp-crud-apiを呼び出すコードを書きます。
ポイントはbasePath
です。
Configに渡すパラメータとしてbasePathにこのコントローラーを呼び出すまでのURLパスを記述します。
これにより、
https://www.example.com/api/data/records/users/
というURLでusersテーブルを読み出すAPIを呼び出すことが出来ます。
php-crud-apiの制限事項
php-crud-apiで呼び出せるテーブルには制限事項があります。
- 主キーは、オートインクリメントまたはUUIDである必要があります
- 複合主キーまたは外部キーはサポートされていません
- 複雑な書き込み(トランザクション)はサポートされていません
- 関数を呼び出す複雑なクエリ(「concat」や「sum」など)はサポートされていません
- データベースは外部キー制約をサポートおよび定義する必要があります
特に気をつけるべきなのは主キーがオートインクリメントって点でしょうか。
主キーがBIGINTでAUTO INCREMENTにしているテーブル等がphp-crud-apiで呼び出せます。
php-crud-apiの操作方法
php-crud-apiの使い方を説明していきます。
例として以下のようなテーブルがあると仮定します。
shop
======
id
name
address
phonenumber
created
基本的CRUD操作
基本的なCRUD処理の説明です。
Create:データを新規作成する
データを新規作成するには/records/
の後にその該当テーブル名をURLに入れてPOSTリクエストを送ります。
データはJSON形式で生の文字列データを送ります。
POST /records/shop
送信データ例:
{
"name": "カフーショップ",
"address": "東京都港区999",
"phonenumber": "03-1234-5678"
}
こういったAPIの開発にはPOSTMANというツールが便利です。
例えば上記のようなテストでもrawにチェックを入れてjsonデータを送るだけです。
データ作成のレスポンスはその新規作成されたレコードの主キーの値を返します。
2
Read:複数のデータを取得する
複数のデータを取得するには、/records/
の後にその該当テーブル名をURLに入れてGETリクエストを送ります。
GET /records/shop
レスポンスで複数データを配列で取得出来ます。
{
"records":[
{
"id": 1,
"name": "田中精肉店",
"address": "東京都港区111",
"phonenumber": "03-0000-0000",
"created": "2019-11-10 00:00:00"
},
{
"id": 2,
"name": "カフーショップ",
"address": "東京都港区999",
"phonenumber": "03-1234-5678",
"created": "2019-11-18 20:00:00"
},
{
...
}
]
}
Read:一つのデータを取得する
一つのデータを取得するには、/records/
の後にその該当テーブル名と主キーのIDをURLに入れてGETリクエストを送ります。
GET /records/shop/2
レスポンスはそのレコードのみのデータが返ります。
{
"id": 2,
"name": "カフーショップ",
"address": "東京都港区999",
"phonenumber": "03-1234-5678",
"created": "2019-11-18 20:00:00"
}
Update:データを更新する
更新するには、PUTリクエストで送ります。URLにはその変更したいレコードの主キーを入れます。
PUT /records/shop/2
データは新規作成時と同様生データで送ります。変更したいプロパティだけ送ればOKです。
送信データ例:
{
"name": "カフーショップ2",
}
この場合の戻り値は変更のあった行数です。
1
Delete:データを削除する
データ削除するにはDELETEリクエスト送ります。URLはその削除したいレコードの主キーを入れます。
DELETE /records/posts/1
削除も変更のあった行数です。
1
ただ、クライアントから自由に削除出来るのは怖いので、このような操作許可は後述するauthorization.tableHandler
を使って慎重に設定しましょう。
フィルター操作
このライブラリの真骨頂である。フィルター処理です。
要するに、SQLでいうところのWHERE句ですね。
例えば、上記までのレコード操作をしたテーブルに対して、「東京都」を含むレコードを取得したい時は以下のようがGETリクエストを出します。
GET /records/shop?filter=address,cs,東京都
実際にはURLエンコードが必要ですが、日本語でもいけます。
レスポンスは複数のデータとして返却されます。
{
"records":[
{
"id": 1,
"name": "田中精肉店",
"address": "東京都港区111",
"phonenumber": "03-0000-0000",
"created": "2019-11-10 00:00:00"
},
{
"id": 2,
"name": "カフーショップ",
"address": "東京都港区999",
"phonenumber": "03-1234-5678",
"created": "2019-11-18 20:00:00"
}
]
}
php-crud-apiの操作を限定する
上記まででphp-crud-apiのインストール方法や使う方法は説明しました。
ただ前述の通り、php-crud-apiは単純に設置しただけだと全てのテーブルのCRUD処理が可能なので、テーブル操作を制御するコードを記述する必要があります。
操作できるテーブルを限定する
php-crud-apiを使ってCRUD操作できるテーブルを限定する場合はConfigにtables
というパラメータで記述します。
例えば、CRUD操作可能なテーブルをshopとcompanyの2つに限定する場合は以下のようになります。
$config = new Config([
'username' => '[db user name]',
'password' => '[db password]',
'database' => '[db scheme name]',
'tables' => 'shop,company',
]);
この設定で、userテーブルを読みだそうとすると下記のようなエラーメッセージが返されます。
{"code":1001,"message":"Table 'user' not found"}
このように操作できるテーブルを限定していきます。
読取処理のみ許可して変更操作は禁止する
shopテーブルの中でも読取は許可しても、それ以外の操作は禁止したい時は以下のようにConfigに記述します。
$config = new Config([
'username' => '[db user name]',
'password' => '[db password]',
'database' => '[db scheme name]',
'tables' => 'shop',
'middlewares' => 'authorization',
'authorization.tableHandler' => function ($operation, $tableName) {
if ($tableName == 'shop') {
if (in_array($operation, ["list", "read"])) {
return true;
}
}
return false;
},
]);
middlewares
にauthorization
を記述して、
authorization.tableHandler
でテーブル操作の特定処理を禁止するコードを記述します。
ここではshopテーブルの list操作とread操作は許可するものとして、それ以外は禁止としています。
サンプルコードのように、最後にはfalseを返却して許可できるテーブルと許可できる操作の時だけtrueを返却するようにすれば、ホワイトリストルールとなります。
実装としてはこのような書き方の方が良いでしょう。
実際POSTでデータを作成しようとすると下記のようなエラーメッセージが返されます。
{"code":1001,"message":"Table 'user' not found"}
操作可能なカラムを限定する
あるテーブルの中でも、特定カラムは外に出したくない場合もあるかと思います。
そういうときはConfigに以下のように記述します。
$config = new Config([
'username' => '[db user name]',
'password' => '[db password]',
'database' => '[db scheme name]',
'tables' => 'shop',
'middlewares' => 'authorization',
'authorization.columnHandler' => function ($$operation, $tableName, $columnName) {
if (in_array($columnName, ["created", "updated"])) {
return false;
}
return true;
},
]);
ここではshopテーブルの中でも、システムで扱うデータ作成日・データ更新日は外に見せたくない時に場合を記述しています。
authorization.columnHandler
で特定カラムに対してfalseを返却すればAPIのレスポンスで出すことはなくなります。
ユーザー単位で操作を限定する
会員登録系のアプリなら、ユーザー毎に設定画面などがあり、そのユーザーのみが更新出来るようにしたいかと思います。
そういう時は、先ほどの authorization.tableHandler
の無名関数にuseを使ってユーザーセッションとリクエスト内容の変数を渡してやります。
// ここでセッション等からユーザーデータを取得する
$userData = getUserData();
// クライアントから送信された生データをjsonでデコードする
$userRequest = [];
if ($_SERVER["REQUEST_METHOD"] == "POST") { // 更新の時はPUT判定が必要だよ
$input_raw = file_get_contents('php://input');
$userRequest = json_decode($input_raw, true);
}
$config = new Config([
'username' => '[db user name]',
'password' => '[db password]',
'database' => '[db scheme name]',
'tables' => 'shop',
'middlewares' => 'authorization',
'authorization.tableHandler' => function ($operation, $userRequest) use($userData, $userRequest){
if ( $userData["user_id"] != $userRequest["user_id"] ){
return false;
}
return true;
},
]);
こちらサンプルコードなのでざっくりと書いてますが、要はチェック処理の為の変数をuseで渡すといった感じです。
まぁ、Configに渡さずとももうその時点でユーザーが違うというエラーがわかっているので、先に例外吐いてエラーレスポンスを返してもいいかもしれません。
おまけ:jQueryからCreateやPutを送るには?
php-crud-apiにCreate(POST)やUpdate(PUT)を送る時のデータはjsonデータを生データ(文字列として)で送る必要があります。
jQueryでCreateクエリを送るサンプルコードも記しておきます。
var url = "https://www.example.com/api/records/shop";
$.ajax({
url: url,
type: 'POST',
contentType: "application/json",
processData: false,
data: JSON.stringify({
"name": "カフーショップ",
"address": "東京都港区999",
"phonenumber": "03-1234-5678"
}),
dataType: "json"
}).done(function(res){
// res は 主キーの値です
});
ミソはprocessData
をfalse
にしてデータ加工しないで、data
にはJSON.stringfy
でオブジェクト形式をjson文字列形式にすることです。
そしてcontentTypeをapplication/json
にします。
このリクエストが成功するとresには主キーのオートインクリメントのIDが返ってきます。
因みに、x-www-form-urlencoded
送信もサポートしてるそうですが、どうにもうまくいかなかったです。
json生データ送信が基本みたいなので、上記方法で問題ないかと思います。
おまけ:POST内容をAPI側で変更するには?
php-crud-apiは設置するだけでCRUD処理が出来てしまいますが、APIによってはサーバー側でデータを追加したり・変更した上でDBに格納したいケースもありますよね。
そういう時はphp-crud-apiのcustomization
を使います。
例えば管理画面からユーザーを新規作成するケースでユーザーIDを別途付け加える時は以下のように書きます。
$config = new Config([
'username' => '[db user name]',
'password' => '[db password]',
'database' => '[db scheme name]',
'tables' => 'users',
'middlewares' => 'middlewares',
'customization.beforeHandler' => function ($operation, $tableName, $request, $environment) {
// このようにリクエスト内容を変える事も出来る
if ($tableName == "user") {
if ( $operation == "create" ){
// requestのgetParsedBodyからPOST内容を取得出来る
$body = $request->getParsedBody();
$body->user_id = "xxxxxxxxx"; // ここでユーザーIDを追加
$request->withParsedBody($body); // requestにwithParsedBodyでまた格納する
}
}
},
]);
これを使えば色々活用出来そうですね。
その他の機能
php-crud-apiにはその他の機能として以下を有しています。
- omposerインストールまたは単一のPHPファイル、展開が簡単。
- 非常に少ないコードで、適応と保守が簡単
- 入力としてPOST変数をサポート(x-www-form-urlencoded)
- 入力としてJSONオブジェクトをサポート
- 入力としてJSON配列をサポート(バッチ挿入)
- コールバックを使用して入力をサニタイズおよび検証する
- データベース、テーブル、列、およびレコードの許可システム
- マルチテナントのシングルおよびマルチデータベースレイアウトがサポートされています
- クロスドメインリクエストのマルチドメインCORSサポート
- 複数のテーブルから結合された結果の読み取りをサポート
- 複数の条件での検索サポート
- ページネーション、ソート、トップNリストおよび列選択
- ネストされた結果(belongsTo、hasMany、およびHABTM)を使用した関係検出
- PATCHを介したアトミックインクリメントのサポート(カウンター用)
- base64エンコーディングでサポートされるバイナリフィールド
- WKTおよびGeoJSONでサポートされる空間/ GISフィールドとフィルター
- OpenAPIツールを使用してAPIドキュメントを生成する
- JWTトークンまたはユーザー名/パスワードによる認証
- データベース接続パラメーターは認証に依存する場合があります
- JSONでのデータベース構造の読み取りのサポート
- RESTエンドポイントを使用したデータベース構造の変更のサポート
- セキュリティ強化ミドルウェアが含まれています
- 標準準拠:PSR-4、PSR-7、PSR-12、PSR-15、PSR-17
これを見て、先日AWSのCognitoのJWT認証に苦労したのでこのライブラリにJWT認証があるとのことで正直小躍りしたのですが、CognitoのJWT認証にはこのライブラリでは出来なさそうでした。。。
CognitoのJWT認証を連携するとしたら、
僕の書いたCognitoの認証処理の記事の内容をもとに、php-crud-apiに渡す前に事前認証をして、useを使ってtableHandlerとかにユーザーデータを渡す等の方法がよいのかと思います。
まとめ
このphp-crud-apiだけで今までのAPI開発が馬鹿みたいに爆速で実装出来ます。
ここではフィルター処理しか紹介していませんが、順番指定や、内部結合とかも出来ます。
個人的にこのライブラリを使う最大のメリットと思うのは、クライアント側から欲しいデータをデザイン出来ることだと思います。
ただ、このphp-crud-apiを使う上で一番気をつけるべきなのは、何度も書いてますがデフォルトだと全テーブルがCRUD出来てしまう点ですね。
使用の際にはしっかりと対象テーブルの絞り込み・許可される操作の限定・許可カラムの限定、を記述しましょう。
このあたりしっかり制御すれば存分に使えるライブラリかと思います。
以上、カフーブログの提供でお送りしました。