application/config/cache.php
< ?php
$config = array(
'adapter' => 'memcached',
); |
< ?php
$config = array(
'adapter' => 'memcached',
);
application/config/memcached.php
< ?php
$config = array(
'cache1' => array(
'hostname' => '127.0.0.1',
'port' => 11211,
'weight' => 1,
),
); |
< ?php
$config = array(
'cache1' => array(
'hostname' => '127.0.0.1',
'port' => 11211,
'weight' => 1,
),
);
application/libraries/Cache/MY_Cache.php
ただ、Cacheを継承しただけのMY_Cacheです。
< ?php
class MY_Cache extends Cache {
} |
< ?php
class MY_Cache extends Cache {
}
まずは、この状態で下記のようにwelcome.phpにキャッシュの利用をするコードを書いてみてアクセスしてみます。
application/controller/welcome.php
public function index()
{
$this->load->driver('cache');
$this->cache->save('hoge', 'foo', 3600);
$hoge = $this->cache->get('hoge');
var_dump($hoge);
$this->load->view('welcome_message');
} |
public function index()
{
$this->load->driver('cache');
$this->cache->save('hoge', 'foo', 3600);
$hoge = $this->cache->get('hoge');
var_dump($hoge);
$this->load->view('welcome_message');
}
アクセスしてみると。。。
An Error Was Encountered
Unable to load the requested class: Cache
足りなければ追加すればいいじゃない
エラーになってしまいましたね。このメッセージがどこで呼ばれているかというと、下記になります。
system/core/Loader.php
767行目付近です。継承されるべきスーパクラスは、system/libraries/直下に存在しないといけないようです。
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.EXT;
// Is this a class extension request?
if (file_exists($subclass))
{
$baseclass = BASEPATH.'libraries/'.ucfirst($class).EXT;
if ( ! file_exists($baseclass))
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
} |
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.EXT;
// Is this a class extension request?
if (file_exists($subclass))
{
$baseclass = BASEPATH.'libraries/'.ucfirst($class).EXT;
if ( ! file_exists($baseclass))
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
ここで少しおかしなコードが存在します。サブクラスは階層化されることを考慮しているにも関わらず、スーパクラスに関しては、階層化が考慮されていません。
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.EXT; |
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.EXT;
じゃあ、追加すればいいじゃん。
ということで、追加してみました。
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.EXT;
// Is this a class extension request?
if (file_exists($subclass))
{
- $baseclass = BASEPATH.'libraries/'.ucfirst($class).EXT;
+ $baseclass = BASEPATH.'libraries/'.$subdir.ucfirst($class).EXT;
if ( ! file_exists($baseclass))
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
} |
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.EXT;
// Is this a class extension request?
if (file_exists($subclass))
{
- $baseclass = BASEPATH.'libraries/'.ucfirst($class).EXT;
+ $baseclass = BASEPATH.'libraries/'.$subdir.ucfirst($class).EXT;
if ( ! file_exists($baseclass))
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
この状態でもう一度アクセスしてみます。
An Error Was Encountered
Invalid driver requested: MY_Cache_memcached
良い傾向ですね。どうやらMY_Cacheは読み込まれたようです。ただ、まだまだ先は長そうです。。。
driversも継承してね
CodeIgniter 2のソースを見たことがある方はもう見えてきましたね。ただ、説明はまだまだ続きます。
system/libraries/Driver.php
先ほど出たエラーですが、コレは下記の一番下の方で発生しています。具体的にどういうものかというと、まず、__getメソッドですが、コレはマジックメソッドというもので、自身のフィールドでもないのに参照しようとした場合に呼ばれるメソッドです。Cache.phpを例にして説明すると、例えば下記のコードです。
public function get($id)
{
return $this->{$this->_adapter}->get($id);
} |
public function get($id)
{
return $this->{$this->_adapter}->get($id);
}
さらに上記の部分のここです。
return $this->{$this->_adapter}->get($id); |
return $this->{$this->_adapter}->get($id);
実行時には{$this->_adapter}が展開されて実際には下記のようにアクセスすることになります。
return $this->memcached->get($id); |
return $this->memcached->get($id);
Cache.phpにはmemcachedなんてフィールドはありませんよね?なので、スーパクラスであるCI_Driver_Libraryの__getメソッドが呼ばれます。さらにこの時、重要なのが、__getの引数には、アクセスしたフィールド名が渡されます。先程の例の場合、「memcached」が下記の$child変数に入ることになります。
function __get($child)
{
if ( ! isset($this->lib_name))
{
$this->lib_name = get_class($this);
}
// The class will be prefixed with the parent lib
$child_class = $this->lib_name.'_'.$child;
if (in_array(strtolower($child_class), array_map('strtolower', $this->valid_drivers)))
{
// check and see if the driver is in a separate file
if ( ! class_exists($child_class))
{
// check application path first
foreach (array(APPPATH, BASEPATH) as $path)
{
// and check for case sensitivity of both the parent and child libs
foreach (array(ucfirst($this->lib_name), strtolower($this->lib_name)) as $lib)
{
// loves me some nesting!
foreach (array(ucfirst($child_class), strtolower($child_class)) as $class)
{
$filepath = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_class.EXT;
if (file_exists($filepath))
{
include_once $filepath;
break;
}
}
}
}
// it's a valid driver, but the file simply can't be found
if ( ! class_exists($child_class))
{
log_message('error', "Unable to load the requested driver: ".$child_class);
show_error("Unable to load the requested driver: ".$child_class);
}
}
$obj = new $child_class;
$obj->decorate($this);
$this->$child = $obj;
return $this->$child;
}
// The requested driver isn't valid!
log_message('error', "Invalid driver requested: ".$child_class);
show_error("Invalid driver requested: ".$child_class);
} |
function __get($child)
{
if ( ! isset($this->lib_name))
{
$this->lib_name = get_class($this);
}
// The class will be prefixed with the parent lib
$child_class = $this->lib_name.'_'.$child;
if (in_array(strtolower($child_class), array_map('strtolower', $this->valid_drivers)))
{
// check and see if the driver is in a separate file
if ( ! class_exists($child_class))
{
// check application path first
foreach (array(APPPATH, BASEPATH) as $path)
{
// and check for case sensitivity of both the parent and child libs
foreach (array(ucfirst($this->lib_name), strtolower($this->lib_name)) as $lib)
{
// loves me some nesting!
foreach (array(ucfirst($child_class), strtolower($child_class)) as $class)
{
$filepath = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_class.EXT;
if (file_exists($filepath))
{
include_once $filepath;
break;
}
}
}
}
// it's a valid driver, but the file simply can't be found
if ( ! class_exists($child_class))
{
log_message('error', "Unable to load the requested driver: ".$child_class);
show_error("Unable to load the requested driver: ".$child_class);
}
}
$obj = new $child_class;
$obj->decorate($this);
$this->$child = $obj;
return $this->$child;
}
// The requested driver isn't valid!
log_message('error', "Invalid driver requested: ".$child_class);
show_error("Invalid driver requested: ".$child_class);
}
CI_Driver_Libraryの__getメソッドで何をやっているかというと、アクセスしたフィールドの名称をもとにdrivers以下に配置された実態をインスタンス化してしまおうというものです。ただ、この時、適当な名前ではなく、下記のような名前である必要がある。
$this->lib_nameには、自身のインスタンス化されているクラス名。今回の場合はMY_Cacheが入ります。ので、サブクラスとして呼びだそうとしているのは、「MY_Cache_memcached」ということになります。
if ( ! isset($this->lib_name))
{
$this->lib_name = get_class($this);
}
// The class will be prefixed with the parent lib
$child_class = $this->lib_name.'_'.$child; |
if ( ! isset($this->lib_name))
{
$this->lib_name = get_class($this);
}
// The class will be prefixed with the parent lib
$child_class = $this->lib_name.'_'.$child;
また、MY_Cache_memcachedなど、対象のクラス名に関しては、MY_Cache.phpのフィールド「valid_drivers」に下記のようにクラス名を小文字化したものを指定する必要があります。
< ?php
class MY_Cache extends Cache {
protected $valid_drivers = array(
'my_cache_apc', 'my_cache_file', 'my_cache_memcached', 'my_cache_dummy'
);
} |
< ?php
class MY_Cache extends Cache {
protected $valid_drivers = array(
'my_cache_apc', 'my_cache_file', 'my_cache_memcached', 'my_cache_dummy'
);
}
さらに下記のようにdrivers/配下にクラスを追加します。
application/libraries/Cache/drivers/MY_Cache_apc.php
< ?php
class MY_Cache_apc extends Cache_apc {
} |
< ?php
class MY_Cache_apc extends Cache_apc {
}
application/libraries/Cache/drivers/MY_Cache_file.php
< ?php
class MY_Cache_file extends Cache_file {
} |
< ?php
class MY_Cache_file extends Cache_file {
}
application/libraries/Cache/drivers/MY_Cache_memcached.php
< ?php
class MY_Cache_memcached extends Cache_memcached {
} |
< ?php
class MY_Cache_memcached extends Cache_memcached {
}
application/libraries/Cache/drivers/MY_Cache_dummy.php
< ?php
class MY_Cache_dummy extends Cache_dummy {
} |
< ?php
class MY_Cache_dummy extends Cache_dummy {
}
これで、やっと終わりですね。また、アクセスしてみましょう。
An Error Was Encountered
Unable to load the requested driver: MY_Cache_memcached
変わっていませんね。。。困った時はログ出力を最大にして何が表示されるか見てみましょう。
DEBUG - 2011-03-24 23:39:17 --> Loader Class Initialized
DEBUG - 2011-03-24 23:39:17 --> Controller Class Initialized
ERROR - 2011-03-24 23:39:17 --> Unable to load the requested driver: MY_Cache_memcached
どうやら、先ほどのCI_Driver_Libraryの__getメソッド内でMY_Cache_memcachedが見つからないと言われているようです。CI_Driver_Libraryにログを追加して読み込もうとしているファイルパスを出してみます。
$filepath = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_class.EXT;
+ log_message('debug', "load driver=$filepath"); |
$filepath = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_class.EXT;
+ log_message('debug', "load driver=$filepath");
ログを追加後アクセスすると、こんなログが出ました。
DEBUG - 2011-03-24 23:42:47 --> Controller Class Initialized
DEBUG - 2011-03-24 23:42:47 --> load driver=application/libraries/MY_Cache/drivers/MY_Cache_memcached.php
DEBUG - 2011-03-24 23:42:47 --> load driver=application/libraries/MY_Cache/drivers/MY_Cache_memcached.php
DEBUG - 2011-03-24 23:42:47 --> load driver=application/libraries/MY_Cache/drivers/MY_Cache_memcached.php
DEBUG - 2011-03-24 23:42:47 --> load driver=application/libraries/MY_Cache/drivers/MY_Cache_memcached.php
DEBUG - 2011-03-24 23:42:47 --> load driver=/home/tatsuya/workspace/hg/fukata-codeigniter-reactor/system/libraries/MY_Cache/drivers/MY_Cache_memcached.php
DEBUG - 2011-03-24 23:42:47 --> load driver=/home/tatsuya/workspace/hg/fukata-codeigniter-reactor/system/libraries/MY_Cache/drivers/MY_Cache_memcached.php
DEBUG - 2011-03-24 23:42:47 --> load driver=/home/tatsuya/workspace/hg/fukata-codeigniter-reactor/system/libraries/MY_Cache/drivers/MY_Cache_memcached.php
DEBUG - 2011-03-24 23:42:47 --> load driver=/home/tatsuya/workspace/hg/fukata-codeigniter-reactor/system/libraries/MY_Cache/drivers/MY_Cache_memcached.php
ERROR - 2011-03-24 23:42:47 --> Unable to load the requested driver: MY_Cache_memcached
想定していた
application/libraries/Cache/drivers/MY_Cache_memcached.php
ではなく、
application/libraries/MY_Cache/drivers/MY_Cache_memcached.php
を読み込んでいるようです。
これは、先ほどログを追加した直前のコードを見ればわかります。
$filepath = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_class.EXT; |
$filepath = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_class.EXT;
lib_nameはインスタンス化されているクラス名だったので、「MY_Cache」になりますね。よって、driversディレクトリを移動します。移動後のapplication/librariesディレクトリの中身はこのようになっています。
./
|-- Cache
| `-- MY_Cache.php
|-- MY_Cache
| `-- drivers
| |-- MY_Cache_apc.php
| |-- MY_Cache_dummy.php
| |-- MY_Cache_file.php
| `-- MY_Cache_memcached.php
`-- index.html
これで、もう一度アクセスします。
Fatal error: Class 'Cache_memcached' not found in /home/tatsuya/workspace/hg/fukata-codeigniter-reactor/application/libraries/MY_Cache/drivers/MY_Cache_memcached.php on line 2
サブクラス化が考慮されていないので、スーパクラスが読み込まれているわけがありませんね・・・。ということで、drivers/配下のそれぞれのファイルに汚いですが、require_onceを追加します。
application/libraries/MY_Cache/drivers/MY_Cache_apc.php
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_apc.php';
class MY_Cache_apc extends Cache_apc {
} |
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_apc.php';
class MY_Cache_apc extends Cache_apc {
}
application/libraries/MY_Cache/drivers/MY_Cache_file.php
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_file.php';
class MY_Cache_file extends Cache_file {
} |
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_file.php';
class MY_Cache_file extends Cache_file {
}
application/libraries/MY_Cache/drivers/MY_Cache_memcached.php
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_memcached.php';
class MY_Cache_memcached extends Cache_memcached {
} |
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_memcached.php';
class MY_Cache_memcached extends Cache_memcached {
}
application/libraries/MY_Cache/drivers/MY_Cache_dummy.php
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_dummy.php';
class MY_Cache_dummy extends Cache_dummy {
} |
< ?php
require_once BASEPATH.'libraries/Cache/drivers/Cache_dummy.php';
class MY_Cache_dummy extends Cache_dummy {
}
これで、再度アクセスしてみます。
やっと表示されました!!!これで、Cache.phpの拡張が出来ました。最後にCacheやCI_Driver_Libraryをスーパクラスに持つクラスのサブクラス化についてはまだ考慮されていないですし、考慮しようとした形跡が見られるコードも見当たりません。よって、今回のCacheクラスのサブクラス化については激しく自己責任でお願いします。
CodeIgniterの中の人がこの辺もサブクラス化を視野に入れているのかわかりませんが、実際の案件では要件としてサブクラス化したい事もあると思います。なので、もう少しスマートにサブクラス化できるように修正したものをPull Requestしたいと思います。
とりあえず、Issueに追加してみました。いつものように英語はGoogle翻訳のものをそのまま利用しているので見苦しい部分はあるかと思います。。。
ellislab / CodeIgniter Reactor / issues / #163 – extends Cache class — Bitbucket
関連記事