目次


YQL と PHP を使用して Web アプリケーションを構築する: 第 2 回

PHP と YQL を使用して複数の Web サービスからデータを取得し、結合する

Comments

はじめに

この連載の第 1 回では、さまざまな Web サービス API からデータを取得するために、統一された SQL ライクなインターフェースを提供する YQL (Yahoo! Query Language) について紹介しました。そして、PHP アプリケーションから YQL サービスにアクセスする方法を具体的に説明し、YQL で SQL の構文を使ってクエリーの実行結果に対してフィルタリングやソートを行ったり、リンクしたりする例を記載しました。

第 2 回となるこの最終回では、YQL をさらに詳しく掘り下げ、PHP アプリケーションから YQL を利用して、サード・パーティー Web サービスのデータを追加、編集、削除する方法を紹介します。また、YQL を使って HTML ページや構造化 XML フォーマット (RSS や Atom など) からデータを検索して取得する方法も学んでください。

YQL でデータを追加する方法

前回の記事で説明したように、YQL ではSELECT 文を使ってサード・パーティー Web サービスからデータを抽出することができます。しかし、YQL でサポートされる SQL 文は SELECT だけではありません。同じ SQL 構文を使って、INSERT や DELETE、そして UPDATE クエリーでサード・パーティー・サービスのデータを操作することも可能です。

これがどのように機能するかを説明する例として、リスト 1 を見てください。このコードでは INSERT クエリーを使用して、ブログ・ポストを Wordpress.com 上のユーザーのブログに追加しています。このサンプル・コードを実行するには、リスト内の BLOGNAME、BLOGUSER、および BLOGPASS 変数を有効な Wordpress.com アカウントのクレデンシャルに置き換えてください。クレデンシャルは、無料の Wordpress.com ブログにサインアップすると入手することができます (「参考文献」にリンクを記載)。

リスト 1. YQL を使った INSERT クエリーによるデータの追加
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// execute YQL query
// add new post to Wordpress blog using POST
try {
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $client->env('store://datatables.org/alltableswithkeys');  
  $client->q("INSERT INTO wordpress.post 
    (title, description, blogurl, username, password) VALUES 
    ('Hello world', 'This is my first blog post with YQL...woohoo!', 
    'http://BLOGNAME.wordpress.com', 'BLOGUSER', 'BLOGPASS')");       
  $result = $client->post();
  echo 'Entry posted with ID: ' . $result->results->postid;
} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
    exit;
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
    exit;
}
?>

リスト 1 に示されているように、YQL を使って INSERT クエリーを実行する方法は、SQL 対応の RDBMS で INSERT クエリーを実行する場合とまったく同じです。つまり、データを挿入するテーブル名と、それに続けてキーと値のリストを指定する必要があります。YQL はテーブル定義を使用してクエリー・データを XML でエンコードされたパケットに変換し、そのパケットを POST メソッドを使ったリクエストによって Wordpress.com Web サービスに送信します。クライアントに返されるレスポンスは、新しく挿入された投稿の ID が含まれる標準的な YQL によるクエリーを実行した結果の文書です。

図 1 に、Wordpress.com ブログに新しく追加された投稿を示します。

図 1. YQL を使って追加されたブログ・ポスト
YQL を使って追加されたブログ・ポストを示す画面のスクリーン・キャプチャー
YQL を使って追加されたブログ・ポストを示す画面のスクリーン・キャプチャー

リスト 2 に、リスト 1 をさらにインタラクティブにしたバージョンを記載します。このバージョンでは、ユーザーが直接 Web フォームにコンテンツを入力することができます。すると、そのコンテンツが Wordpress.com ブログ・ポストに変換されます。

リスト 2. YQL を使ったインタラクティブな方法でのブログ・ポストの追加
<html>
  <head></head>
  <body> 
    <h2>Post Blog Entry</h2> 
    <form method="post" action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>">
      <div>
        Title: <br/>
        <input type="text" name="title" size="40" /> 
      </div>
      <div>
        Body: <br/>
        <textarea name="body" rows="5" cols="30"></textarea>
      </div>    
      <div>
        Blog URL: <br/>
        http:// <input type="text" name="prefix" size="40" /> .wordpress.com
      </div>
      <div>
        Username: <br/>
        <input type="text" name="user" size="40" />
      </div>
      <div>
        Password: <br/>
        <input type="text" name="pass" size="40" />
      </div>
      <input type="submit" name="submit" value="Post" />    
    </form>
    <?php
    if (isset($_POST['submit'])) {
      // validate input (omitted for brevity)
      $title = $_POST['title'];
      $body = $_POST['body'];
      $blog = 'http://' . $_POST['prefix'] . '.wordpress.com';
      $user = $_POST['user'];
      $pass = $_POST['pass'];

      // set up Zend auto-loader
      // load Zend REST client classes
      require_once 'Zend/Loader.php';
      Zend_Loader::loadClass('Zend_Rest_Client');

      // execute YQL query
      try {
        $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
        $client->env('store://datatables.org/alltableswithkeys');  
        $client->q(
          "INSERT INTO wordpress.post (title, description, blogurl, username, password) 
          VALUES ('$title', '$body', '$blog', '$user', '$pass')");       
        $result = $client->post();
      } catch (Zend_Rest_Client_Exception $e) {
          echo "Client error: " . $e->getResponse();
          exit;
      } catch (Exception $e) {
          echo "Error: " . $e->getMessage();
          exit;
      }

      // iterate over query result set
      echo 'Entry posted with ID: ' . $result->results->postid;
    }
    ?>
  </body>      
</html>

ユーザーには、図 2 の Web フォームが表示されます。この図ではフォームは入力された状態になっています。

図 2. ブログ・ポストをインタラクティブな方法で Wordpress.com に追加する Web フォーム
ブログ・ポストをインタラクティブな方法で Wordpress.com に追加する Web フォームを示す画面のスクリーン・キャプチャー
ブログ・ポストをインタラクティブな方法で Wordpress.com に追加する Web フォームを示す画面のスクリーン・キャプチャー

フォームが送信されると、ブログ・ポストは図 3 のように Wordpress.com に表示されます。

図 3. Wordpress.com に新しく追加されたブログ・ポスト
Wordpress.com に新しく追加されたブログ・ポストを示す画面のスクリーン・キャプチャー
Wordpress.com に新しく追加されたブログ・ポストを示す画面のスクリーン・キャプチャー

YQL を使ってデータを更新、削除する方法

UPDATE クエリーと DELETE クエリーも、INSERT クエリーと同じようにして使用することができます。リスト 3 は、リスト 1 で新しく作成した投稿を UPDATE クエリーを使って更新する例です。

リスト 3. YQL を使った UPDATE クエリーによるデータの変更
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// execute YQL query
// update post on Wordpress blog
try {
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $client->env('store://datatables.org/alltableswithkeys');  
  $client->q(
    "UPDATE wordpress.post SET title='Updated blog post', 
    description='Look, ma, I updated my blog post via YQL', publish='true' 
    WHERE blogurl = 'http://BLOGNAME.wordpress.com' AND 
    username = 'BLOGUSER' AND password = 'BLOGPASS' AND postid = '5'");       
  $result = $client->post();  
  if ($result->methodResponse->params->param->value->boolean == 1) {
    echo 'Post successfully updated';    
  } else {
    throw new Exception ('Could not delete post');  
  }
} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
    exit;
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
    exit;
}
?>

あらゆるデータベース UPDATE クエリーの例に洩れず、YQL クエリーにも、更新するフィールドのリストとそれぞれのフィールドの新しい値、そして更新をレコードの特定のセットに制限する WHERE 節を渡す必要があります。リスト 3 の WHERE 節の中に含まれているのは、ユーザーのクレデンシャルと、更新対象の投稿 ID です。

図 4 に、Wordpress.com ブログ上に表示された更新後の投稿を示します。

図 4. YQL を使って変更されたブログ・ポスト
YQL を使って変更されたブログ・ポストを示す画面のスクリーン・キャプチャー
YQL を使って変更されたブログ・ポストを示す画面のスクリーン・キャプチャー

リスト 4 に、今度は YQL を使って投稿を削除するためのコードを示します。

リスト 4. YQL を使った DELETE クエリーによるデータの削除
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// execute YQL query
// delete post from Wordpress blog
try {
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $client->env('store://datatables.org/alltableswithkeys');  
  $client->setConfig(array('timeout' => 30)); 
  $client->q(
    "DELETE FROM wordpress.post WHERE blogurl = 'http://BLOGNAME.wordpress.com' 
    AND username = 'BLOGUSER' AND password = 'BLOGPASS' AND postid = '5'");       
  $result = $client->post();  
  if ($result->methodResponse->params->param->value->boolean == 1) {
    echo 'Post successfully deleted';    
  } else {
    throw new Exception ('Could not delete post');  
  }
} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
    exit;
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
    exit;
}
?>

注意する点として、この記事を執筆している時点では、Wordpress.com の コミュニティー YQL テーブルには、関数による UPDATE および DELETE のサポートは組み込まれていません。ただし、パッチの開発は進行中です。上記のリストを実行してみて問題がある場合には、これらのテーブルに著者が作成したパッチを適用したバージョンを代わりに参照することができます。それには、YQL クエリー・ストリングを変更して、USE 節でカスタム・テーブルを参照してください。以下に例を記載します。

USE 'http://github.com/vikram0/yql-tables/raw/master/wordpress/wordpress.post.xml' 
AS wordpress.post; 
DELETE FROM wordpress.post WHERE blogurl = 'http://BLOGNAME.wordpress.com' 
AND username = 'BLOGUSER' AND password = 'BLOGPASS' AND postid = '5'

パッチを適用したバージョンを使用する場合には、以下のようにクライアント・タイムアウトも調整する必要があります。

  $client->setConfig(array('timeout' => 30));

サンプル・アプリケーション: クラウド内のショッピング・カート

上記の基礎知識を踏まえた上で、これから YQL を使ってデータを変更する機能を利用するサンプル・アプリケーションを構築していきます。この例でターゲットとする Web サービスは、「クラウド内のショッピング・カート」を実装した Payvment (「参考文献」にリンクを記載) です。その名前から推測できるとおり、Payvment では REST API を使ってショッピング・カートを作成、編集、変更することができるので、ショッピング・カートの機能を Web アプリケーションに簡単に統合することができます。また、Payvment ではコミュニティーによる Open Data Table を YQL でも使用できるようにしているため、Payvment REST API にアクセスする代わりに、YQL を使ってカートの操作を実行することができます。

以上の点を念頭に置いて、YQL と PHP を使用してクラウド内にショッピング・カートを作成するサンプル・アプリケーションを組み立てます。このアプリケーションでは各 SKU の価格と説明を記載する基本的な製品カタログを定義し、ユーザーがこれらの商品を、YQL を使った INSERT クエリーによって自分の Payvment カートに追加できるようにします。ユーザーは UPDATE クエリーと DELETE クエリーを使用して、ショッピング・カート内の商品の数量を変更したり、ショッピング・カートから商品を削除したりすることもできます。ショッピング・カートの現在の中身 (および小計) に関しては、随時 SELECT クエリーを使って取得することができます。

リスト 5 に、上記の内容をすべて盛り込んだコードを記載します。

リスト 5. Payvment の YQL 用コミュニティー・テーブルを使用して作成したショッピング・カート
<?php
// set API keys and constants
define (PAYVMENT_API_KEY, 'YOUR-API-KEY');
define (PAYVMENT_ID, 'YOUR-ID');

// define simple product catalog
$catalog = array(
  '167' => array('title' => 'Flying hammer', 'price' => 35.99),
  '543' => array('title' => 'Lightsaber', 'price' => 76.99),
  '129' => array('title' => 'Sonic screwdriver', 'price' => 10.99)
);

// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// start session
session_start();

try {
  // check if a cart already exists, if not, get a new cart ID
  if (!isset($_SESSION['cart'])) {
    $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
    $query = "USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
              shoppingcart.YourProducts.xml' AS shoppingcart; 
              SELECT * FROM shoppingcart WHERE apiKey='" . PAYVMENT_API_KEY . "'";
    $client->q($query);       
    $result = $client->get();
    if ($result->results->cartfeed->status->StatusCode != 200) {
      throw new Exception ('ERROR: Cannot get shopping cart');  
    } 
    $_SESSION['cart'] = (string) $result->results->cartfeed->results->CartID;
  }

  // check for add/edit/delete operations
  switch(strtolower($_REQUEST['op'])) {
    // add = INSERT
    case 'add':
      $id = $_REQUEST['id'];
      $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
      $query = 
        "USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
        shoppingcart.YourProducts.xml' AS shoppingcart; 
        INSERT INTO shoppingcart (apiKey, cartID, payvmentID, itemID, itemName, 
        itemPrice) VALUES ('" . PAYVMENT_API_KEY . "', '" . 
        $_SESSION['cart'] . "', '" . PAYVMENT_ID . "', '$id', '" . 
        $catalog[$id]['title'] . "', '" . $catalog[$id]['price'] . "')";
      $client->q($query);       
      $result = $client->post();
      if ($result->results->cartfeed->status->StatusCode != 200) {
        throw new Exception ('ERROR: Cannot add item to shopping cart');  
      } 
      break;  
    // remove = DELETE
    case 'remove':
      $id = $_REQUEST['id'];
      $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
      $query = 
        "USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
        shoppingcart.YourProducts.xml' AS shoppingcart; 
        DELETE FROM shoppingcart WHERE apiKey = '" . PAYVMENT_API_KEY . "' 
        AND cartID = '" . $_SESSION['cart'] . "' AND itemID = '" . $id . "' 
        AND retailerID = '" . PAYVMENT_ID . "'";
      $client->q($query);       
      $result = $client->post();
      if ($result->results->cartfeed->status->StatusCode != 200) {
        throw new Exception ('ERROR: Cannot remove item from shopping cart');  
      }     
      break;  
    // update = UPDATE
    case 'update':
      $id = $_REQUEST['id'];
      $qty = $_REQUEST['qty'];
      $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
      $query = 
        "USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
        shoppingcart.YourProducts.xml' AS shoppingcart; 
        UPDATE shoppingcart SET itemQty = $qty WHERE apiKey = '" . PAYVMENT_API_KEY . "' 
        AND cartID = '" . $_SESSION['cart'] . "' AND itemID = '" . $id . "' 
        AND retailerID = '" . PAYVMENT_ID . "'";
      $client->q($query);       
      $result = $client->post();
      if ($result->results->cartfeed->status->StatusCode != 200) {
        throw new Exception ('ERROR: Cannot update item in shopping cart');  
      }         
      break;      
  }

  // get current cart contents (summary and individual items)
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $query = 
    "USE 'http://github.com/vikram0/yql-tables/raw/master/shoppingcart/
    shoppingcart.YourProducts.xml' AS shoppingcart; 
    SELECT * FROM shoppingcart WHERE apiKey='" . PAYVMENT_API_KEY . "' 
    AND cartID='" . $_SESSION['cart'] . "'";
  $client->q($query);       
  $result = $client->get();
  if ($result->results->cartfeed->status->StatusCode == 200) {
    $cart_summary = $result->results->cartfeed->results;
    $cart_items = $result->results->cartfeed->items->storeitems;
  } else {
    throw new Exception ('ERROR: Cannot get shopping cart contents');  
  }  
} catch (Exception $e) {
  die($e->getMessage());  
}
?>
<html>
  <head>
    <style type="text/css">
      table {
                 border-width: 1px;
                 border-spacing: 2px;
                 border-style: outset;
                 border-color: red;
                 border-collapse: collapse;
                 background-color: white;
      }
      table th {
                 border-width: 1px;
                 padding: 4px;
                 border-style: inset;
                 border-color: red;
                 background-color: white;
                 -moz-border-radius: 0px 0px 0px 0px;
        font-weight: bolder;
      }
      table td {
                 border-width: 1px;
                 padding: 4px;
                 border-style: inset;
                 border-color: red;
                 background-color: white;
                 -moz-border-radius: 0px 0px 0px 0px;
      }
      </style>
  </head>
  <body>    
    <h2>Current Shopping Cart</h2>
    <table>
      <tr>
        <th>Item</th>
        <th>Price</th>
        <th>Quantity</th>
        <th>Subtotal</th>
        <th></th>
        <th></th>
      </tr>
      <?php foreach ($cart_items->item as $i): // display current cart ?>
      <form method="post">
      <input type="hidden" name="id" value="<?php echo $i->ItemID; ?>" />
      <tr>
        <td><?php echo $i->ItemName; ?></td>
        <td><?php echo $i->ItemPrice; ?></td>
        <td><input type="text" name="qty" size="3" 
          value="<?php echo $i->ItemQty; ?>" /></td>
        <td><?php echo $i->ItemQtyPrice; ?></td>
        <td><input type="submit" name="op" value="Update" /></td>
        <td><input type="submit" name="op" value="Remove" /></td>
      </tr>  
      </form>
      <?php endforeach; ?>
      <tr>
        <td colspan="3">Total</td>
        <td><?php echo $cart_summary->GrandTotal; ?></td>  
        <td colspan="2"></td>
      </tr>
    </table>  

    <h2>Product Catalog</h2>
    <table>
      <tr>
        <th>Title</th>
        <th>Price</th>
        <th></th>
      </tr>
      <?php foreach ($catalog as $id => $item): // display product catalog ?>
      <form method="post">
      <input type="hidden" name="id" value="<?php echo $id; ?>" />
      <tr>
        <td><?php echo $item['title']; ?></td>
        <td><?php echo $item['price']; ?></td>
        <td><input type="submit" name="op" value="Add" /></td>
      </tr>
      </form>
      <?php endforeach; ?>
    </table>
  </body>
</html>

上記のコードは一見すると手ごわそうに思えるかもしれませんが、実際には見た目よりも遥かに単純です。リスト 5 の重要なコンポーネントについて、以下にその概要を簡単に説明します。

  • スクリプトの最初の数行で定義しているのは、ユーザーの Payvment API キーと一意のユーザー ID です。これらのトークンは、Payvment Web サイト (「参考文献」にリンクを記載) に登録すると無料で入手することができます。このコードに続き、スクリプトでは単純な製品カタログを配列として定義します。この配列は、SKU として定義された配列キーと、それに対応する製品情報が含まれる値からなります。この製品カタログは HTML の表にフォーマット設定され、ユーザーが各商品をショッピング・カートに入れるためのコントロールが追加されます。
  • スクリプトでは次にセッションを初期化し、セッションにすでにショッピング・カート ID が含まれているかどうかをチェックします。ショッピング・カート ID は、ユーザーのショッピング・カートを Payvment API 全体で一意に識別する役割を持つため、ほとんどの API 呼び出しには、この ID を組み込むことになります。ショッピング・カート ID がセッションに存在する場合、スクリプトは次のステップに進みます。ID がない場合には、スクリプトがダミーの SELECT クエリーを作成して実行し、それによって新しいショッピング・カート ID を取得し、その ID をセッションに保存します。
  • 続いてスクリプトは、$_REQUEST['op'] 変数によって示される追加操作、編集操作、または削除操作が現在のリクエストに含まれているかどうかをチェックします。要求されている操作に応じて、Zend_Rest_Client は YQL を使って INSERT、UPDATE、または DELETE クエリーを Payvment データ・テーブルに対して実行します。各クエリーには API キーとユーザー ID だけでなく、場合によっては変更または削除する商品の ID も組み込まれることに注意してください。
  • 最後のステップとして、スクリプトは SELECT クエリーを実行してショッピング・カートの現在の中身を取得します。そして、この情報が HTML の表にフォーマット設定され、数量を変更するのか、あるいは商品を削除するのかに応じた適切なコントロールと一緒に表示されます。商品の小計とカート内の合計は、カートへの商品追加時に提供される情報に基づいて、Payvment API が自動的に計算します。

この記事を執筆している時点で、Payvment サービスのコミュニティー YQL テーブルでは古い REST API エンドポイントを使用していることに注意してください。パッチは現在、開発中です。上記のリストでは、著者がパッチを適用したバージョンのテーブルを利用しています。このバージョンのテーブルは、Github で入手することができます (「参考文献」を参照)。

図 5 に、実行中のアプリケーションを示します。

図 5. クラウド・ベースのショッピング・カート
クラウド・ベースのショッピング・カートと製品カタログを示す画面のスクリーン・キャプチャー
クラウド・ベースのショッピング・カートと製品カタログを示す画面のスクリーン・キャプチャー

INSERT、DELETE、および UPDATE クエリーを YQL で使用する場合には、注意しなければならない重要な点が 1 つあります。それは、これらの操作を許可する Web サービスのほとんどでは、データを変更する際にユーザー名、パスワード、API キーが必要になることです。通常、これらのクレデンシャルは、それぞれのサービスの Web サイトから無料で入手することができます。このようなクレデンシャルを必要とするサービスの場合、データを追加または変更する手順は、前に記載したリストでの手順と同様です。つまり、YQL によるクエリーにクレデンシャルを組み込み、それを Zend_Rest_Client を使用して YQL を使ったサービスに渡すことになります。

一方、OAuth による承認を必要とするサービスの場合には、Zend_Rest_Client ではなく、OAuth 対応のツールキットを使用するほうが賢明です。OAuth 対応のツールキットであれば、OAuth による承認の詳細がすべて自動的に処理されます。Yahoo! Open Social SDK は、そのようなツールキットの一例です。この SDK へのリンクの他、詳しい情報を記載した資料へのリンクについては、「参考文献」を参照してください。

YQL によるクエリーの実行結果のページネーション処理

この記事の前のセクションからわかるように、YQL では WHERE 節のサポートと、結果セットのソート、カウント、重複の削除を行うユーティリティー関数によって、検索結果のフィルタリングおよびソートを可能にしています。しかし、YQL で実行可能な操作はこれだけではありません。YQL ではページネーションのローカル制限とリモート制限の両方によって、結果セットに含まれるレコード数を制限することもできます。

リモート制限は、YQL がサード・パーティー Web サービスから取得するレコード数を制御する一方、ローカル制限は YQL が呼び出し側アプリケーションに返すレコード数を制御します。

制限が指定されない場合、YQL によるクエリーは通常、10 件のレコードを返します。この数を変更するには、制限数を括弧で囲んでクエリーに渡します。以下に示すのは、リモート制限を 15 件のレコードに設定する場合の例です。

SELECT * FROM music.artist.popular (15)

すべてのレコードを返すには、以下の例のように制限を 0 に設定します。

SELECT * FROM music.artist.popular (0)

ローカル制限を適用するには、LIMIT 節と OFFSET 節を使用します。これらの節は SQL で使用する場合とほとんど同じような役割を持ち、LIMIT 節は結果として返されるレコードの数を指定し、OFFSET 節は開始オフセットを指定します。以下に示すのは、結果セットのレコード 5 から 9 を返す場合の例です。

SELECT * FROM music.artist.popular (15) LIMIT 5 OFFSET 5

このような制御が可能であれば、Zend_Paginator のようなページネーション・ウィジェットを使用して、YQL によるクエリーの実行結果を対象とした単純なページネーション・システムを実装するのはごく簡単な話です。リスト 6 に、YQL によるクエリーの実行結果のページネーション処理をする 1 つの方法を記載します。

リスト 6. Zend_Paginator を使って行う、YQL によるクエリーの実行結果のページネーション処理
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
Zend_Loader::loadClass('Zend_Paginator');
Zend_Loader::loadClass('Zend_Paginator_Adapter_Array');

try {
  // execute YQL query
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $client->q("SELECT name FROM music.artist.popular (0)"); 
  $result = $client->get();
  $data = array();
  foreach ($result->results->Artist as $artist) {
    $data[] = (string)$artist['name'];  
  }

  // initialize pager with data set
  $pager = new Zend_Paginator(new Zend_Paginator_Adapter_Array($data));

  // set page number from request
  $currentPage = isset($_GET['p']) ? (int) htmlentities($_GET['p']) : 1;
  $pager->setCurrentPageNumber($currentPage);

  // set number of items per page from request
  $itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 10;
  $pager->setItemCountPerPage($itemsPerPage);

  // get pages
  $pages = $pager->getPages();

  // create page links
  $pageLinks = array();
  $separator = ' | ';
  for ($x=1; $x<=$pages->pageCount; $x++) {
    if ($x == $pages->current) {
      $pageLinks[] = $x;      
    } else {
      $q = http_build_query(array('p' => $x, 'c' => $itemsPerPage));
      $pageLinks[] = "<a href=\"?$q\">$x</a>";  
    }
  } 

} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// iterate over current page set
echo '<h2>Popular Artists</h2>';
$ctr = $pages->firstItemNumber;
foreach ($pager->getCurrentItems() as $item) {
  echo  $ctr . '. ' . $item . '<br/>';  
  $ctr++;
}

// print page links
echo '<div>';
echo 'Pages:' . implode($pageLinks, $separator);
echo '</div>';
?>

リスト 6 では最初に Zend_Paginator コンポーネントをロードしています。その名前からわかるように、これは大規模なデータ・セットのページネーション処理に使用できる既製の Zend Framework コンポーネントです。このコンポーネントは、ユーザーが簡単に結果セットのさまざまなページにアクセスできるように、ページ・リンクを生成するための既製のメソッドも提供しています。

Zend_Paginator をロードした後、リスト 6 では制限なしの SELECT クエリーを実行して YQL によるクエリーの実行結果のすべてを取得します。続いて、返されたデータを PHP 配列に変換し、その配列を Zend_Paginator 配列データ・ソースに渡すことで、現在のページに必要な配列のサブセットを抽出させます。Zend_Paginator はさらにページ・ナビゲーション・リンクを生成して、ユーザーが結果セットを前後のページに移動できるようにします。

この方法は、YQL によるクエリーの実行結果のページネーションを行う方法としては、最も効率的であるとは言えないので、本番環境にはこの例を適用しないようにしてください。リスト 6 の場合、リクエストのそれぞれで YQL によるクエリーを再実行せざるを得ません。そのため、ページネーション処理に余計な時間がかかり、パフォーマンスの犠牲を伴います。それよりも効率的な方法は、YQL によるクエリーの実行結果セットをサーバー・サイドのキャッシュに保管し、このキャッシュからデータを取得するという方法です。そうすれば、リクエストのたびに YQL を使ったサービスからデータをリクエストする必要がなくなります。リスト 7 に、Zend_Cache によって YQL によるクエリーの実行結果をサーバー・サイドのファイル・キャッシュに保管するように改善した方法を記載します。

リスト 7. Zend_Paginator と Zend_Cache を使用して効率化したページネーション処理
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');
Zend_Loader::loadClass('Zend_Paginator');
Zend_Loader::loadClass('Zend_Cache');
Zend_Loader::loadClass('Zend_Paginator_Adapter_Array');

try {

  // set up cache
  $front = array(
     'lifetime' => 2000, 
     'automatic_serialization' => true
  );
  $back = array(
      'cache_dir' => './' 
  );
  $cache = Zend_Cache::factory('Core', 'File', $front, $back);

  // look for YQL data in cache and use if available
  // if not, execute YQL query, get fresh result set and save to cache
  if(!$data = $cache->load('yql')) {     
    // execute YQL query
    // get total count of items
    $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
    $client->q("SELECT name FROM music.artist.popular (0)"); 
    $result = $client->get();
    $data = array();
    foreach ($result->results->Artist as $artist) {
      $data[] = (string)$artist['name'];  
    }
    $cache->save($data, 'yql');
  }

  // initialize pager with data set
  $pager = new Zend_Paginator(new Zend_Paginator_Adapter_Array($data));

  // set page number from request
  $currentPage = isset($_GET['p']) ? (int) htmlentities($_GET['p']) : 1;
  $pager->setCurrentPageNumber($currentPage);

  // set number of items per page from request
  $itemsPerPage = isset($_GET['c']) ? (int) htmlentities($_GET['c']) : 10;
  $pager->setItemCountPerPage($itemsPerPage);

  // get pages
  $pages = $pager->getPages();

  // create page links
  $pageLinks = array();
  $separator = ' | ';
  for ($x=1; $x<=$pages->pageCount; $x++) {
    if ($x == $pages->current) {
      $pageLinks[] = $x;      
    } else {
      $q = http_build_query(array('p' => $x, 'c' => $itemsPerPage));
      $pageLinks[] = "<a href=\"?$q\">$x</a>";  
    }  
  } 

} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// iterate over current page set
echo '<h2>Popular Artists</h2>';
$ctr = $pages->firstItemNumber;
foreach ($pager->getCurrentItems() as $item) {
  echo  $ctr . '. ' . $item . '<br/>';  
  $ctr++;
}

// print page links
echo '<div>';
echo 'Pages:' . implode($pageLinks, $separator);
echo '</div>';
?>

リスト 7 の背後にある基本ロジックは単純です。リクエストごとに、このスクリプトはキャッシュをチェックして、YQL によるクエリーの実行結果が既にキャッシュ内に存在するのかどうかを調べます。既に結果が存在する場合、キャッシュ内のコピーを Zend_Paginator データ配列に取り込んだ後、通常のページネーション処理を行います。YQL によるクエリーが実行されるのは、そのクエリーの実行結果がキャッシュ内に見つからない場合、あるいはその結果の有効期限が切れている場合だけです。クエリーが実行された場合は、その実行結果を以降のリクエストで使用できるように再びキャッシュに保管します。

別の方法としては、クエリーの実行結果のデータをクライアント・サイドの変数に保管し、ユーザーがページ間を移動するときに、JavaScript を使用してページ・コンテナーに動的にデータが取り込まれるようにするという方法も考えられます。

図 6 に出力結果を示します。

図 6. ページネーション処理を適用した YQL によるクエリーの実行結果
ページネーション処理を適用した YQL によるクエリーの実行結果 (人気のあるアーティストの一覧) を示す画面のスクリーン・キャプチャー
ページネーション処理を適用した YQL によるクエリーの実行結果 (人気のあるアーティストの一覧) を示す画面のスクリーン・キャプチャー

複数のクエリーの同時実行

YQL には他にも多数のユーティリティー・テーブルがあり、複数のクエリーを一度に実行したり、外部フィードを構文解析したりするなどのさまざまな目的で使用することができます。一例として、リスト 8 では特殊な「yql.query.multi」テーブルを使用して、3 つ一組のクエリーを一度に実行しています。

リスト 8. YQL によるマルチクエリーの実行
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// execute multiple YQL queries
// combine results
try {
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $q=<<<EOF
    SELECT * 
    FROM yql.query.multi 
    WHERE queries="
      SELECT * FROM geo.places WHERE text='london';
      SELECT * FROM geo.places WHERE text='hong kong';
      SELECT * FROM geo.places WHERE text='tanzania'
    "
EOF;
  $client->q($q);
  $result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// iterate over query result set
foreach ($result->results->results as $result) {
  foreach ($result->place as $place) {
    echo '<div>';
    echo '<strong>' . $place->name . ', ' . 
      $place->country . ' (' . $place->placeTypeName . 
      ') </strong> <br/>';
    echo 'Latitude:' . $place->centroid->latitude . 
    ' Longitude:' . $place->centroid->longitude;
    echo '</div>';    
  }
}  
?>

リスト 8 に記載されている 3 つの YQL によるクエリーは、それぞれに異なる都市に関する情報を geo.places テーブルで検索します。処理を多少効率化するために、この 3 つのクエリーは yql.query.multi テーブルに対する 1 つのクエリーにラップされ、各クエリーの結果が 1 つの結果文書にマージされます。複数のクエリーをこのように実行することには、いくつかの利点があります。その 1 つは、コードが読み易くなることです。そして、すべてのクエリーが同じテーブルをターゲットとしている場合には、クエリーの実行速度が向上することにもなります。それは、YQL は同じテーブル定義ファイルを何度も読み取らなくても済むように、内部キャッシングを使用するからです。

図 7 は、リスト 8 による出力です。

図 7. YQL によるマルチクエリーのマージされた結果
YQL によるマルチクエリーのマージされた結果 (都市、国、緯度と経度) を示す画面のスクリーン・ショット
YQL によるマルチクエリーのマージされた結果 (都市、国、緯度と経度) を示す画面のスクリーン・ショット

XML フィードの構文解析

さらに、RSS フィードや Atom フィードなどの標準 XML 形式の文書のデータを簡単に構文解析して抽出できるようにしているユーティリティー・テーブルのセットもあります。リスト 9 の例では rss テーブルを使用して、YQL によって外部 RSS フィードを構文解析し、そこから情報のサブセットを抽出しています。

リスト 9. YQL による RSS フィードの構文解析
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// execute YQL query
// get 5 top stories from Google News RSS feed
try {
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $client->q("SELECT title FROM rss (5) 
    WHERE url='http://news.google.com/news?ned=us&topic=h&output=rss'");
  $result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// iterate over query result set
echo '<h2>Top Stories</h2>';
foreach ($result->item as $item) {
  echo '<div>' . $item->title . '</div>';
}  
?>

図 8 に出力結果を示します。

図 8. YQL を使って RSS フィードから抽出した Google ニュースの記事一覧
YQL を使って RSS フィードから抽出した Google ニュースの記事一覧を示す画面のスクリーン・キャプチャー
YQL を使って RSS フィードから抽出した Google ニュースの記事一覧を示す画面のスクリーン・キャプチャー

同じように、xml テーブルを使用して YQL によるフィルターを XML 文書に適用し、XPath と組み合わせて特定のノード・コレクションを取得することもできます。リスト 10 は、この手法の一例です。

リスト 10. YQL による XML 文書の構文解析
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// execute YQL query
// get 5 top stories from Google News RSS feed
try {
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $client->q("SELECT title, link FROM xml (5) 
    WHERE url='http://news.google.com/news?ned=us&topic=h&output=rss' 
    AND itemPath='//item'");
  $result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// iterate over query result set
echo '<h2>Top Stories</h2>';
foreach ($result->item as $i) {
  echo '<div><a href="' . $i->link . '">' . $i->title . 
  '</a></div>';
}  
?>

リスト 10 では SELECT クエリーを使用して外部 XML 文書を構文解析し、その結果に XPath 式を適用して特定のノードのサブセットを選択しています。このように、SQL ライクな構文とフィルターを一般的な方法で XML データに適用できるとなれば、複雑な XML 文書ツリーから情報を抽出するというタスクが大幅に簡易化されることになります。

図 9 に出力結果を示します。

図 9. YQL を使って XML 文書から抽出した Google ニュースの記事一覧
YQL を使って XML 文書から抽出した Google ニュースの記事一覧を示す画面のスクリーン・キャプチャー
YQL を使って XML 文書から抽出した Google ニュースの記事一覧を示す画面のスクリーン・キャプチャー

HTML ページからのデータの抽出

YQL では HTML Web ページからデータを抽出することも可能です。YQL の組み込み html ユーティリティー・テーブルをフィルターとして使用すれば、HTML ページから特定のデータを取得することができます。XPath が、HTML 文書ツリーで個々のノードまたはノードのコレクションを見つけて取得するために必要な式言語となります。

この仕組みを説明する例として、インドの州と連邦直轄地域をすべて網羅した一覧を取得するとします。この情報は、インド政府の公式 Web サイトにフォーマット設定された HTML テーブルとして公開されています (図 10 を参照)。

図 10. インドの州に関するデータが記載された Web ページの HTML ソース
インドの州に関するデータが記載された Web ページの HTML ソースを示す画面のスクリーン・キャプチャー
インドの州に関するデータが記載された Web ページの HTML ソースを示す画面のスクリーン・キャプチャー

YQL を使用すれば、たった 1 つの SELECT クエリーで、このソース・ページのデータを YQL によるクエリーの結果セットに抽出することができます (リスト 11 を参照)。

リスト 11. YQL による HTML 文書の構文解析
<?php
// set up Zend auto-loader
// load Zend REST client classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Rest_Client');

// execute YQL query
// scrape GOI page to get list of Indian states
try {
  $client = new Zend_Rest_Client('http://query.yahooapis.com/v1/public/yql');
  $client->q("SELECT content FROM html 
    WHERE url='http://india.gov.in/knowindia/state_uts.php' 
    AND xpath='//ul[@class=\'sec1\']/li/a'");

  $result = $client->get();
} catch (Zend_Rest_Client_Exception $e) {
    echo "Client error: " . $e->getResponse();
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
// iterate over query result set
echo '<h2>List of States in India</h2>';
echo '<ul>';
foreach ($result->a as $state) {
  echo "<li>$state</li>";
}
echo '</ul>';
?>

リスト 11 に、出力結果を示します。

図 11. YQL を使って Web ページから抽出したインドの州の一覧
YQL を使って Web ページから抽出したインドの州の一覧を示す画面のスクリーン・キャプチャー
YQL を使って Web ページから抽出したインドの州の一覧を示す画面のスクリーン・キャプチャー

まとめ

YQL で実行できる操作は、1 つまたは複数の Web サービスから情報を抽出することだけではないことは明らかです。YQL では、標準の INSERT、UPDATE、および DELETE クエリーを使用してサード・パーティー Web アプリケーションのデータを追加、変更することができるため、複数のデータ・ソースに対してデータの読み取り/書き込みを行うクラウド・ベースの新しいアプリケーションを作成するのも簡単なことです。さらに、YQL が提供している数々のユーティリティー・テーブルと YQL XPath フィルターを組み合わせれば、RSS、Atom、CSV、HTML などのよく使われるファイル・フォーマットから情報を抽出することも可能です。

現在、YQL の開発は活発に進められているので、将来さらに多くの利点がもたらされることが期待できます。ぜひとも YQL を試してみてください。あるいは YQL と PHP アプリケーションの統合に着手すると同時に、独自のデータ・テーブルをコミュニティー・リポジトリーに追加するのも一考です。それでは、コーディングをお楽しみください!


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source
ArticleID=607234
ArticleTitle=YQL と PHP を使用して Web アプリケーションを構築する: 第 2 回
publish-date=11302010