Delphi for PHP - Zend Frameworkを統合する: ZCache - パート3

By: Tomohiro Takahashi

Abstract: この記事は、Delphi for PHPの開発者であるJosé León氏のブログに掲載されたものです。この記事では、パート2,3で記述したZCacheコンポーネントを使用して、VCL for PHPにキャッシュ機能を追加します。

Zend Frameworkを統合する: ZCache (パート3)

この記事では、パート2,3の記事で記述したZCacheコンポーネントを使用して、VCL for PHPにキャッシュ機能を追加しようと思います。そのためにはVCL for PHPのソースコードを修正する必要がありますが、オープンソースですので修正しても特に問題ありません。

私達はできる限りVCL for PHPライブラリをオープンなものとして設計しているので、ライブラリにこの種のものを追加するのがいかに簡単であるかを示す最高の例でしょう。

計画

まず、VCL for PHPにキャッシュ機能を統合する方法をチェックしましょう。キャッシュ機能は様々な場面でとても役立つものですが、VCL for PHPに適用する際に最初に思い浮かぶ事柄は、スクリプトが呼び出されるたびにコンポーネントがレンダリングされないように、ユーザーがコンポーネントを選べるようにすることです。コントロールの中にはDBGridのように処理の重いものもあります。それらは、データに変更が無ければ、リクエストがあるたび、毎回のように全てのコードを再生成する必要の無いものです。

では、グローバルなレベルで処理を行えるように、全てのコンポーネントがレンダリングされる共通のコードの場所を見つける必要があります。それはdumpContents()メソッドです。このメソッドは、コンポーネント開発者が出力用のコードを記述するためにオーバーライドしなければならないメソッドです。しかし実は他にも、何からの出力を行う可能性のある dump で始まるメソッドがあります。例えば以下のものです:

  • dumpHeaderJavascript()
  • dumpChildrenHeaderCode()
  • dumpChildrenJavascript()
  • dumpChildrenFormItems()
  • dumpChildren()
  • dumpContents()

Webコントロールは、HTML, Javascript, 画像などの様々なものから構成されているので、いったい誰がコントロールをブラウザへ出力する責務を負っているのでしょうか? 通常、Pageコンポーネントが全てのコントロールに対してそのdumpメソッドを適切なタイミングで呼び出します。この記事では、show()メソッドとdumpContents()メソッドにキャッシュ機能を統合する方法だけを解説しようと思います。私達が完成したコードをVCL for PHPのリポジトリに組み込んだ際に、そのコード全体をチェックしてみてください。

では、ユーザーがVCL for PHPアプリケーションにキャッシュ機能を簡単に実装できるようにするには、どうしたら良いでしょうか?

  • Pageコンポーネントに、どのCacheオブジェクトを使ってキャッシュを行うのか指定できるプロパティを追加する
  • Controlの派生クラスの全てに、コンポーネントのキャッシュの有効・無効を設定するCachedという名前のプロパティを追加する
  • Pageコンポーネントが出力を行うdumpで始まるすべてのメソッドをチェックして、その中でキャッシュ機能を実装する
  • dumpContents()を呼び出す責務を負っているControl::show()メソッドの中で、全てのコントロールに対してキャッシュ機能を実装する

さぁ、計画が出来上がりました。取り掛かりましょう...

拡張のための準備

現時点でVCL for PHP向けには、Zend Frameworkをベースにしたキャッシュコンポーネントがたった1つしかありませんので、サードパーティが独自のキャッシュコンポーネントを実装できるように共通のプレースホルダーを提供しなくてはなりません。ですので、最初に行わなければならないのは、キャッシュインターフェース用の共通のプロパティおよびメソッドを持つ基本クラスを作成することです。

cache.inc.phpという名前の新しいユニットをVCLのルートフォルダに作成して、その中に以下の基本クラスを記述します:

class Cache extends Component
{
   /**
   * Start caching output, will be called just before start dumping content
   * to the output.
   *
   * This method should use $control and $cachetype to create a unique identifier
   * for the cache storage and should start the caching process. If content is not
   * cached, should return false to allow the caller know should produce the content.
   *
   * If the identifier already exists, meaning the content has been already cached,
   * then should dump out the cached content and return true.
   *
   * @param object $control Control to be cached
   * @param string $cachetype A prefix to specify what kind of contents are going to be cached
   *
   * @return boolean True if the content was already cached
   */
   function startCache($control, $cachetype)
   {
   }

   /**
   * Finish the caching process
   */
   function endCache()
   {
   }
}

たった2つのメソッド(キャッシュの開始と終了)ですが、これにより各コンポーネントを読み出して、その期待する動作についてより詳しく知ることができます。

では、ZCacheを修正して、コンポーネントをCacheから継承するようにし、そのメソッドを実装します:

class ZCache extends Cache
{
   function startCache($control, $cachetype)
   {
      $id=$control->readNamePath().'_'.$cachetype;
      $id=str_replace('.','_',$id);
      return($this->start($id));
   }

   function endCache()
   {
      $this->end();
   }
   .............................
}

startCache()メソッドでは、キャッシュ用の一意なIDを生成するために、渡されたコントロールを使用します。VCLには、そのためのreadNamePath()メソッドまたはNamePathプロパティがあります。また、キャッシュのタイプも識別子に追加し、JavascriptやHTMLのようなコントロールの異なる部位をキャッシュできるようにしています。最後にZCacheコンポーネントのstart()メソッドを呼び出してキャッシュ処理を開始しています。

これにより、Applicationの名前、Unitの名前、Componentの名前およびそのキャッシュのタイプから構成される「vclapp_Unit139_PearDataGrid1_contents」のようなIDが作成されます。

PageCacheプロパティを追加する

キャッシュを行うために使用するZCacheコンポーネントを指定するのに使われるCacheプロパティを追加する必要があります。では、プロパティを追加しましょう:

protected $_cache=null;

function getCache() { return $this->_cache; }
function setCache($value) { $this->_cache=$this->fixupProperty($value); }
function defaultCache() { return null; }

fixupPropertyメソッド(詳しくはこちらを参照)の呼び出しに注意してください。また、loaded()メソッドも修正する必要があります。

キャッシュを使用するためにControlクラスの準備を行う

以上により、ユーザーがPageコンポーネントで使用するキャッシュコンポーネントを指定できるようにするプロパティを持つようになりましたので、Control基本クラスがそれを利用するように修正する必要があります。ただし、ControlのCachedプロパティがtrueの場合のみです。では、そのプロパティをControl基本クラスに追加しましょう:

protected $_cached="0";

function getCached() { return $this->_cached; }
function setCached($value) { $this->_cached=$value; }
function defaultCached() { return "0"; }

デフォルトはfalseにして、アプリケーションの通常の動作を変更しないようにします。次に、キャッシュが利用可能でかつ有効な場合にはそのキャッシュを使用するようにshow()メソッドを修正する必要があります。それらの2つの処理を2つのメソッド(beginCacheとendCache)にカプセル化しましょう:

function beginCache($type)
{
   $result=false;
   if (($this->ControlState & csDesigning) != csDesigning)
   {
      if ($this->_cached==true)
      {
         if ($this->owner!=null)
         {
            if ($this->owner->inheritsFrom('Page'))
            {
               if ($this->owner->Cache!=null)
               {
                  return($this->owner->Cache->startCache($this, $type));
               }
            }
            else if ($this->inheritsFrom('Page'))
            {
               if ($this->Cache!=null)
               {
                  return($this->Cache->startCache($this, $type));
               }
            }
         }
      }
   }
   return($result);
}

function endCache()
{
   if (($this->ControlState & csDesigning) != csDesigning)
   {
      if ($this->_cached==true)
      {
         if ($this->owner!=null)
         {
            if ($this->owner->inheritsFrom('Page'))
            {
               if ($this->owner->Cache!=null)
               {
                  $this->owner->Cache->endCache();
               }
            }
            else if ($this->inheritsFrom('Page'))
            {
               if ($this->Cache!=null)
               {
                  $this->Cache->endCache();
               }
            }
         }
      }
   }
}

全てのプロパティが適切な値を含んでいるかチェックされ、キャッシュ処理の開始と終了を行うために、Cacheプロパティの中にあるCacheオブジェクトが呼び出されます。

追加したプロパティにに対して適切なプロパティエディタを指定する必要があります。これはstandard.package.phpファイルで行います:

registerPropertyValues('CustomPage','Cache',array('Cache'));
registerBooleanProperty('Control','Cached');

キャッシュのパフォーマンスをテストする

まだまだ改善する余地はたくさんありますが(例:Javascriptのキャッシュ)、キャッシュの効果があるかを知るために現時点のものでテスト可能です。次の手順に従ってください:

  • 空のページを作成する
  • ZCacheコンポーネントを配置する
  • Buttonを配置する
  • 全てHTMLでレンダリングするグリッドであるPearDataGridを配置して、キャッシュのパフォーマンスを確認できる程度の負荷を与える
  • PageのCacheプロパティにZCache1をセットする
  • PearDataGridのDSNプロパティにDSN文字列を設定する。例えば、mysql://root:test@localhost/blog など
  • SQLプロパティにクエリーを設定する。例えば、select id, post_title from wp_posts など

全てのインターフェースが整ったので、ButtonのOnClickイベントハンドラを生成して、その中に以下のコードを記述します:

$this->PearDataGrid1->Cached=true;

ボタンがクリックされると、グリッドにキャッシュするよう伝えられます。

最後に、パフォーマンスの改善を確認するために、ページの先頭に次のコードを追加します:

$start=microtime(true);

また、ページの最後に次のコードを追加します:

$end=microtime(true);
$diff=$end-$start;
$prof=$_SESSION['prof'];
$prof[]=$diff;
$_SESSION['prof']=$prof;
echo "<pre>";
print_r($prof);
echo "</pre>";

ページを生成して保存するのにどのくらいの時間が掛かるのか測定しています。スクリプトが呼び出された時に掛かった時間がすべて表示されます。

では、実行してボタンを数回押してください。次のようなログが得られます:

Array
(
[0] => 0.68625521659851
[1] => 0.58160591125488
[2] => 0.39929389953613  <-- First execution with cache!
[3] => 0.41545391082764
[4] => 0.38096499443054
[5] => 0.37384510040283
)

最初の実行時にはキャッシュ処理は行われていませんので、実行に0.686掛かりました。そして、ボタンをクリックしてグリッドのCachedをtrueに設定すると、コンテンツは保存されますが、ここではまだコントロールが生成されますので0.581掛かりました。その後の全てのリクエストはキャッシュされ、実行時間が少し短くなりました。今回は、シンプルなページにあるシンプルなHTMLグリッドがキャッシュされただけですが、もっと多くのコントロールやコンテンツを持つ重いインターフェースの場合はもっと時間が短縮されます。

この例では以下のようになります:

Hide image
Click to see full-sized image

以上、3つの記事で解説したように、ユーザーはコンポーネントを配置してプロパティを設定するだけで、キャッシュのテクノロジーの内部を知ることなく、また大量のパラメータ等を憶える必要もなく、簡単にアプリケーションにキャッシュ機能を実装できるようになります。

Server Response from: ETNASC01