大家有没有想过在PHP中如何使用redis作为缓存:1、前后台模块共享Model层;2.但是不能缓存每个Model类,浪费Redis资源;3.前后端模块可以自由决定是从数据库还是从缓存中读取数据;4.没有冗余代码;5.使用方便。这里先展示一下最终实现的效果。最终代码和使用说明请移步Github:https://github.com/yeszao/php-redis-cache马上安装使用命令:$composerinstallyeszao/cache简单配置后即可使用,请参考Github的README说明。1、最终效果假设在MVC框架中,模型层有一个Book类和一个getById方法,如下:classBook{publicfunctiongetById($id){return$id;}}加入缓存技术后,原来的方法调用methodNeither返回的数据结构应该改变了。所以,我们希望最终的效果应该是这样的:(newBook)->getById(100);//没有缓存调用的原方法,或者原方法,一般是读取数据库的数据(newBook)->getByIdCache(100);//使用缓存调用方法,缓存键名是:app_models_book:getbyid:+md5(参数列表)(newBook)->getByIdClear(100);//删除缓存(新书)->getByIdFlush();//删除getById()方法对应的所有缓存,即删除app_models_book:getbyid:*。此方法不带参数。这样一来,我们就可以清楚的知道自己在做什么,同时知道数据的来源功能,被引用的方式也完全统一了,可以说是一箭三雕。其实实现起来比较简单,就是用PHP的魔术方法__call()方法。2.__call()方法这里简单介绍一下__call方法的作用。在PHP中,当我们访问一个不存在的类方法时,就会调用这个类的__call()方法。(如果类方法不存在,没有写__call()方法,PHP会直接报错)假设我们有一个Book类:classBook{publicfunction__call($name,$arguments){echo'有类Book'没有方法,$name,PHP_EOL;}publicfunctiongetById($id){echo'我的ID是',$id,PHP_EOL;}}当调用已有的getName(50)方法时,程序打印:MyIDis50。如果调用不存在的getAge()方法,程序会执行到A类的__call()方法中,将打印:类Book中不存在方法getAge。这就是__call的工作原理。3.实现细节接下来,我们将使用__call()方法的这个特性来实现缓存策略。从上面的例子我们看到,调用__call()方法时,传入了两个参数。仍然以Book类为例,我们假设它的原始结构如下:'id'=>$id,'title'=>'PHP缓存技术'.$id];}}在开始之前,我们还要确认Redis的连接,这是缓存所必需的。这里我们写了一个简单的单例类:classCommon{privatestatic$redis=null;publicstaticfunctionredis(){if(self::$redis===null){self::$redis=new\Redis('127.0.0.1');self::$redis->connect('redis');}returnself::$redis;}然后,我们开始填写__call()方法代码,具体请看注释:classBook{publicfunction__call($name,$arguments){//因为我们主要判断具体操作根据方法名的后缀,//所以如果传入的$name长度小于5,可以直接报错if(strlen($name)<5){exit('Method不存在。');}//接下来我们截取$name来获取原来的方法和要执行的action,//不管是cache,clear还是flush,这里有个trick,action的名字//是5个字符,所以拦截非常高效。$method=substr($name,0,-5);$action=substr($name,-5);//当前调用的类名,包括命名空间名$class=get_class();//生成缓存Key名,$arguments后面会加上$key=sprintf('%s:%s:',str_replace('\\','_',$class),$method);//使用小写看起来更好$key=strtolower($key);switch($action){case'Cache'://缓存键名加上$arguments$key=$key.md5(json_encode($arguments));//从Redis读取数据$data=Common::redis()->get($key);//如果Redis中有数据if($data!==false){$decodeData=json_decode($data,JSON_UNESCAPED_UNICODE);//如果数据不是json格式,直接返回,否则返回json解析后的数据return$decodeData===null?$数据:$解码数据;}//如果Redis中没有数据,继续执行//如果原来的方法不存在if(method_exists($this,$method)===false){exit('方法不存在。');}//调用原始方法获取数据$data=call_user_func_array([$this,$method],$arguments);//将数据保存到Redis中供下次使用Common::redis()->set($key,json_encode($data),3600);//结束执行并返回数据return$data;休息;case'Clear'://缓存键名加上$arguments$key=$key.md5(json_encode($arguments));返回Common::redis()->del($key);休息;case'Flush':$key=$key。'*';//获取所有匹配的$class:$method:*规则的缓存键名$keys=Common::redis()->keys($key);返回Common::redis()->del($keys);休息;默认值:exit('方法不存在。');}}//其他方法}这实现了我们开始的效果4、在实际使用中在实际使用中,我们需要做一些改动,将这段代码归为一个类,然后在模型层的基类中引用这个类,然后传入Redis句柄、类对象、方法name和parameters,可以降低代码的耦合度,使用起来更加灵活。
