hogehoge foobar Blog Style Beta

Web,Mac,Linux,JavaScript,Perl,PHP,RegExp,Git,Vim,Redmineなど技術的なことのメモや、ちょっと便利そうなものの紹介をしています。

Zend で ブログパーツを作ってみる (東京電力電力供給状況API と Google Chart API で 電力使用状況グラフ)

以前に「東京電力電力供給状況API と Google Chart API で 電力使用状況グラフ 」というエントリを書きました。
今回は、前回の作成したソースコードのHTTPリクエストを「Zend」を使うようにしたり、キャッシュやテンプレートについても「Zend」を使用してみました。

ZendFramework のインストール

最初に、今回必要となる「ZendFramework」をインストールします。
基本的には、ソースファイルをダウンロードしてきて、PHPのインクルードパスが通っている場所に配置するだけになります。

$ wget http://framework.zend.com/releases/ZendFramework-1.11.5/ZendFramework-1.11.5.zip
$ unzip ZendFramework-1.11.5.zip
$ cd ZendFramework-1.11.5/library/
$ mv Zend $PHP_CLASSPATH/Zend

ソースの構成

今回は、クラスを作ったり、HTML(実際はJavaScript)のテンプレートを作成したりしたので、複数のファイルから構成されます。
作成したファイルはこんな感じです。

ファイル名 説明
TepcoGraph.php メインのクラス。APIへのリクエストやグラフURLの生成、出力用HTMLの生成を行う。
tepcoViewTemplate.php 出力用のHTMLテンプレート。(Zend_Viewを使用)
tepcoViewTemplateError.php 出力用のHTMLテンプレート。(エラー用)(Zend_Viewを使用)
graph.js.php 呼び出し側のPHP。実際にグラフを貼り付ける場合、scriptタグではこのファイルを指定する。

主な追加点と変更点

API へのリクエストを cURL関数 から Zend_Http_Client に変更

前回は、APIとのHTTP通信に PHP組込みの「cURL関数」を使用していましたが、今回は「Zend_Http_Client」を使用しました。
使い方はこんな感じです。

<?php
// Zend Framework の ライブラリ のインクルード
require_once 'Zend/Http/Client.php';

// 接続パラメータを設定
$config = array(
    'adapter'    => 'Zend_Http_Client_Adapter_Curl', // 接続アダプタ(curl)
    'curloptions' => array(
        CURLOPT_HEADER => false,                     // HTTPヘッダの内容は受け取らない
        CURLOPT_RETURNTRANSFER => true,              // 文字列形式で受け取る
        
        // プロキシの認証が必要な場合は以下をコメントアウトして設定
        #CURLOPT_PROXY => 'your-proxy:portno',        // プロキシのURL/ポート番号
        #CURLOPT_PROXYUSERPWD => 'user_id:password',  // プロキシ認証用のID/PW
    )
);

// Zend_Http_Clientのインスタンスを生成
$client = new Zend_Http_Client($request_url, $config);

// レスポンスのオブジェクトを取得
$response = $client->request();

// HTTPステータスコードのチェック
$status_code = $response->getStatus()
if ($status_code !== 200) {
    echo $status_code;
}

// レスポンスボディー
echo $response->getBody();

接続アダプタについてですが、マニュアルによるとプロキシ経由での接続の場合「Zend_Http_Client_Adapter_Proxy」を使うとあるのですが、
なぜか私の環境では接続が正常に出来なかったため、「Zend_Http_Client_Adapter_Curl」を使用しています。

HTMLテンプレートとして Zend_View を追加

前回までは、HTML部分をソース上にハードコーディングしていましたが、デザイン変更等をする場合に面倒なことになるので、
「Zend_View」を使用してソースコードとHTML部分を分離してみました。

プログラム側(テンプレートを読み込む側)はこんな感じになります。

<?php
// Zend Framework の ライブラリ のインクルード
require_once 'Zend/View.php';

// Zend_View のインスタンス生成
$view = new Zend_View();

// テンプレートファイルが格納されているディレクトリを指定
$view->addScriptPath("zend_temp_path");

// テンプレートへの出力パラメタを送る
$view->assign('foo', "ABC");
$view->assign('bar', 'XYZ');

// テンプレートのレンダリング(表示・出力)
// ※引数としてテンプレートファイル名を指定
echo $view->render("template.php");

テンプレートファイル側(読み込まれる側)はこんな感じになります。

<!-- $this->foo → 「$view->assign」で指定したパラメタ -->
<!-- $this->escape → HTMLエスケープ処理               -->
<html>
    <h1>サンプル</h1>
    <p><?php echo $this->escape($this->foo); ?></p>
    <p><?php echo $this->escape($this->bar); ?></p>
</html>
キャッシュ機能として Zend_Cache を追加

前回はページがリクエストされるたびにAPI側へもリクエストをしており、場合によっては非常にメイワクな数のリクエストが飛んでしまうので、
「Zend_Cache」を使用してリクエスト結果などをキャッシュしてみました。
(東京電力電力供給状況API は「5〜10分以上に1回程度のアクセス」が推奨されています。)

「Zend_Cache」の使い方はこんな感じです。

<?php
// Zend Framework の ライブラリ のインクルード
require_once 'Zend/Cache.php';

// Zend_Cache のインスタンスを生成
// ・キャッシュはファイルに保存
// ・キャッシュ時間は10分(600秒)
// ・シリアライズを自動で行う。
// ・キャッシュファイルの格納先は「/tmp/」
$cache = Zend_Cache::factory('Core', 'File', 
                             array('lifetime' => 600, 'automatic_serialization' => true),
                             array('cache_dir' => "/tmp/" ) );

// キャッシュからのデータ読み込み
// ・キャッシュが存在する場合→変数 $data にデータを格納
// ・キャッシュが存在しない場合→ifブロック内の処理を実行する
if ( ($data = $cache->load("cache_id")) === false ){
    /*
        データ取得処理などをココに書く
    */

    // 取得したデータをキャッシュに保存
    $cache->save($data, "cache_id");
}
echo $data
出力を HTML から JavaScript に変更

前回は HTML を直接出力しており、グラフを表示する場合は iframe を使用する必要がありましたが、
今回は scriptタグでの埋め込みが出来るように、JavaScriptで出力するようにしてみました。

基本的に PHP 側から必要なデータを送る形にしていて、JavaScript側では難しいことはしないので、
「document.write」を使用して HTML を出力するだけです。

document.write(
'<html>',
'    <h1>サンプル</h1>',
'    <p><?php echo $this->escape($this->foo); ?></p>',
'    <p><?php echo $this->escape($this->bar); ?></p>',
'</html>'
);

ブログパーツとしては iframe と JavaScript どっちがいいの?」については、Web2.0的アフィリエイトラボ - javascriptとiframe がとても参考になりました。

サンプルソース(全部)

だらだらと書いてきましたが、全てのサンプルソースコードについては以下のとおりになります。

TepcoGraph.php
<?php
// Zend Framework の ライブラリ のインクルード
require_once 'Zend/Cache.php';
require_once 'Zend/View.php';
require_once 'Zend/Json.php';
require_once 'Zend/Http/Client.php';

// キャッシュファイル関連
define("CACHE_FILE_PATH", "/tmp/");
define("CACHE_TEPCO_API", "tepco_blogparts");
define("CACHETIME_TEPCO_API", 1800);

// ビューファイル関連
define("ZEND_TEMP_PATH", "Zend_template_path");
define("TEPCO_TEMPLATE_OK", "tepcoViewTemplate.php");
define("TEPCO_TEMPLATE_ERROR", "tepcoViewTemplateError.php");

// 東京電力電力供給状況API の URL
define("TEPCO_USAGE_API_URL", "http://tepco-usage-api.appspot.com/latest.json");

/**
 * TepcoGraph
 * 東京電力電力供給状況グラフ
 */
class TepcoGraph {

function __construct() {
}

function __destruct() {
    unset($this);
}

/**
 * HTTPリクエスト (Zend_Http_Clientを使用)
 * @param $request_url:リクエスト対象のURL
 * @return レスポンスの本文
 */
private function httpReqest($request_url)
{
    // 接続パラメータを設定
    $config = array(
        'adapter'    => 'Zend_Http_Client_Adapter_Curl',
        'curloptions' => array(
            CURLOPT_HEADER => false,
            CURLOPT_RETURNTRANSFER => true,
            
            // プロキシの認証が必要な場合は以下をコメントアウトして設定
            #CURLOPT_PROXY => 'your-proxy:portno',
            #CURLOPT_PROXYUSERPWD => 'user_id:password',
        )
    );
    $client = new Zend_Http_Client($request_url, $config);
    
    // レスポンスのオブジェクトを取得
    $response = $client->request();
    
    // HTTPステータスコードのチェック
    if ($response->getStatus() !== 200) {
        return false;
    }
    
    // レスポンスボディーを戻す
    return $response->getBody();
}

/**
 * 東京電力電力供給状況API からのデータの取得
 *  JSON取得→PHP配列に変換
 * @param 無し
 * @return PHPの配列(Zend_Json::decodeでパースした値)
 */
private function tepcoApiLatest(){
    $json = $this->httpReqest(TEPCO_USAGE_API_URL);
    if ( $json === false ){
        return false;
    }
    return Zend_Json::decode($json);
}

/**
 * 電力使用率の計算
 * @param $usage:電力使用量
 * @param $capacity:電力供給量
 * @return 電力使用率
 */
private function getUsageRate($usage, $capacity)
{
    return round($usage / $capacity * 100);
}

/**
 * Google Chart Tools API 用のURL生成
 * @param $usage:電力使用量
 * @param $capacity:電力供給量
 * @return グラフ画像URL(Google Charts API)
 */
private function getGraphImageUrl($usage, $capacity)
{
    // 使用率の算出
    $usage_rate = $this->getUsageRate($usage, $capacity);

    return 'http://chart.apis.google.com/chart?chs=300x225'
        . '&cht=p&chd=t:' . ceil(100 - $usage_rate) . ',' . ceil($usage_rate)  
        . '&chdl=%E6%AE%8B%E4%BE%9B%E7%B5%A6%E8%83%BD%E5%8A%9B|%E4%BD%BF%E7%94%A8%E9%87%8F'
        . '&chdlp=t&chl=' . ceil($capacity - $usage) . 'Kw|' . ceil($usage) . 'Kw'
        . '&chtt=%E6%9D%B1%E4%BA%AC%E9%9B%BB%E5%8A%9B%E3%81%AE%E9%9B%BB%E5%8A%9B%E4%BD%BF%E7%94%A8%E7%8A%B6%E6%B3%81';
}

/**
 * HTMLソースの文字列生成(Viewレンダリング)
 * @param $data:tepcoApiLatestメソッドで取得したPHP配列
 * @return レンダリングされたHTMLソース(文字列)
 */
private function createViewSting($data)
{
    $view = new Zend_View();
    $view->addScriptPath(ZEND_TEMP_PATH);

    // Google Charts Tools 用の URLを生成
    $graph_url = $this->getGraphImageUrl($data['usage'], $data['capacity']);

    //  使用率の算出
    $usage_rate = $this->getUsageRate($data['usage'], $data['capacity']);

    // テンプレートファイルに出力パラメタを送る
    $view->assign('graph_url', $graph_url);
    $view->assign('usage_rate', $usage_rate);
    $view->assign('usage', $data['usage']);
    $view->assign('capacity', $data['capacity']);
    $view->assign('year', $data['year']);
    $view->assign('month', $data['month']);
    $view->assign('day', $data['day']);
    $view->assign('hour', $data['hour']);

    return $view->render(TEPCO_TEMPLATE_OK);
}

/**
 * エラー用HTMLソースの文字列生成(Viewレンダリング)
 * @param 無し
 * @return レンダリングされたHTMLソース(文字列)
 */
private function createErrorViewSting()
{
    $view = new Zend_View();
    $view->addScriptPath(ZEND_TEMP_PATH);
    return $view->render(TEPCO_TEMPLATE_ERROR);
}

/**
 * Viewのレンダリング/キャッシュなど
 * @param 無し
 * @return HTML(JavaScript)ソースの文字列
 */
private function renderView()
{
    $cache = Zend_Cache::factory('Core', 'File', 
                                 array('lifetime' => CACHETIME_TEPCO_API, 'automatic_serialization' => true),
                                 array('cache_dir' => CACHE_FILE_PATH) );

    // OKページのキャッシュが存在していない場合は、以下のifブロック内を実行
    if ( ($html = $cache->load(CACHE_TEPCO_API)) === false ){

        // API へのリクエスト
        $data = $this->tepcoApiLatest();

        // リクエスト結果の判定
        if ( $data === false ){

            // リクエスト失敗の場合:エラーページのHTML生成
            $html = $this->createErrorViewSting();

        } else {

            // リクエスト成功の場合:OKページのHTML生成
            $html = $this->createViewSting($data);
        }

        // 生成したページをキャッシュに保存
        $cache->save($html, CACHE_TEPCO_API);

    }
    return $html;
}

/**
 * メイン処理(Public属性)
 * @param 無し
 * @return HTML(JavaScript)ソースの文字列
 */
public function main()
{
    return $this->renderView();
}

}
tepcoViewTemplate.php
document.write(
'<h1>電力使用量サンプル</h1>',
'<p><?php echo $this->escape($this->year); ?><?php echo $this->escape($this->month); ?><?php echo $this->escape($this->day); ?><?php echo $this->escape($this->hour); ?>時台</p>',
'<p>使用量:<?php echo $this->escape($this->usage); ?>万Kw/供給能力:<?php echo $this->escape($this->capacity); ?>万Kw</p>',
'<img src="<?php echo $this->escape($this->graph_url); ?>" width="300" height="225" />'
);
tepcoViewTemplateError.php
document.write(
'<h1>電力使用量サンプル</h1>',
'<p>エラーだよ</p>'
);
graph.js.php
<?php
ini_set('default_charset', 'UTF-8');
ini_set('mbstring.internal_encoding', 'UTF-8');
ini_set('mbstring.http_output', 'UTF-8');
require_once 'TepcoGraph.php';

$graph = new TepcoGraph();

header("Content-type: application/x-javascript; charset=UTF-8");
echo $graph->main();

実際にページに貼り付ける

実際にページにグラフを貼り付ける場合は、以下のようなHTMLタグを貼り付けたい場所に記述します。

<script type="text/javascript" src="graph.js.php" charset="utf-8"></script>

そうすると、いかのようなイメージで表示されます。(デザインはテキトーです。)
f:id:mrgoofy33:20110420052036p:image

デザインを変えたい場合は、テンプレートファイル(今回で言うとtepcoViewTemplate.phpとか)を変更すればOKです。

今回参考にしたページ

ライブラリーとして使うZend Framework - Zend_Http_Client/Zend_Json篇 | twk @ ふらっと
http://nonn-et-twk.net/twk/zend-http-client

17.3. Zend_Http_Client - 接続アダプタ
http://document.kappe.ne.jp/php/zend_framework/1.5.0RC1/zend.http.client.adapters.html

Zend Frameworkについて(View編1):なまはげ カンタービレ:So-netブログ
http://pluto-blog.blog.so-net.ne.jp/2007-01-14

25.3. Zend_Http_Client - 接続アダプタ
http://www.m-takagi.org/docs/php/zend/zend.http.client.adapters.html

Zend Framework - Zend_Cache編
http://doremi.s206.xrea.com/zend/ref/zend_cache.html

Zend Framework Documentation ライター - Zend Framework Manual
http://framework.zend.com/manual/ja/zend.log.writers.html#zend.log.writers.firebug

Web2.0アフィリエイトラボ - javascriptとiframe
http://www.web2-labo.com/javascriptiframe.html

document.write() 複数行の記述方法いろいろ tshinobu.com
http://tshinobu.com/blog/archives/73

ブログパーツが文字化けしない方法 開発ブログ
http://www.oasob.com/blog/detail/30/