プロキシパターンを使用してPHPキャッシュを整理する

問題の声明。 PHPには、それぞれ5つのデータサンプリングメソッドを持つ多数のモデルを含む有効なデバッグプロジェクトがあります。 プロジェクトは成長しており、すべてが順調に進んでいますが、ある時点で、負荷の重みの下で、モデル呼び出しのキャッシュを何らかの方法で追加する必要があります。



可能な解決策。



「額」の最初の方法:モデルの各メソッドで、標準スキームに従ってキャッシュを追加します。キャッシュをチェックし、関連するデータがある場合はそれを返し、そうでない場合はメソッドを実行します。キャッシュに。 これがひどい方法だと言うことは、何も言わないことを意味するので、これがなぜ悪いのかだけを言っておきます。



  1. SOLIDの原則の1つに違反しています。「コードは拡張のために開かれ、変更のために閉じられる必要があります」、つまり 新しい機能を追加するために、既にデバッグされている実稼働コードを取得および破壊します。これにより、常にエラーが発生し、その結果、ユーザーおよび顧客の不満が生じます。
  2. 同じコードでは、データの取得とキャッシングのロジックが混在しているため、クラスが膨張し、コードが容赦なく繰り返されます。
  3. これにより、キャッシュをバイパスしてライブデータを取得する機能が失われます(次のステップは、$ nocacheフラグを追加することです)。
  4. この方法でキャッシュを切断することは非常に複雑で、後でキャッシュを切断することは非常に複雑です。


2番目のメソッド「モデルクラスの拡張」:既存のメソッドの呼び出しをキャッシングでラップするモデルにダブラー追加します(たとえば、findById_Cached())。

より良いように見えます。既存のメソッドには触れず、代わりに新しいメソッドを追加します。 ただし、残りのマイナスはそのままです。

  1. ミキシングロジック。
  2. クラスのサイズは、以前の方法よりもさらに大きくなります。
  3. 非常に高度な複雑さ(この例では50個の新しいメソッドを追加)+アプリケーション内の古いメソッドの呼び出しを新しいメソッドに置き換えます。将来キャッシュをキャッシュする必要がある場合は、すべてのステップを繰り返します。




3番目の方法は「キャッシングプロキシ」です 。これは非常にシンプルで迅速なソリューションであり、その優雅さと実装速度に優れています。 それを行う方法-コードを見てください。



まず、モデル(サンプル)があります。

<?php namespace Storage { Class News { public function getTodayNews() { return "today news"; } public function searchNews( array $filter ) { $key = \http_build_query($filter); return "search news where $key"; } } } ?>
      
      





そして、キャッシュする前にアプリケーションでそれを呼び出します:

 <?php $news = new \Storage\News; $todayNews = $news->getTodayNews(); $searchNews = $news->searchNews( array('tag' =>'sport') ); ?>
      
      





次に、キャッシュ(サンプル)があります。

 <?php namespace Cache { Class Cache { protected $data = array(); public function get($key) { if ( isset($this->data[$key]) ) { return $this->data[$key]; } else { return null; } } public function set($key, $val, $ttl = 60) { $this->data[$key] = $val; } } ?>
      
      





そして、このトピックの主役はキャッシングプロキシです。

 <?php namespace Cache { Class Proxy { protected $realObject = null; protected $cache = null; protected $ttl = 0; public function __construct( $object, $ttl = 60 ) { $this->realObject = $object; //  ,  - //    $this->cache = new Cache; $this->ttl = $ttl; } //       //      //    __call() public function __call( $method, $args ) { $cacheKey = $method . '(' . \serialize($args) . ')'; $data = $this->cache->get( $cacheKey ); if ( null === $data ) { $call = array( $this->realObject, $method ); $data = \call_user_func_array( $call, $args ); $this->cache->set( $cacheKey, $data, $this->ttl ); } return $data; } } } ?>
      
      







私たちは使用します:

 <?php // $news = new \Storage\News; //   : $realNews = new \Storage\News; /** *    IDE  PhpDoc, *     ,      * * @var \Storage\News $news; */ $news = new \Cache\Proxy( $realNews , 600 ); $todayNews = $news->getTodayNews(); $searchNews = $news->searchNews( array('tag' =>'sport') ); ?>
      
      







ご覧のとおり、プロキシはどのオブジェクトとメソッドをキャッシュするかは関係ありません。これはデータベースで動作するクラスである必要はまったくありません。 必要に応じて、ライブデータを受信する機能を保持しました。 また、キャッシングを担当するアプリケーションの巨大な層は小さなクラスに削減され、その実装は問題を引き起こしません。



UPD:ニュアンスがあり 、この例の$ newsは異なるタイプのオブジェクトになりました。コードのどこかにタイプチェックがある場合(たとえば、メソッドパラメーターのinstanceofまたはデータタイプ)、これらのチェックは壊れます。 これを回避するには、\ Storageから\ Cache \ Proxyを継承する必要があります。もちろん、この場合、キャッシングクラスの普遍性は低下します。



All Articles