cakePHP内でPEAR Services_Amazonのキャッシュ機能を使おうとするとエラー

気がついたら1年ぶり以上のCake関係の記事。
さて、最近またCakePHPを使ってWEBアプリを組んでいるので久々に更新します。


最近、Product Advertising APIと名前が変わったAmazonのAPI
これを使うのに便利なライブラリとしてPEAR Services_Amazonがあります。
このライブラリを使用すると、通信結果結果を配列で取得、Amazonから取得したデータのキャッシュの実装などなどが簡単に行えます。


が、CakePHP内部で、このライブラリのキャッシュ関係の部分が動かずハマってしまったので、かつ同じようにハマる人がいそうなのでメモしておきます。

発生したエラーについて

CakePHPの、あるmodelの中で、次のようなコードを書きました

<?php
  require_once("Services/Amazon.php");
  $amazon = new Services_Amazon($amazon_access_key, $amazon_secret_key, $amazon_associate_key);
  $amazon->setLocale("jp");
  $amazon->setCache('file', array('cache_dir' => '/path/to/cache/dir/'));
?>

すると以下のようなエラーが発生
Fatal error: Call to undefined method Cache::get() in /usr/share/php/Services/Amazon.php on line 1361

エラーが起こっているServices/Amazon.phpの処理を見て、原因特定

1361行目のクラスの_sendRequestメソッド内の_cacheというプロパティからgetメソッドを実行しようとしているところでエラーが起こっています。

<?php
$cache = $this->_cache->get($cache_id);
?>
・$this->_cacheの初期化

ということで、この_cacheプロパティに、どのようなインスタンスが設定されるのかをチェック。
397行目から、始まるsetCacheメソッド内で初期化されています。

<?php
 397     function setCache($container = 'file', $container_options = array())
 398     {
 399         if(!class_exists('Cache')){
 400             @include_once 'Cache.php';
 401         }
 402 
 403         @$cache = new Cache($container, $container_options);
 404 
 405         if (is_object($cache)) {
 406             $this->_cache = $cache;                                                                                     
 407         } else {
 408             $this->_cache = null;
 409             return PEAR::raiseError('Cache init failed');
 410         }
 411 
 412         return true;
 413     }

エラー処理も、きちんとしてるし問題なさそうです。
今回、発生したエラーはCache::getというメソッドが存在していねぇよっというエラーなので、$cacheのメソッド一覧を確認してみましょう。
406行目の下に次の行を追加してみます

<?php
 407 print_r( get_class_methods($this->_cache) );
>?

以下が結果です。

<?php
Array
(
    [0] => getInstance
    [1] => __loadEngine
    [2] => config
    [3] => engine
    [4] => set
    [5] => gc
    [6] => write
    [7] => read
   -----
    [20] => cakeError
    [21] => _persist
    [22] => _savePersistent
    [23] => __openPersistent
)
>?

あ、あれ、このメソッドどこかで見たことあるぞ。。。っていうか配列の20個目,cakeとかいう文字が。。。PEARのライブラリなのに。。。
つまり、CakePHP内でもCacheという名前のクラスがあって、それがPEARのCacheクラスと競合していました。
Services/Amazon.phpPEAR Cacheを使用したいのに、CakePHPのCacheクラスのインスタンスが生成されてしまっています。

解決方法(暫定

ということで解決方法です。
今回とった方法は、あんまりよくないかもしれないけど、PEARのライブラリを書き換えてしまいます。
1.Cache.phpをコピーしてCache_PEAR.phpを作成
まず、クラス名が被っているのがまずいのでPEAR::Cacheのクラス名を変更します。
直でCache.phpを書き換えてもいいのだけど、一応コピーをとって、そっちを書き換える方針で。
2.Cache_PEAR.phpの次の2箇所(クラス名とコンストラクタ部分)を変更

  • 66行目
    • 変更前:class Cache extends PEAR
    • 変更後:class Cache_PEAR extends PEAR
  • 129行目
    • 変更前:function Cache_PEAR($container, $container_options = '')
    • 変更後:function Cache_PEAR($container, $container_options = '')

3.Services/Amazon.phpでCacheインスタンスの代入部分を変更

  • 変更前
<?php
 399         if(!class_exists('Cache')){
 400             @include_once 'Cache.php';
 401         }
 403         @$cache = new Cache($container, $container_options);
>?
  • 変更後
<?php
 399         if(!class_exists('Cache_PEAR')){
 400             @include_once 'Cache_PEAR.php';
 401         }
 403         @$cache = new Cache_PEAR($container, $container_options);
>?

結果

無事、Services_Amazonのキャッシュ部分が動きました。