PHP と MySQL を使用して REST API を作成して IBM Bluemix にデプロイする

2014年 7月 31日
PDF (1196 KB)
 

Build and deploy a REST API on IBM Bluemix with PHP and MySQL

05:20  |  Transcript
author photo - Vikram Vaswani

Vikram Vaswani

Founder and CEO of Melonfire

vikram-vaswani.in

はじめに

 

IBM Bluemix™ にサインアップ
無償のサービス、ランタイム、インフラを含むクラウド・プラットフォームが、新たなモバイルやウェブ・アプリのクイックな構築とデプロイを実現します。

このクラウド・プラットフォームには、無料のサービス、ランタイム、インフラストラクチャーが用意されており、皆さんの次のモバイル・アプリケーションや Web アプリケーションを迅速に作成してデプロイできるように支援します。

オンラインの製品やサービスとやり取りするアプリケーションを構築しているとしたら、データを取得または挿入するために、おそらく REST API を使っていることでしょう。この数年にわたり、REST API の人気はますます高まっています。その理由は、REST API は理解しやすいこと、短時間でコーディングできること、そして HTTP の組み込みサポートによって、あらゆるプログラミング言語で使用できることにあります。

独自のクラウド・サービスを開発する場合、REST API はデータの共有と再利用を促す最適な手段にもなります。REST を使用して、外部の開発者でもデータにアクセスできるようにすれば、それらの開発者は素晴らしい新規のアプリケーションを容易に構築できるようになるとともに、皆さんが提供している製品やデータを利用する興味深い方法を思いつきやすくもなります。Facebook、Twitter、あるいは Instagram を中心に成長しているアプリケーション・エコシステムを考えれば、REST API を使用するメリットは明らかです。

REST を使用して、外部の開発者でもデータにアクセスできるようにすれば、それらの開発者は素晴らしい新規のアプリケーションを容易に構築できるようになるとともに、皆さんが提供している製品やデータを利用する興味深い方法を思いつきやすくもなります。

この記事では、Bullet という PHP マイクロフレームワークを使用して REST API を作成する方法を短期集中コースで習得します。4 つの基本的な REST メソッドを実装する方法に加え、API 認証やマルチフォーマットのサポートといった共通機能のサポートを追加する方法を説明してから、作成した REST API を IBM Bluemix にデプロイする方法を説明します。それでは、早速始めましょう!

REST API の基礎知識

 

読む:REST に関するウィキペディアのページ

まず始めに、別名 Representational State Transfer として知られている REST について理解するために少し時間を取りましょう。REST とは、「リソース」と「アクション」をベースとした API 開発のスタイルの 1 つです。リソースとは単に、アクションの実行対象であるオブジェクトまたはエンティティー (/users、/photos など) を参照する URL に過ぎません。アクションは、以下の 4 つの HTTP 動詞のうちの 1 つです。

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

この仕組みを理解するための例として、ソフトウェアのバグを追跡するために設計されたアプリケーションがあり、アプリケーション・データベース内のデータを簡単に再利用および操作できるようにしたいとします。その場合、URL エンドポイント (例えば /bugs という名前の URL エンドポイント) を公開して、外部の開発者が各種の HTTP メソッドとその対象コンテンツを指定してこのエンドポイントにアクセスできるようにします (例えば、すべてのバグを一覧表示にするには GET /bugs と指定し、バグ #78 を削除するには DELETE /bugs/78 と指定するなど)。

リクエストされている操作は HTTP メソッドとその対象コンテンツに基づいて推定できるので、データに対して適切なアクションを実行することができます。以下にその例を示します。

  • GET /documents: ドキュメントのリストを取得する
  • GET /bugs/123: バグ #123 を取得する
  • POST /photos: POST リクエストの本体を使用して新しい写真を作成する
  • PUT /photos/123: PUT リクエストの本体を使用して写真 #123 を更新する
  • DELETE /orders/123: 注文 #123 を削除する

前提条件

 

この記事で開発するサンプル REST API は、製品情報のデータベースがあることを前提として、一般的な REST 規約に従って製品データを取得、追加、削除、更新できるようにすることに重点を置きます。簡単のため、Product リソースの属性は、固有の ID、名前、価格の 3 つだけに絞ります。さらに、製品データは MySQL データベースに保管されること、API リクエストおよびレスポンスは JSON でエンコードされることを前提とします (ただし、後のほうのセクションで、XML を代わりのフォーマットとして使用する方法についても説明します)。

この記事を読み進めるにあたって必要なものは以下のとおりです。

ステップ 1: アプリケーション・データベースをセットアップする

 

アプリケーション・データベースをセットアップするには、以下の MySQL テーブル定義とサンプル・データを使用します。

  • ローカルに限定して開発およびデプロイする場合は、これを使用して、API が接続する MySQL データベースを初期化することができます。
  • Bluemix にデプロイする場合は、とりあえずこのステップをスキップしてください。ステップ 7 で、MySQL サービス・インスタンスを Bluemix 上で初期化してバインドした後、このステップに戻ります。
CREATE TABLE IF NOT EXISTS `products` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `price` decimal(5,2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `products` (`id`, `name`, `price`) VALUES
(1, 'Garden spade', 15.99),
(2, 'Cotton hammock', 54.50),
(3, 'Single airbed', 35.49);

ステップ 2: Bullet と Eloquent をインストールする

 

次のステップでは、Bullet マイクロフレームワークをダウンロードしてセットアップします。なぜ Bullet を使用するかというと、このマイクロフレームワークには柔軟な URL ルーターが組み込まれていて、カスタム URL に対する各種 HTTP メソッドの作成と、このメソッドに対する応答を簡単に行えるからです。さらに、ネストされたルーティング・コールバックによって、共通の反復タスク (API 認証など) の実行を単純化することもできます。

データベースへのアクセスを容易にするために、Eloquent を使用します。このよく使われているオブジェクト・リレーショナル・マッパー (ORM) は、ActiveRecord 実装を使用して、データベース・レコードを簡単に処理できるようにします。Eloquent は Laravel PHP フレームワークの一部となっていますが、スタンドアロンで使用することもできます。

Bullet と Eloquent をダウンロードしてセットアップするには、Composer という PHP 依存関係マネージャーを使用します。以下に、Composer 構成ファイルを記載します。このファイルを $APP_ROOT/composer.json に保存してください。この記事では一貫して、$APP_ROOT がアプリケーションの作業ディレクトリーを参照します。

{
    "require": {
        "vlucas/bulletphp": "*",
        "illuminate/database": "*"
    }
}

以下のコマンドを実行することで、Composer を使用して Bullet と Eloquent をインストールすることができます。

shell> php composer.phar install

アプリケーションに簡単にアクセスできるようにするために、開発環境内に新しい仮想ホストを定義して、その仮想ホストのドキュメント・ルートを $APP_ROOT に設定するという方法もあります。このステップはオプションですが、こうすると Bluemix でのターゲット・デプロイメント環境に近いレプリカになるため、このステップは実行しておくことをお勧めします。

  • Apache で、アプリケーション用に指定した仮想ホストをセットアップする場合は、Apache 構成ファイル (httpd.conf または httpd-vhosts.conf) を開いて、以下の行を追加します。
	NameVirtualHost 127.0.0.1
	<VirtualHost 127.0.0.1>
	    DocumentRoot "/usr/local/apache/htdocs/api"
	    ServerName api.localhost
	</VirtualHost>
  • nginx で、アプリケーション用に指定した仮想ホストをセットアップする場合は、nginx 構成ファイル (nginx.conf) を開いて、以下の行を追加します。
	server {
	    server_name api.localhost;
	     root /usr/local/apache/htdocs/api;
	     try_files $uri /index.php;
	     
	     location ~ \.php$ {
	        try_files $uri =404;            
	        include fastcgi_params;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # assumes you are using php-fcgi
        fastcgi_pass 127.0.0.1:90;
	    }        
	}

これらの行は、新しい仮想ホスト http://api.localhost/ を定義し、そのドキュメント・ルートとして $APP_ROOT に相当するルートを設定します (必ず、ローカル設定を反映したルートに変更してください)。Web サーバーを再起動すると、新しい設定が有効になります。注意する点として、この新規ホストを認識させるために、ネットワークのローカル DNS サーバーを更新しなければならない場合があります。

ステップ 3: リソース・コレクションの GET リクエストを処理する

 

Bullet の動作は、URL セグメントを 1 つずつ解析し、特定のパスまたはパラメーター・パターンにコールバックを使用して応答するというものです。コールバックは HTTP メソッドごとに定義することができるため、例えば GET リクエストと POST リクエストに対してそれぞれ異なるアクションを実行することができます。

標準的な REST API は、2 つのタイプの GET リクエストをサポートします。1 つは、リソースのコレクションを取得するための リクエスト (GET /products)、そしてもう 1 つは特定のリソースを取得するためのリクエスト (GET /products/123) です。まずは、最初のシナリオに対処するためのコードを作成します。つまり、GET /products リクエストに対し、データベースから入手可能なすべての製品レコードのリストを返すコードです。$APP_ROOT/index.php ファイルを、以下のコードで更新します。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

上記のコードは最初のステップとして、必要なクラスを必要に応じてロードする Composer 自動ローダーを初期化します。次に、アプリケーションを表す新しい Bullet\App オブジェクトを作成します。そして、このアプリケーション・オブジェクトの path() メソッドを使用して、エンドポイント /v1/products のネストされた URL ルートをセットアップします。最も内側にある path() コールバックは、そのエンドポイントでサポートされるメソッドを定義します。この例では、アプリケーション・オブジェクトの get() メソッドを使用して、GET リクエストのハンドラーを定義しています。

ルート・ハンドラーは Eloquent モデルを使用して、データベースからすべての製品レコードを返します。ルート・ハンドラーが返すのは配列であるため、Bullet は自動的にレスポンスのフォーマットが JSON であることを前提として、配列を変換します。変換された配列は、'Content-Type: application/json' ヘッダーと 200 OK 成功コードとともにクライアントに送信されます。エラーが発生した場合は、例外ハンドラーがそれをトラップして、500 Internal Server Error コードとエラー・メッセージが含まれる JSON 本体をクライアントに返します。

上記のリストの最後の行に示されている、アプリケーション・オブジェクトの run() メソッドが、リクエストの受信時に Bullet アプリケーションを実行することによって、このコードのすべてを機能させます。

この時点で、この仕組みを理解できたとしても 1 つの疑問が残っていることでしょう。それは、Eloquent モデルはどこから取得するのかという疑問です。この疑問に対する答えは、次のリストが明らかにしてくれます。下記リストでは、Eloquent を利用するために必要なコードを上記の GET ハンドラーとともに追加しています。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// use Eloquent ORM
use Illuminate\Database\Capsule\Manager as Capsule;  
use Illuminate\Database\Schema\Blueprint as Schema;  
 
// create model for Eloquent ORM mapped to REST API resource
class Product extends Illuminate\Database\Eloquent\Model {
  public $timestamps = false;
}

// get MySQL service configuration from Bluemix
$services = getenv("VCAP_SERVICES");
$services_json = json_decode($services, true);
$mysql_config = $services_json["mysql-5.5"][0]["credentials"];
$db = $mysql_config["name"];
$host = $mysql_config["host"];
$port = $mysql_config["port"];
$username = $mysql_config["user"];
$password = $mysql_config["password"];

// initialize Eloquent ORM
$capsule = new Capsule;
 
$capsule->addConnection(array(
  'driver'    => 'mysql',
  'host'      => $host,
  'port'      => $port,
  'database'  => $db,
  'username'  => $username,
  'password'  => $password,
  'charset'   => 'utf8',
  'collation' => 'utf8_unicode_ci',
  'prefix'    => ''
));

$capsule->setAsGlobal();
$capsule->bootEloquent();


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

Eloquent には、Laravel 外部で Eloquent を簡単に使用できるように設計された、Capsule マネージャー・インスタンスがあります。上記のリストは、この Capsule インスタンスの使用例を示すもので、MySQL データベースへの新しい接続をセットアップし、Capsule インスタンスをグローバルに使用できるようにします。

この記事の目的は、最終的にはアプリケーションを Bluemix にデプロイすることなので、MySQL 接続のクレデンシャルは特殊な Bluemix の環境変数 VCAP_SERVICES から抽出されるようにしています。ただし、ローカルでデプロイする予定の場合は、該当する箇所を単純にローカル・データベース・サーバー固有の値に置き換えて構いません。

Illuminate\Database\Eloquent\Model クラスを継承する Product モデルにも注目してください。この Product モデルが、いくつもの事前定義されたメソッドを提供することで、対応するデータベース・レコードの処理が容易になります。上記のリストでは、これらの事前定義されたメソッドのうちの 1 つ ― Product::all() ― が使用されています。このメソッドは、「データベース内のすべての製品レコードを返す」ための SELECT クエリーの生成を内部で処理します。

/v1/products に対する GET リクエストが成功すると、以下の結果になります。

/v1/products に対する GET リクエスト

ステップ 4: 新規リソースの POST リクエストを処理する

 

読む:HTTP Response Status Codes (HTTP レスポンス・ステータス・コード)

前のステップと同様の方法で、POST リクエストを処理して新規の製品レコードに変換することができます。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // POST /v1/products
    // create new product
    $app->post(function($request) use ($app) {
      $product = new Product();
      $product->name = trim(htmlentities($request->name));
      $product->price = round(trim(htmlentities($request->price)), 2);
      if ($product->name && $product->price) {
        $product->save();
        return $app->response(201, $product->toArray());
      } else {
        return 400;
      }

    });

  });
    
});

echo $app->run(new Bullet\Request());

上記のリストは、/v1/products に対する POST リクエストのハンドラーを新規に追加します。アプケーションは POST リクエストを受信すると、空の Product オブジェクトを作成し、その Product にリクエスト・オブジェクトに含まれる name と price を設定します。その後、モデルの save() オブジェクトを呼び出して、この新規レコードをデータベースに追加します。

レコードが正常に追加されると、ハンドラーは 201 Created ステータス・コードと、新規 Product リソースの JSON 表現を返します。正常に追加されなかった場合は、400 Bad Request または 500 Internal Server Error のいずれかのエラー・コードと、エラーを説明する JSON メッセージ本体を返します。以下に示す、POST リクエストが成功した場合と失敗した場合のレスポンスの例を見てください。

/v1/products に対する POST リクエストが成功した場合/v1/products に対する POST リクエストが失敗した場合

指摘しておく価値のある興味深い点として、新規に作成される Product リソースは、POST 本体に JSON オブジェクトとして格納されて API に渡されます。しかし、POST ハンドラー内では、POST ペイロードをデコードする PHP の json_decode() 関数を使用していません。その理由は、Bullet が POST または PUT リクエスト内で JSON ペイロードを検出すると、このペイロードを自動的にデコードしてくれるからです。これで、リクエスト・オブジェクトのプロパティーとして、JSON オブジェクトのプロパティーをそのまま使用できるようになります。

ステップ 5: 個々のリソースに対する GET、PUT、DELETE リクエストの処理

 

読む:HTTP Response Status Codes (HTTP レスポンス・ステータス・コード)

前のセクションでは、リソース・コレクションに対する GET リクエスト (GET /v1/products) を処理する方法を説明しました。しかし、REST API は、リソース固有の ID を使用して、直接リソースを扱えるようにもなっていなければなりません (例えば、GET /v1/products/123 または DELETE /v1/products/123)。それにより、API を使用して個別のリソースを取得、更新、削除することが可能になります。

Bullet には、静的 URL パス用に設計された先ほどの path() メソッドと同じく、変数が含まれる URL パスを扱えるように設計された param() メソッドもあります。以下のリストに、特定のリソースを対象とする GET リクエスト・ハンドラーのコンテキストで、このメソッドを使用する方法を示します。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
           
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });
           
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());

このリストは、/v1/products/[id] 形式の URL のコールバック・ハンドラーをセットアップします。ここで、[id] は固有のリソース ID を表す可変のパス部分です。さらに 'int' 引数によって、この可変部分を $id 変数に割り当てられる整数として指定しています。

上記のリストでは、URL ルートから取り込んだリソース ID が Product モデルに渡された後、このモデルの find() メソッドを使用して、対応するデータベース・レコードが返されます。すると、Bullet がレコードを自動的に JSON に変換してからクライアントに返します。一致するレコードが見つからない場合、ハンドラーは 404 Not Found エラー・コードを返します。エラーが発生した場合は、例外ハンドラーがそれをトラップして、500 Internal Server Error エラー・コードを返します。

以下に、GET リクエストが成功した場合の結果とそのレスポンスを示します。

/v1/products/:id に対する GET リクエスト

この構造が用意されていれば、特定のリソースに対する DELETE リクエストと PUT リクエストのサポートも極めて簡単に追加することができます。この追加機能を組み込む以下のリストを見てください。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
                
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });

      // PUT /v1/products/:id
      // update product by id
      $app->put(function($request) use($product, $app) {
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(200, $product->toArray());
        } else {
          return 400;          
        }
      });
          
      // DELETE /v1/products/:id
      // delete product by id
      $app->delete(function($request) use($product) {
        $product->delete();
        return 204;
      });
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());
  • PUT リクエストは、PUT リクエスト本体に含まれる更新情報によって既存のリソースを更新することを意味します。上記のリストが定義する PUT ハンドラーは、PUT リクエスト内の JSON ドキュメントを読み取り、Eloquent を使用して取得した Product オブジェクトのプロパティーを更新します。更新されたリソースは、このオブジェクトの save() メソッドによってデータベースに再び保存されます。処理が正常に完了したことを通知する 200 Accepted サーバー・レスポンス・コードと一緒に、更新されたリソースの JSON 表現がクライアントに返されます。
  • DELETE リクエストは、DELETE URL で参照されているリソースをデータストアから削除することを意味します。上記の DELETE ハンドラーは、まさにこの処理を行います。つまり、取得された Product オブジェクトの delete() メソッドを呼び出して、該当する製品レコードを MySQL データベースから削除します。この場合、処理が正常に完了したことは、204 No Content レスポンス・コードと空のレスポンス本体によって示されます。

以下の図に、正常に完了した PUT リクエストと DELETE リクエストを示します。

/v1/products/:id に対する PUT リクエスト/v1/products/:id に対する DELETE リクエスト

ここで触れておきたい興味深い点は、3 つのネストされたルート・ハンドラーはすべて、親ハンドラーで作成された $product インスタンスを使用していることです。親ハンドラーで共通のルーチンまたはオブジェクトを一度定義した後、それらをすべての子クロージャーまたは子ブロックで使用すれば、重複するコードの行数が減り、保守および更新がより簡単にできるようになります。

ステップ 6: 複数のフォーマットをサポートする

 

通常は、JSON フォーマットのリクエストとレスポンスの本体で問題がありませんが、例えば XML のような別のフォーマットもサポートする必要がある場合を考えてみてください。その場合、Bullet では異なるフォーマットごとにカスタム・レスポンダーを定義することができるので、簡単にマルチフォーマットの要件に対処することができます。

一例として、次のリストは /v1/products GET エンドポイントに XML 出力のサポートを追加します。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products[.xml|.json]
    // list all products
    $app->get(function() use ($app)  {

      $products = Product::all();         
      
      // handle requests for XML content
      $app->format('xml', function($request) use($app, $products) {
        return $app->response(200, convert_array_to_xml($products->toArray()))
                      ->header('Content-Type', 'application/xml');
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app, $products) {
        return $products->toArray();
      });  
            
    });
        
  });    
  
});

echo $app->run(new Bullet\Request());

XML 出力と JSON 出力にそれぞれ個別のコールバック・ハンドラーを定義するために、アプリケーション・オブジェクトの format() メソッドを使用しています。Bullet は、URL /v1/products.xml に対するリクエスト、またはヘッダーが 'Accept: application/xml' に設定された、URL /v1/products に対するリクエストを検出すると、自動的に XML コールバック・ハンドラーを呼び出します。後者の場合、Bullet は自動コンテンツ・ネゴシエーションによって、要求されたフォーマットを理解し、それに応じて出力を生成します。

上記のリストを見るとわかるように、このハンドラーは単に、データベースから取得した製品情報の配列を XML ドキュメントに変換してからクライアントに返しています。以下の画像は、この出力を表示したものです。

/v1/products.xml に対する GET リクエスト

JSON フォーマットの出力を取得するには、クライアントは代わりに URL /v1/products.json、またはヘッダー 'Accept: application/json' を設定した URL /v1/products をリクエストする必要があります (以下の出力を参照)。

/v1/products.json に対する GET リクエスト

同じように POST ハンドラーを更新できれば、JSON フォーマットの入力だけでなく、XML フォーマットの入力もハンドラーが受け入れられるようになります。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // POST /v1/products[.xml|.json]
    // create new product
    $app->post(function($request) use ($app) {

      // handle requests for XML content
      $app->format('xml', function($request) use($app) {
        $input = simplexml_load_string($request->raw());
        $product = new Product();
        $product->name = trim(htmlentities((string)$input->name));
        $product->price = round(trim(htmlentities((string)$input->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, convert_array_to_xml(array($product->toArray())))
                    ->header('Content-Type', 'application/xml');          
        } else {
          return 400;
        }
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app) {          
        $product = new Product();
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, $product->toArray());
        } else {
          return 400;
        }
      });   

    });   
        
  });    
  
});

echo $app->run(new Bullet\Request());

上記では、/v1/products.xml に送信された XML フォーマットの POST 本体が、simplexml_load_string() メソッドによって PHP オブジェクトに変換され、このオブジェクトのプロパティーが新規 Product インスタンスに取り込まれます。このインスタンスが保存された後、新たに作成された Product リソースの XML 表現がクライアントに返されます。

以下の画像は、XML POST リクエストとそれに対するレスポンスの一例を示します。

/v1/products.xml に対する POST リクエスト

クライアントが XML ではなく JSON を使用する場合には、URL /v1/products.json に JSON のリクエスト本体を POST 送信することができます。この場合、Bullet は自動的に JSON のリクエスト本体をオブジェクトにデコードするので、それを Product インスタンスに変換してデータベースに保存することができます。クライアントが受信するのは、保存された Product リソースの JSON 表現です (以下を参照)。前述したように、POST リクエストの一部として 'Accept: application/json' ヘッダーを URL /v1/products に送信するという方法でも、同じ結果が得られます。

/v1/products.json に対する POST リクエスト

ステップ 7: エラーを処理し、認証を追加する

 

BulletPHP には、強力なイベント処理システムが用意されています。このシステムを使用することで、特定のサーバー・コード、レスポンスのフォーマット、または例以外タイプに基づいて、カスタム・アクションを実行することができます。その一例として、次のリストでは、イベント・ハンドラーを使用して、自動的に例外を捕捉し、それらをクライアントに JSON でエンコードされたパケットとして中継します。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// global 'Exception' handler event
$app->on('Exception', function($request, $response, Exception $e) use ($app) {
  // send 500 error with JSON info about exception
  $response->status(500);
  $response->content(array(
    'exception' => get_class($e),
    'message' => $e->getMessage()
  ));
});

// route handlers
// snipped

echo $app->run(new Bullet\Request());

以下は、例外に対する API のレスポンスの一例です。

API の例外処理

このイベント処理システムを使用して、リクエストの「処理前」または「処理後」にアクションを実行したり、カスタム・イベントに対してアクションを実行したりすることもできます。その一般的な用途は、認証です。保護されたルートに対してカスタム「認証要求」イベントをトリガーし、そのイベントを対象に、保護されたルートへのアクセスに必要なクレデンシャルを要求側クライアントが持っているかどうかをチェックするハンドラーを作成することができます。

以下のリストに、単純な例を示します。この例では、保護されたルートに対してカスタム 'auth' イベントがトリガーされると、制御がイベント・ハンドラーに移ります。するとイベント・ハンドラーは、リクエストの以降の処理を行う前に、認証クレデンシャル (この例では、平文のクッキー) をチェックします。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// runs when 'auth' event is triggered on protected API routes to check that
// credentials (here, plaintext cookies) are present
// replace with more complex authentication function in production
$app->on('auth', function($request, $response) use ($app) {

  if (!$request->cookie('uid') == 'demo' || !$request->cookie('pass') == 'demo') {
    $response->status(401);
    $response->send();
    exit;
  }
  
});

// unprotected API route to set authentication cookies
$app->path('set-auth-cookies', function($request) use ($app) {

  $app->get(function() use ($app)  {
    setcookie('uid', 'demo');
    setcookie('pass', 'demo');
    return 200;
  });
  
});

// protected API route
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // trigger authentication event
    $app->filter('auth');

    // route handler code
    // snipped
    }
}

echo $app->run(new Bullet\Request());

このサンプル・シナリオでは、ユーザーは特殊な /set-auth-cookies ルートをリクエストすることでクッキーを作成することができます。ただし、本番環境では、ユーザーが API キーに対する登録を行って、あらゆるリクエストでそのキーを提供しなければならない可能性があります。

以下の画像に、API が非認証リクエストに 401 Unauthorized コードで応答する場合の例を示します。

非認証リクエストに対する API のレスポンス

ステップ 8: Bluemix にデプロイする

 

読む:Deploy a Hello World PHP Application to Zend Server on Bluemix

この記事の冒頭でリンクされている、この API のデモ・バージョンは、認証なしで使用することができます。認証を使用できるようにするには、DevOps Services から API のソース・コードをダウンロードして、該当するセクションのコメントを外してから新規インスタンスをデプロイしてください。

API のコーディングはすべて完了したので、最後のステップとして、この API をデプロイします。

  • ローカルにデプロイするとしたら、このステップを実行する必要はないので、このセクションの終わりまでスキップしてください。そこに、API とのやりとりに利用できる有用なツールがリストアップされています。
  • Bluemix にデプロイする場合は、Bluemix アカウントが必要です。また、Cloud Foundry コマンド・ライン・クライアントをダウンロードしてインストールする必要もあります。以下の手順に従って、デプロイメント・プロセスを完了してください。

a. アプリケーションのマニフェストを作成する

 

読む:Deploy a minimal Node.js application to Bluemix

アプリケーション・マニフェスト・ファイルは、Bluemix に対し、アプリケーションをデプロイする方法を指示します。具体的には、使用する PHP ランタイム環境 (「ビルド・パック」) を指定します。$APP_ROOT/manifest.yml に新規ファイルを作成し、以下の情報を入力してください。

---
applications:
- name: products-api-[random-number]
memory: 256M
instances: 1
host: products-api-[random-number]
buildpack: https://github.com/dmikusa-pivotal/cf-php-build-pack.git

必ず、ホストとアプリケーションの名前を変更するか、名前に乱数を追加して、名前が固有になるように更新してください。私は Cloud Foundry PHP ビルド・パックを使用しますが、別の手段を用いることもできます。

b. Bullet の URL ルーティングをセットアップする

 

読む:Configure URL Rewriting for Framework-Based PHP Applications on IBM Bluemix

デフォルトでは、Cloud Foundry PHP ビルド・パックは Web サーバーとして Apache を使用します。nginx はそれよりも軽量の代替手段になるので、このステップでは代わりに nginx をWeb サーバーとして使用するように、ビルド・パックのデフォルト設定を変更します。その前に、このセクションのすべてのファイルは、このプロジェクトの DevOps Services にあるソース・コード・リポジトリーから入手できることを覚えておいてください。

まず始めに、$APP_ROOT/.bp-config ディレクトリーを作成し、そこに $APP_ROOT/.bp-config/options.json を作成して以下の内容を追加します。

{
    "WEB_SERVER": "nginx"
}

この時点で、nginx が API ルートを Bullet の URL ルーターに正しく渡せるように、nginx のURL リライティング・ルールを設定する必要もあります。それにはまず、$APP_ROOT/.bp-config/nginx/server-defaults.conf を作成して以下の内容を追加します。

        listen @{VCAP_APP_PORT};
        server_name _;

        fastcgi_temp_path @{TMPDIR}/nginx_fastcgi 1 2;
        client_body_temp_path @{TMPDIR}/nginx_client_body 1 2;
        proxy_temp_path @{TMPDIR}/nginx_proxy 1 2;

        real_ip_header x-forwarded-for;
        set_real_ip_from 10.0.0.0/8;
        real_ip_recursive on;

        try_files $uri /index.php;

次に、$APP_ROOT/.bp-config/nginx/server-locations.conf を作成して以下の内容を追加します。

        # Some basic cache-control for static files to be sent to the browser
        location ~* \.(?:ico|css|js|gif|jpeg|jpg|png)$ {
            expires max;
            add_header Pragma public;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";
        }

        # Deny hidden files (.htaccess, .htpasswd, .DS_Store).
        location ~ /\. {
            deny all;
            access_log off;
            log_not_found off;
        }

        # pass .php files to fastcgi
        location ~ .*\.php$ {
            try_files $uri =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass php_fpm;
        }

c. Bluemix に接続してアプリケーションをデプロイする

 

「cf」コマンド・ライン・ツールを使用して、IBM ID とパスワードで Bluemix にログインします。

shell> cf api https://api.mybluemix.net
shell> cf login

カレント・ディレクトリーを $APP_ROOT ディレクトリーに変更し、アプリケーションを Bluemix にプッシュします。

shell> cf push

以下は、このプロセス中に表示される出力の一例です。

アプリケーションを Bluemix にデプロイする

これで、アプリケーションは Bluemix にデプロイされましたが、これで作業が完了したわけではありません!

d. MySQL サービス・インスタンスをアプリケーションにバインドする

 

アプリケーションはデプロイされましたが、さらに MySQL データベース・インスタンスにアプリケーションを接続することで、API が何らかのデータを処理できるようにする必要があります。そのためには、Bluemix 管理ダッシュボードにアクセスして、IBM ID とパスワードでログインします。「Apps (アプリケーション)」メニュー・バーのリストには、このサンプル・アプリケーションが含まれているはずです。

Bluemix アプリケーション・ダッシュボード

アプリケーションを選択し、表示されるページで、「Add new service (新規サービスの追加)」オプションを使用して「mysql」サービスをアプリケーションに追加します。

Bluemix 上で MySQL サービスをアプリケーションにバインドする

Bluemix 管理ダッシュボードを見ると、アプリケーションにバインドされている MySQL サービス・インスタンスが表示されているはずです。

Bluemix アプリケーションにバインドされた MySQL サービス・インスタンス

アプリケーションの詳細を調べれば、VCAP_SERVICES 環境変数に含まれる MySQL アクセス・クレデンシャルを確認することもできます。

Bluemix アプリケーション環境変数

e. サンプル・スキーマをインストールする

 

この時点で、ステップ 1 に記載したサンプル・スキーマを使用して、アプリケーション・データベースを初期化することができます。ステップ 1 に記載した SQL コマンドを Bluemix 環境の中で直接実行することはできませんが、アプリケーションには /install-schema という名前の特殊なルートが含まれています。このルートをブラウザーからリクエストすれば、データベース・テーブルとサンプル・データをセットアップすることができます。記事ではこのルートについて説明していませんが、アプリケーションのソース・コード・リポジトリー内にこのルートがあります。

以下の画像は、/install-schema ルートから正常にスキーマがインストールされた結果を示しています。

6. REST API を使い始める
Bluemix 上でのスキーマのインストール

REST API のデプロイメントが完了した後は、このAPI に対して GET、POST、PUT、DELETE リクエストの送信を開始することができます。

  • Firefox の HttpRequester 拡張機能と Chrome の Postman 拡張機能は、ブラウザーを使用して多種多様な API リクエストを作成して送信できる強力なツールです。この記事のスクリーンショットのほとんどは、Postman 拡張機能を使って生成されています。
  • API にリクエストをサブミットするには、よく使われているクライアント・サイドおよびサーバー・サイドのプログラミング言語をどれでも使用することができます。つまり、(さまざまなプログラミング言語があるなかでも) jQuery、PHP、Perl、Java、および Python はいずれも、HTTP リクエストのサブミットとレスポンスの処理をサポートします。
  • この記事には、ブラウザー・ベースの API 探索ツールとなる Swagger を使用した、API のライブ・デモも組み込まれています。このデモを使用して、製品レコードを追加、更新、削除したり、個々の製品レコードを取得したりすることができます。以下の画像には、稼働中の Swagger フロント・エンドが示されています。 Swagger API エクスプローラー

まとめ

 

この記事で説明したように、REST API を作成してクラウド・ベースのプラットフォームにデプロイする場合、Bluemix は堅固な基盤になります。さらに、Bullet による一般的な REST 規約に対する柔軟なサポートと、Eloquent の極めて直感的な ORM を追加すれば、簡単にプロトタイプを作成して独自のカスタム REST API をデプロイするために必要なものが完備されることになります。

この記事で実装したコードは、記事で使用した PHP ビルド・パックの構成ファイルと一緒にすべて、DevOps Services のリポジトリーからダウンロードすることができます。ぜひともコードを入手して、いろいろ試してください。また、新しい機能を追加するのも一考です。何も壊れることはなく、よい勉強になるはずです。

コメントの追加

注意: HTML コードは、コメント内ではサポートされません。


残り 1000 文字

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=Web development, Cloud computing
ArticleID=976615
ArticleTitle=PHP と MySQL を使用して REST API を作成して IBM Bluemix にデプロイする
publish-date=07312014