Slim マイクロフレームワークで REST アプリケーションを作成する

PHP のマイクロフレームワーク Slim を使用して、REST API のプロトタイピングとデプロイを効率的に行う

Slim は、PHP アプリケーションを開発するための機能を完備したオープンソースのマイクロフレームワークです。Slim はその高度な URL ルーターおよびミドルウェア・アーキテクチャーから、静的 Web サイトや API プロトタイピングにとって最適なフレームワークとなっています。この記事では、Slim について詳しく探り、Slim を使用して認証および各種のリクエスト/レスポンス・フォーマットに対応する REST API を迅速に作成してデプロイする方法を説明します。

Vikram Vaswani, Founder, Melonfire

Ảnh của Vikram VaswaniVikram Vaswani は、オープンソースのツールと技術を専門とするコンサルティング・サービス会社、Melonfire の創業者で、現在 CEO を務めています。彼は、『Zend Framework: A Beginners Guide』および『PHP: A Beginners Guide』の著者でもあります。



2013年 11月 07日

はじめに

数年前までは、PHP アプリケーションの開発にフレームワークを使用することは、習慣にはなっておらず、例外的なことでした。今では CakePHPSymfonyCodeIgniterZend Framework などのフレームワークが PHP によるアプリケーション開発で広く使用されるようになっており、それぞれのフレームワークにアプリケーション開発を効率化および単純化する独自の機能が用意されています。

本格的なフレームワークを使うまでのことはない場合はよくあります。例えば、Web アプリケーションのプロトタイプを作成する場合や、間に合わせの CRUD フロントエンドを作成する場合、あるいは基本的な REST API をデプロイする場合を考えてみてください。従来のフレームワークを使用すれば、これらの作業をすべて行うことができますが、フレームワークについて学習して使用できるようになるまでの時間と労力は、これを利用するメリットを上回ってしまう場合が多々あります。そこで検討したいのがマイクロフレームワークです。マイクロフレームワークを使用すれば、本格的なフレームワークが持つパフォーマンスのオーバーヘッドや習得の困難さがなく、迅速に Web アプリケーションの開発とプロトタイピングを実現することができます。

この記事では、Web アプリケーションと API を迅速に開発するために設計された PHP マイクロフレームワークの 1 つ、Slim を紹介します。Slim という名前にだまされないでください。Slim には、高度な URL ルーターも備わっており、ページ・テンプレート、フラッシュ・メッセージ、暗号化クッキー、ミドルウェアなどのサポートも用意されています。しかも、Slim はごく簡単に理解して使用できる上に、ドキュメントも充実しており、熱心な開発者コミュニティーもあります。

この記事では、Slim を使用して単純な REST API を作成するプロセスについて説明します。Slim の URL ルーターで 4 つの基本的な REST メソッドを実装する方法について説明した後、Slim ならではのユニークな機能によって、API 認証や複数のフォーマットのサポートなどの高度な機能を簡単に追加する方法を、具体的な例で説明します。


REST の概要

まずは、REST すなわち Representational State Transfer についての理解を新たにしてください。REST は、メソッドとデータ型をベースとするのではなく、リソースとアクションをベースにするという点で、SOAP とは異なります。リソースとは単に、アクションの実行対象であるオブジェクトまたはエンティティー (/users、/photos など) を参照する URL に過ぎません。アクションは、以下の 4 つの HTTP 動詞のうちの 1 つです。

  • GET (取得)
  • POST (作成)
  • PUT (更新)
  • DELETE (削除)

この仕組みを理解するための単純な例として、ファイル共有アプリケーションがあるとします。このアプリケーションには、開発者がリモートからこのアプリケーションのデータ・ストアに新しいファイルを追加したり、データ・ストアから既存のファイルを取得したりするための API メソッドが必要です。REST の手法では、URL エンドポイント (/files など) を公開し、この URL にアクセスするために使用された HTTP メソッドを調べて必要なアクションを判断します。例えば、新しいファイルを作成するには /files に対して HTTP パケットが POST 送信され、使用可能なファイルのリストを取得するには /files に対して GET でリクエストが送信されます。

このように既存の HTTP 動詞を CRUD 操作にマッピングする手法は、すんなりと理解することができます。さらに、この手法であれば、リクエスト/レスポンス・ヘッダーにデータ型を正式に定義する必要はないことから、リソースの使用量も少なくなります。

URL リクエストに関する一般的な REST 規約とその意味は以下のとおりです。

  • GET /items: すべての項目のリストを取得する
  • GET /items/123: 項目 123 を取得する
  • POST /items: 新しい項目を作成する
  • PUT /items/123: 項目 123 を更新する
  • DELETE /items/123: 項目 123 を削除する

Slim の URL ルーターは、開発者が「リソースの URI を特定の HTTP リクエスト・メソッド (GET、POST、PUT、DELETE など) のコールバック関数にマッピングする」作業を支援します。これらのコールバック・メソッドを定義して、そこに適切なコードを実装するだけで、新しい REST API を定義することができます。これこそまさに、この記事でこれから行おうとしていることです。


アプリケーション・スタブの作成

REST API の実装に取り掛かる前に、いくつかの注意事項を確認しておきましょう。まず、この記事では一貫して、読者の皆さんが、実際に使える開発環境 (Apache、PHP、および MySQL) をお持ちであること、そして SQL と XML の基礎を十分に理解していることを前提とします。さらに、この記事の説明は、お使いの Apache Web サーバーが仮想ホスト、URL 書き換え、ならびに PUT リクエストと DELETE リクエストをサポートするように構成されているという前提で進めます。

REST API は、リソースを扱うように設計されています。このサンプル・アプリケーションでのリソースは記事であり、それぞれの記事に、タイトル、URL、日付、そして一意の ID があります。これらのリソースは MySQL データベースに格納されます。今回の記事で開発するサンプル REST API は、開発者が通常の REST 規約を使用して、記事を取得、追加、削除、更新できるようにするためのものです。本記事の大部分では、リクエストとレスポンスの本体が JSON であることを前提とします。XML でのリクエストとレスポンスを処理する方法についての詳細は、後ほど説明する「複数のレスポンス・フォーマットのサポート」を参照してください。

ステップ 1: アプリケーションのディレクトリー構造を作成する

カレント・ディレクトリーを Web サーバーのドキュメント・ルート・ディレクトリー (通常、Linux では /usr/local/apache/htdocs、Windows では C:\Program Files\Apache\htdocs) に変更し、サンプル・アプリケーション用の新しいサブディレクトリーを作成します。このディレクトリーに、slim/ という名前を付けます。

shell> cd /usr/local/apache/htdocs
shell> mkdir slim

本記事では一貫して、このディレクトリーを $SLIM_ROOT として参照します。

ステップ 2: Slim フレームワークをダウンロードする

次に、Slim を追加する必要があります。PHP の依存関係管理ツールである Composer を使用している場合は、$SLIM_ROOT/composer.json ファイルを作成して、そのファイルに以下の内容を含めるようにします。

{
    "require": {
        "slim/slim": "2.*"
    }
}

Composer を使用して Slim をインストールするには、以下のコマンドを実行します。

shell> php composer.phar install

Slim をロードするために、アプリケーションの index.php ファイルに以下の行を追加します。

<?php
require 'vendor/autoload.php';

Composer を使用しない場合には、手動で Slim フレームワークをダウンロードし (ダウンロード・リンクについては「参考文献」を参照)、ダウンロード・アーカイブに含まれている Slim ディレクトリーを PHP インクルード・パスのディレクトリーまたは $SLIM_ROOT/Slim に解凍します。

ステップ 3: サンプル・データベースを初期化する

次のステップは、アプリケーションのデータベースを初期化することです。それには以下のコードを使用して、記事のレコードを保持するための「articles」という名前の新規 MySQL テーブルを作成します。

CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` text NOT NULL,
  `url` text NOT NULL,
  `date` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8

作業を開始できるように、このテーブルにレコードをいくつか追加します。

INSERT INTO `articles` (`id`, `title`, `url`, `date`) VALUES 
(1, 'Search and integrate Google+ activity streams with PHP applications', 
'http://www.ibm.com/developerworks/xml/library/x-googleplusphp/index.html', '2012-07-10');

INSERT INTO `articles` (`id`, `title`, `url`, `date`) VALUES 
(2, 'Getting Started with Zend Server CE', 
'http://devzone.zend.com/1389/getting-started-with-zend-server-ce/', '2009-03-02');

この MySQL データベースを操作するには、低フットプリントの ORM ライブラリーである RedBeanPHP もダウンロードする必要があります。このライブラリーは、PHP スクリプトの中で簡単にインクルードすることができる単一ファイルとして入手することができます。忘れずに、このファイルを PHP インクルード・パスのディレクトリーまたは $SLIM_ROOT/RedBean にコピーしてください。

ステップ 4: 仮想ホストを定義する

アプリケーションに簡単にアクセスできるようにするために、新しい仮想ホストを定義し、その仮想ホストを作業ディレクトリーに設定します。このステップはオプションですが、特に複数のアプリケーションを同時に実行している開発マシンで作業する場合には、このステップを実行することを推奨します。こうすることによって、ターゲット・デプロイメント環境により近いレプリカが作成されるためです。Slim には、index.php の接頭辞を削除した簡潔な URL を使用できるようにする .htaccess ファイルも用意されています。

アプリケーションに対する仮想ホストを指定してセットアップするには、Apache 構成ファイル (httpd.conf または httpd-vhosts.conf) を開いて以下の行を追加します。

NameVirtualHost 127.0.0.1
<VirtualHost 127.0.0.1>
    DocumentRoot "/usr/local/apache/htdocs/slim"
    ServerName example.localhost
</VirtualHost>

上記の行によって、$SLIM_ROOT に対応するドキュメント・ルートを持つ新しい仮想ホスト http://example.localhost/ が定義されます。Web サーバーを再起動して、この新しい設定を有効にしてください。注意する点として、この新規ホストを認識させるために、ネットワークのローカル DNS サーバーを更新しなければならない場合もあります。

以上の作業が完了したら、ブラウザーで http://example.localhost/ にアクセスしてください。すると、図 1 のようなページが表示されるはずです。

図 1. デフォルトの Slim アプリケーション index ページ
デフォルトの Slim アプリケーション index ページのスクリーン・キャプチャー

GET リクエストの処理

Slim は、HTTP メソッドとエンドポイントに対し、ルーター・コールバックを定義することで動作します。そのために必要なことは、これに対応するメソッド (例えば、GET リクエストの場合は get()、POST リクエストの場合は post()、等々) を呼び出し、そのメソッドの最初の引数として、突き合わせの対象となる URL ルートを渡すことのみです。メソッドの最後の引数である関数では、ルートが受信されるリクエストと一致したときに実行するアクションを指定します。

標準的な REST API は、2 つのタイプの GET リクエストをサポートします。1 つはリソースのリストを取得するためのリクエスト (GET /articles)、そしてもう 1 つは、特定のリソースを取得するためのリクエスト (GET /articles/123) です。まずは、最初のシナリオに対処するためのコードを作成します。つまり、GET /articles リクエストに対し、入手可能なすべての記事のレコードのリストを返すコードです。

$SLIM/index.php ファイルを、リスト 1 のコードで更新します。

リスト 1. 複数リソースの GET リクエスト・ハンドラー
<?php
// load required files
require 'Slim/Slim.php';
require 'RedBean/rb.php';

// register Slim auto-loader
\Slim\Slim::registerAutoloader();

// set up database connection
R::setup('mysql:host=localhost;dbname=appdata','user','pass');
R::freeze(true);

// initialize app
$app = new \Slim\Slim();

// handle GET requests for /articles
$app->get('/articles', function () use ($app) {  
  // query database for all articles
  $articles = R::find('articles'); 
  
  // send response header for JSON content type
  $app->response()->header('Content-Type', 'application/json');
  
  // return JSON-encoded response body with query results
  echo json_encode(R::exportAll($articles));
});

// run
$app->run();

リスト 1 では、Slim クラスと RedBean クラスを読み込むとともに、これ以外の Slim クラスも必要に応じて読み込まれるように、Slim 自動ローダーを登録しています (Composer を使用する場合、Slim を要求する必要も、Slim の自動ローダーを登録する必要もないことに注意してください)。次に、RedBeanPHP の R::setup() メソッドが、該当する資格情報を渡すことによって、MySQL データベースへの接続を開き、R::freeze() メソッドが RedBean から伝搬される変更に対してデータベース・スキーマをロックします。そして最後に、新しい Slim アプリケーション・オブジェクトが初期化されます。このオブジェクトが、ルーター・コールバックを定義するための主要な制御ポイントとしての役割を果たします。

次のステップでは、HTTP メソッドとエンドポイントに対して、ルーター・コールバックを定義します。リスト 1 では、アプリケーション・オブジェクトの get() メソッドを呼び出し、最初の引数として URL ルート ‘/articles’ を渡します。最後の引数として渡される匿名関数は、RedBeanPHP の R::find() メソッドを使用して articles データベース・テーブルからすべてのレコードを取得し、それらのレコードを R::exportAll() メソッドによって PHP 配列に変換してから、その配列を JSON にエンコードしたレスポンス本体として呼び出し側に返します。Slim のレスポンス・オブジェクトは、header() メソッドも公開しています。このメソッドは、任意のレスポンス・ヘッダーを設定するために使用することができます。リスト 1 では、クライアントがレスポンスを JSON レスポンスとして解釈するように、header() メソッドで Content-Type ヘッダーを設定しています。

図 2 に、このエンドポイントに対するリクエストの結果を示します。

図 2. JSON フォーマットの GET リクエストとそのレスポンス
JSON フォーマットの GET リクエストとそのレスポンスのスクリーン・キャプチャー

header() メソッドの代わりに $app->contentType() ヘルパー・メソッドを使用することもできる点に注意してください。その場合、リクエストのコンテンツ・タイプに直接アクセスすることも、Slim のレスポンス・オブジェクトを配列として扱って (このヘルパー・メソッドは ArrayAccess インターフェースを実装します)、ヘッダーを設定することもできます。

同様に、URL ルート ‘/articles/:id’ に対するコールバックを追加することで、特定のリソースに対する GET リクエストを処理することができます。リスト 2 に、その場合のコードを示します。

リスト 2. 単一リソースの GET リクエスト・ハンドラー
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

class ResourceNotFoundException extends Exception {}

// handle GET requests for /articles/:id
$app->get('/articles/:id', function ($id) use ($app) {    
  try {
    // query database for single article
    $article = R::findOne('articles', 'id=?', array($id));
    
    if ($article) {
      // if found, return JSON response
      $app->response()->header('Content-Type', 'application/json');
      echo json_encode(R::exportAll($article));
    } else {
      // else throw exception
      throw new ResourceNotFoundException();
    }
  } catch (ResourceNotFoundException $e) {
    // return 404 server error
    $app->response()->status(404);
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
});

// run
$app->run();

Slim は、URL から自動的にルート・パラメーターを抽出することができます。リスト 2 でこの機能が示されているのは、GET リクエストから :id パラメーターを抽出して、それをコールバック関数に渡しているところです。このパラメーターに対応するリソースのレコードは、関数の中で R::findOne() メソッドが取得します。取得されたレコードは、JSON にエンコードされたレスポンス本体としてクライアントに返されます。指定された ID が無効であり、一致するリソースが見つからない場合、コールバック関数はカスタム ResourceNotFoundException をスローします。すると、try-catch ブロックが、この例外を「404 Not Found」サーバー・レスポンスに変換します。

図 3 に、リクエストが成功した場合の結果を示します。

図 3. JSON フォーマットの GET リクエストとレスポンス (成功した場合)
JSON フォーマットの GET リクエストとレスポンス (成功した場合) のスクリーン・キャプチャー

図 4 に、リクエストが失敗した場合の結果を示します。

図 4. JSON フォーマットの GET リクエストとレスポンス (失敗した場合)
JSON フォーマットの GET リクエストとレスポンス (失敗した場合) のスクリーン・キャプチャー

Slim はルート条件もサポートするため、開発者はルート・パラメーターとして正規表現を指定することができます。これらのパラメーターが一致しなければ、ルート・コールバックは実行されません。以下のコードに示すように、ルートごとに条件を指定することができます。

<?php
$app->get('/articles/:id', function ($id) use ($app) {    
  // callback code
})->conditions(array('id' => '([0-9]{1,}'));

あるいは、以下のように、setDefaultConditions() メソッドを使用してグローバルに条件を指定することもできます。

<?php
// set default conditions for route parameters
\Slim\Route::setDefaultConditions(array(
  'id' => '[0-9]{1,}',
));

Slim アプリケーションが公開しているメソッドには notFound() もあります。一致するルートが見つからない状況では、このメソッドを使用してカスタム・コードを定義することができます。このメソッドは、カスタム例外をスローして手動で「404」サーバー・レスポンスを設定する方法に代わる手段となります。


POST リクエストの処理

POST リクエストを処理するとなると、少し複雑になってきます。通常の REST 規約では、POST リクエストは新規リソースを作成します。そのために使用されるのは、そのリソースに必要なすべての入力 (このサンプル・アプリケーションの場合は、著者とタイトル) が含まれたリクエスト本体です。このリクエスト本体を、コールバック関数がデコードし、RedBean オブジェクトに変換し、データベースに保存してから、新しく作成したリソースの JSON エンコード表現を返す必要があります。

リスト 3 に、POST リクエストを処理する場合のコードを記載します。

リスト 3. POST リクエスト・ハンドラー
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

// handle POST requests to /articles
$app->post('/articles', function () use ($app) {    
  try {
    // get and decode JSON request body
    $request = $app->request();
    $body = $request->getBody();
    $input = json_decode($body); 
    
    // store article record
    $article = R::dispense('articles');
    $article->title = (string)$input->title;
    $article->url = (string)$input->url;
    $article->date = (string)$input->date;
    $id = R::store($article);    
    
    // return JSON-encoded response body
    $app->response()->header('Content-Type', 'application/json');
    echo json_encode(R::exportAll($article));
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
.});

// run
$app->run();

リスト 3 では、コールバック関数がリクエスト・オブジェクトの getBody() を使って (JSON にエンコードされたパケットになることが前提の) リクエストの本体を取得し、それを json_decode() 関数によって PHP オブジェクトに変換します。続いて、新しい RedBeanPHP 記事オブジェクトがこの PHP オブジェクトのプロパティーで初期化され、R::store() メソッドによってデータベースに格納されます。その後、この新規オブジェクトが配列にエクスポートされて、JSON パケットとしてクライアントに返されます。

図 5 に、POST リクエストおよびレスポンスの一例を示します。

図 5. JSON フォーマットの POST リクエストとレスポンス
JSON フォーマットの POST リクエストとレスポンスのスクリーン・キャプチャー

PUT リクエストと DELETE リクエストの処理

PUTリクエストは既存のリソースに対する変更を指示するために使用されることから、リクエストのストリングにはリソース ID が組み込まれます。成功した PUT とは、既存のリソースが PUT リクエスト本体に指定されたリソースによって置き換えられたことを意味します。成功した PUT に対するレスポンスは、レスポンス本体に更新後のリソースの表現を含めた「200 (OK)」ステータス・コードにすることも、レスポンス本体が空の「204 (No Content)」ステータス・コードにすることもできます。

リスト 4 に、PUT リクエストを処理するためのコードを記載します。

リスト 4. PUT リクエスト・ハンドラー
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

// handle PUT requests to /articles/:id
$app->put('/articles/:id', function ($id) use ($app) {    
  try {
    // get and decode JSON request body
    $request = $app->request();
    $body = $request->getBody();
    $input = json_decode($body); 
    
    // query database for single article
    $article = R::findOne('articles', 'id=?', array($id));  
    
    // store modified article
    // return JSON-encoded response body
    if ($article) {      
      $article->title = (string)$input->title;
      $article->url = (string)$input->url;
      $article->date = (string)$input->date;
      R::store($article);    
      $app->response()->header('Content-Type', 'application/json');
      echo json_encode(R::exportAll($article));
    } else {
      throw new ResourceNotFoundException();    
    }
  } catch (ResourceNotFoundException $e) {
    $app->response()->status(404);
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
});

// run
$app->run();

リスト 4 では、URL ルートの一部として指定された ID を使用して、それに対応するリソースを RedBeanPHP オブジェクトとしてデータベースから取得します。(JSON にエンコードされたパケットになることが前提の) リクエストの本体は、json_decode() 関数によって PHP オブジェクトに変換され、そのプロパティーを使用して RedBeanPHP オブジェクトのプロパティーが上書きされます。変更後のオブジェクトはデータベースに再び保存され、JSON 表現が「200」サーバー・レスポンス・コードとともに呼び出し側に返されます。指定された ID が既存のリソースと一致しない場合には、カスタム例外がスローされ、「404」サーバー・エラー・レスポンスがクライアントに送信されます。

図 6 に、PUT リクエストおよびレスポンスの一例を示します。

図 6. JSON フォーマットの PUT リクエストとレスポンス
JSON フォーマットの PUT リクエストとレスポンスのスクリーン・キャプチャー

データ・ストアから指定されたリソースの削除を行うための DELETE リクエストを処理するコールバックを作成することもできます。リスト 5 に必要なコードを記載します。

リスト 5. DELETE リクエスト・ハンドラー
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

// handle DELETE requests to /articles/:id
$app->delete('/articles/:id', function ($id) use ($app) {    
  try {
    // query database for article
    $request = $app->request();
    $article = R::findOne('articles', 'id=?', array($id));  
    
    // delete article
    if ($article) {
      R::trash($article);
      $app->response()->status(204);
    } else {
      throw new ResourceNotFoundException();
    }
  } catch (ResourceNotFoundException $e) {
    $app->response()->status(404);
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
});

// run
$app->run();

リスト 4 の場合と同じく、リスト 5 でも URL に含まれるリソース ID を使用してデータベースから対応するリソースをオブジェクトとして取得し、R::trash() メソッドを使用してそのオブジェクトを完全に削除します。成功した DELETE リクエストに対するレスポンスは、レスポンス本体にステータスを含んだ「200 (OK)」、またはレスポンス本体が空の「204 (No Content)」のいずれかにすることができます。リスト 5 でのレスポンスは、後者です。

図 7 に、DELETE リクエストと API レスポンスの一例を示します。

図 7. JSON フォーマットの DELETE リクエストとレスポンス
JSON フォーマットの DELETE リクエストとレスポンスのスクリーン・キャプチャー

ルート・ミドルウェアによる認証の追加

Slim は柔軟なルーティングに加え、いわゆる「ルート・ミドルウェア」もサポートしています。簡単に言うと、ミドルウェアとは、対応するルートに対するコールバック関数の前に呼び出される 1 つ以上のカスタム関数のことです。ミドルウェアを使用することで、リクエストに対するカスタム処理を実行することができます。Ephemera のブログ投稿「Rack Middleware Use Case Examples」で説明しているように、ミドルウェアは「…リクエストの操作、レスポンスの操作、処理全体の停止、あるいはロギングなどのまったく関係ないことも実行することができます」(この Ephemera の完全なブログ投稿へのリンクについては、「参考文献」を参照してください)。

Slim のミドルウェア・アーキテクチャーがどのように機能するかを説明するために、一般的な API 要件である認証について考えてみます。ミドルウェアを使用する場合、カスタム認証メソッドを介してリクエストを渡すという方法により、API リクエストが適切に認証されてからでないと、そのリクエストを処理できないようにすることが可能です。

リスト 6 では、単純な認証関数を例示し、この関数をミドルウェアとして GET リクエスト・ハンドラーに追加します。

リスト 6. 認証ミドルウェア
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

// route middleware for simple API authentication
function authenticate(\Slim\Route $route) {
    $app = \Slim\Slim::getInstance();
    if (validateUserKey() === false) {
      $app->halt(401);
    }
}

function validateUserKey($uid, $key) {
  // insert your (hopefully complex) validation routine here
}

// handle GET requests for /articles
$app->get('/articles', 'authenticate', function () use ($app) {  
  // query database for all articles
  $articles = R::find('articles'); 
  
  // send response header for JSON content type
  $app->response()->header('Content-Type', 'application/json');
  
  // return JSON-encoded response body with query results
  echo json_encode(R::exportAll($articles));
});

// run
$app->run();

リスト 6 のサンプル authenticate() 関数は、GET ルート・ハンドラー内からミドルウェアとして参照され、自動的にルート・オブジェクトを引数として取ります。この関数は、Slim::getInstance() メソッドを使用して Slim アプリケーション・オブジェクトにアクセスすることもできます。

上記のコードにより、ユーザーが URL ‘/articles’ にリクエストを送信すると、そのリクエストが処理される前に authenticate() 関数が呼び出されるようになります。この関数は、カスタム検証ルーチンを使用してリクエストを認証し、検証ルーチンが true を返した場合にのみ、以降の処理を許可します。検証に通らなかった場合、authenticate() 関数は処理を停止して、クライアントに「401 Authorization Required」レスポンスを送信します。

リスト 7 に、さらに具体的な authenticate() メソッドの実装例として、ユーザー ID ‘demo’ と API キー ‘demo’ が設定されたクッキーを調べる方法を示します。これらのクッキーを設定するために、新しい API メソッド (/demo) が呼び出されます。このメソッドは、クライアントに 5 分間限定で API への一時アクセスを許可します。

リスト 7. 認証ミドルウェアの単純な実装
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

// route middleware for simple API authentication
function authenticate(\Slim\Route $route) {
    $app = \Slim\Slim::getInstance();
    $uid = $app->getEncryptedCookie('uid');
    $key = $app->getEncryptedCookie('key');
    if (validateUserKey($uid, $key) === false) {
      $app->halt(401);
    }
}

function validateUserKey($uid, $key) {
  // insert your (hopefully more complex) validation routine here
  if ($uid == 'demo' && $key == 'demo') {
    return true;
  } else {
    return false;
  }
}

// handle GET requests for /articles
$app->get('/articles', 'authenticate', function () use ($app) {  
  // query database for all articles
  $articles = R::find('articles'); 
  
  // send response header for JSON content type
  $app->response()->header('Content-Type', 'application/json');
  
  // return JSON-encoded response body with query results
  echo json_encode(R::exportAll($articles));
});

// generates a temporary API key using cookies
// call this first to gain access to protected API methods
$app->get('/demo', function () use ($app) {    
  try {
    $app->setEncryptedCookie('uid', 'demo', '5 minutes');
    $app->setEncryptedCookie('key', 'demo', '5 minutes');
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
});

// run
$app->run();

この例の場合、クライアントが API エンドポイント ‘/demo’ に対するリクエストを送信すると、5 分間有効な資格情報 ‘demo’ が設定された暗号化クッキーが設定されます。これらのクッキーは、同じホストに対する以降のリクエストに自動的に付加されます。‘/articles’ URL エンドポイントを対象とした GET ハンドラーに渡されている authenticate() ミドルウェア関数は、GET リクエストを受信するたびにそれらのリクエストのクッキーを調べてから、validateUserKey() 関数を使用して、ユーザーの資格情報の検証 (この例では、単純に値 ‘demo’ の有無を調べるだけです) を行います。クッキーの有効期限が切れると、validateUserKey() 関数は false を返します。それにより、authenticate() ミドルウェアはリクエストを終了して、‘/articles’ URL エンドポイントへのアクセスを「401」サーバー・エラーとして禁止します。

図 8 に、認証されなかった GET リクエストに対するサーバー・エラーを示します。

図 8. 認証されなかった、JSON フォーマットの GET リクエストとレスポンス
認証されなかった、JSON フォーマットの GET リクエストとレスポンスのスクリーン・キャプチャー

記事に付属のコード・アーカイブは、これと同じミドルウェアを使用して、POST、PUT、DELETE の各リクエストを認証します。このシステムは、説明のみを目的としてこの記事に含めており、こうした明白な理由から、このシステムは特にセキュアというわけではないので、本番環境では決して使用しないでください。

この例から明らかなように、ルート・ミドルウェアはリクエストの事前処理を行うのに重宝するだけでなく、リクエストのフィルタリングやロギングなどといった他のタスクにも使用することができます。


複数のレスポンス・フォーマットのサポート

これまでのセクションでは、単純な JSON ベースの REST API をセットアップする方法を説明してきました。その一方、XML もよく使用されるデータ交換フォーマットであり、REST API で XML のリクエストおよびレスポンス本体をサポートする必要が出てくることもよくあります。

Slim では、リクエスト・ヘッダーにフル・アクセスすることができます。上記の要件を満たす最も簡単な方法の 1 つは、Content-Type リクエスト・ヘッダーを使用して、トランザクションで使用されるデータ・フォーマットを決定することです。リスト 8 について検討してみてください。このリストでは、GET リクエストのコールバック関数が改訂されていて、Content-Type ヘッダーに応じて XML または JSON のいずれかによるリクエスト本体を返すようになっています。

リスト 8. XML および JSON フォーマットをサポートする GET リクエスト・ハンドラー
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

// handle GET requests for /articles
$app->get('/articles', function () use ($app) {
  try {
    // query database for articles
    $articles = R::find('articles'); 
    
    // check request content type
    // format and return response body in specified format
    $mediaType = $app->request()->getMediaType();
    if ($mediaType == 'application/xml') {
      $app->response()->header('Content-Type', 'application/xml');
      $xml = new SimpleXMLElement('<root/>');
      $result = R::exportAll($articles);
      foreach ($result as $r) {
        $item = $xml->addChild('item');
        $item->addChild('id', $r['id']);
        $item->addChild('title', $r['title']);
        $item->addChild('url', $r['url']); 
        $item->addChild('date', $r['date']); 
      }
      echo $xml->asXml();
    } else if (($mediaType == 'application/json')) {
      $app->response()->header('Content-Type', 'application/json');
      echo json_encode(R::exportAll($articles));
    }
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
});

// run
$app->run();

リスト 8 では、リクエストのコンテンツ・タイプを取得するために、リクエスト・オブジェクトの getMediaType() メソッドが使用されています。

  • コンテンツ・タイプが ‘application/json’ のリクエストの場合、記事のリストは前と同じく JSON フォーマットで返されます。
  • コンテンツ・タイプが ‘application/xml’ のリクエストの場合、記事のリストは SimpleXML によって XML 文書としてフォーマット設定されてから、XML フォーマットで返されます。

図 9 に、GET リクエストに対する XML レスポンスを示します。

図 9. XML フォーマットの GET リクエストとレスポンス
XML フォーマットの GET リクエストとレスポンスのスクリーン・キャプチャー

これで終わりではありません。リスト 9 で、POST リクエストの XML サポートも追加します。

リスト 9. XML および JSON フォーマットをサポートする POST リクエスト・ハンドラー
<?php
// do initial application and database setup

// initialize app
$app = new \Slim\Slim();

$app->post('articles', function () use ($app) {    
  try {
    // check request content type
    // decode request body in JSON or XML format
    $request = $app->request();
    $mediaType = $request->getMediaType();
    $body = $request->getBody();
    if ($mediaType == 'application/xml') {
      $input = simplexml_load_string($body);
    } elseif ($mediaType == 'application/json') {    
      $input = json_decode($body); 
    } 
    
    // create and store article record
    $article = R::dispense('articles');
    $article->title = (string)$input->title;
    $article->url = (string)$input->url;
    $article->date = (string)$input->date;
    $id = R::store($article);
    
    // return JSON/XML response
    if ($mediaType == 'application/xml') {
      $app->response()->header('Content-Type', 'application/xml');
      $xml = new SimpleXMLElement('<root/>');
      $result = R::exportAll($article);
      foreach ($result as $r) {
        $item = $xml->addChild('item');
        $item->addChild('id', $r['id']);
        $item->addChild('title', $r['title']);
        $item->addChild('url', $r['url']); 
        $item->addChild('date', $r['date']); 
      }
      echo $xml->asXml();          
    } elseif ($mediaType == 'application/json') {
      $app->response()->header('Content-Type', 'application/json');
      echo json_encode(R::exportAll($article));
    } 
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
});

// run
$app->run();

リスト 9 では、コンテンツ・タイプが ‘application/json’ または ‘application/xml’ のどちらであるかによって、json_decode() または simplexml_load_string() を使用してリクエスト本体が PHP オブジェクトに変換されます。変換後のオブジェクトはデータベースに保存され、リソースの JSON 表現または XML 表現がクライアントに返されます。図 10 に、XML フォーマットの POST リクエストおよびレスポンスを示します。

図 10. XML フォーマットの POST リクエストとレスポンス
XML フォーマットの POST リクエストとレスポンスのスクリーン・キャプチャー

まとめ

Slim は、REST API を作成するための強力かつ拡張可能なフレームワークであり、アプリケーション開発者がサード・パーティーのソフトウェアから直感的なアーキテクチャー・パターンを使用して、アプリケーションの関数に容易にアクセスできるようにします。Slim の URL 突き合わせ機能と URL ルーティング機能は、その軽量の API と各種 HTTP メソッドのサポートと一体となって、Slim を迅速な API プロトタイピングおよび実装を行う上で理想的なフレームワークにしています。

この記事で実装したすべてのコードについては、「ダウンロード」を参照してください。このダウンロード・ファイルには、サンプル API で GET、POST、PUT、および DELETE リクエストを行うために使用できる単純な jQuery ベースのテスト・スクリプトも含まれています。ぜひともコードを入手して、いろいろと試してみてください。実際に新しい機能を追加してみるのも一考です。いろいろと試しても、何も壊れることはなく、よい勉強になること請け合いです。


ダウンロード

内容ファイル名サイズ
Archive of example applicationexample-app-slim-rest-api.zip45KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source
ArticleID=951713
ArticleTitle=Slim マイクロフレームワークで REST アプリケーションを作成する
publish-date=11072013