代理模式
代理模式其实就类别的一种类似网络代理这样,当请求某个方法或者接口的时候,不直接请求,而且做一层代理,来代理请求。这样的好处是,如果在请求真正方法前做一些行为上的事情,例如写入日志,操作之后统计时间等,这样使用代理可以轻松解决,而没有改变真正的业务。
静态代理
静态代理是针对某一个具体的业务做的具体代理,不具备通用性。例如有个新闻代理类,在请求新闻之后写入日志。
INews.php
<?php
namespace DesignProxy;
interface INews
{
public function edit(int $id);
public function list();
}
News.php
<?php
namespace DesignProxy;
class News implements INews
{
public function edit(int $id)
{
echo "编辑新闻中业务" . PHP_EOL;
}
public function list()
{
echo "新闻列表";
}
}
NewsProxy.php
<?php
namespace DesignProxy;
/**
* 新闻静态代理类,在请求编辑新闻的前加入对应的操作,在请求处理完成之后
* Class NewProxy
* @package DesignProxy
*/
class NewProxy implements INews
{
/**
* @var INews
*/
protected $newsObj;
public function __construct(INews $news)
{
$this->newsObj = $news;
}
public function edit(int $id)
{
$this->beforeRequestLog();
$this->newsObj->edit($id);
$this->afterSqlLog();
}
public function list()
{
$this->newsObj->list();
}
/**
* 模拟业务前置记录日志方法
*/
private function beforeRequestLog()
{
echo "我是在记录请求日志" . PHP_EOL;
}
/**
* 记录请求完成之后日志
*/
private function afterSqlLog()
{
echo "记录请求日志" . PHP_EOL;
}
}
动态代理
上面的静态代理模式是针对于在具体的代理类里面进行特定方法的操作。耦合比较大,而且是针对具体的方法做的不同静态代理。使用动态代理的话,可以进行外部依赖注入进代理类里面进行相关操作。
<?php
namespace DesignProxy;
/**
* 实现了动态代理,所有的前置方法和后置方法都在外部依赖注入进来的
* Class NewsProxy2
* @package DesignProxy
*/
class NewsProxy2
{
private $obj;
private $beforeFunc, $afterFunc = [];
// 这里是配置哪些方法可以进行前置方法和后置方法操作,其实这里可以借鉴一些规则,例如以什么开头的方法使用前置
private $beforeDoFunc = [
'edit'
];
private $afterDoFunc = [
'edit'
];
public function __construct(object $obj)
{
$this->obj = $obj;
}
public function setBeforeFunc(callable $func)
{
$this->beforeFunc[] = $func;
}
public function setAfterFunc(callable $func)
{
$this->afterFunc[] = $func;
}
public function __call($methodName, $args)
{
$refObj = new ReflectionObject($this->obj);
if (!$refObj->hasMethod($methodName)) {
throw new Exception("has no this method");
}
if (in_array($methodName, $this->beforeDoFunc)) {
foreach ($this->beforeFunc as $k => $before) {
$before();
}
}
$method = $refObj->getMethod($methodName);
// $res = $this->obj->$method(...$args);
$res = $method->invoke($this->obj, ...$args);
if (in_array($methodName, $this->afterDoFunc)) {
foreach ($this->afterFunc as $k => $after) {
$after();
}
}
return $res;
}
}
AOP切面编程
其实切面编程就是上面动态代理模式的一个升级版本,针对不同类型的行为进行横向切入,例如执行方法前,执行方法后,出现异常之后等操作的切入,而且像java中使用注解的方式更加的方便。其实可以使用PHP的注释加反射机制实现这样的操作
<?php
namespace DesignProxy;
class NewsProxy3
{
private $obj;
private $asp;
public function __construct(object $obj, NewsAsp $asp)
{
$this->obj = $obj;
$this->asp = $asp;
}
public function __call($methodName, $args)
{
$refObj = new ReflectionObject($this->obj);
if (!$refObj->hasMethod($methodName)) {
throw new Exception("has no this method");
}
$method = $refObj->getMethod($methodName);
$this->invokeAdvice($methodName, [], '@before');
$res = $method->invoke($this->obj, ...$args);
$this->invokeAdvice($methodName, [], '@after');
return $res;
}
private function invokeAdvice(string $methodName, $args, string $advice)
{
if (!$this->asp->isJoinPoint($methodName)) {
return;
}
$asp = new ReflectionClass($this->asp);//切面反射对象
$aspMethods = $asp->getMethods(ReflectionMethod::IS_PUBLIC);//获取所有方法
foreach ($aspMethods as $aspMethod) {
if (preg_match("/$advice/i", $aspMethod->getDocComment())) {
$aspMethod->invoke($this->asp, $args);
}
}
}
}
<?php
namespace DesignProxy;
class NewsAsp
{
private $joinPoint; //切入点
public function setJoinPoint(string $joinPoint)
{
$this->joinPoint = $joinPoint;
}
public function isJoinPoint($methodName)
{
return preg_match($this->joinPoint, $methodName);
}
/**
* @before()
*/
public function checkUserLogin()
{
echo "[判断用户登录]
";
}
/**
* @before()
*/
public function checkSelf()
{
echo "[判断用户自己]
";
}
/**
* @after()
*/
public function setLog()
{
echo "[设置日志]
";
}
}