[TOC]
PHP 的反射 API 很多,但是常用的一般都是 ReflectionClass 和 ReflectionMethod:
# ReflectionClass
这个是用来获取类的信息,可以简单测试一下:
~~~
class Student {
private $name;
public function setName($name)
{
$this->name = $name;
}
protected function getName()
{
return $this->name;
}
}
~~~
获取类的方法列表:
~~~
$ref = new ReflectionClass(Student::class);
var_dump($ref->getMethods());
~~~
返回的是一个 ReflectionMethod 的数组:
~~~
array(1) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(7) "setName"
["class"]=>
string(7) "Student"
}
}
~~~
附上一些常用方法,详细的可以查看文档:
~~~
ReflectionClass::getMethods 获取方法的数组
ReflectionClass::getName 获取类名
ReflectionClass::hasMethod 检查方法是否已定义
ReflectionClass::hasProperty 检查属性是否已定义
ReflectionClass::isAbstract 检查类是否是抽象类(abstract)
ReflectionClass::isFinal 检查类是否声明为 final
ReflectionClass::isInstantiable 检查类是否可实例化
ReflectionClass::newInstance 从指定的参数创建一个新的类实例
~~~
# ReflectionMethod
这个主要是针对方法的反射,我们可以简单执行一下:
~~~
$stu = new Student();
$ref = new ReflectionClass(Student::class);
$method = $ref->getMethod('setName');
$method->invoke($stu, 'john');
var_dump($stu->name);
~~~
可以输出:
john
附上一些常用的方法,详细的可以去看看文档:
~~~
ReflectionMethod::invoke 执行
ReflectionMethod::invokeArgs 带参数执行
ReflectionMethod::isAbstract 判断方法是否是抽象方法
ReflectionMethod::isConstructor 判断方法是否是构造方法
ReflectionMethod::isDestructor 判断方法是否是析构方法
ReflectionMethod::isFinal 判断方法是否定义 final
ReflectionMethod::isPrivate 判断方法是否是私有方法
ReflectionMethod::isProtected 判断方法是否是保护方法 (protected)
ReflectionMethod::isPublic 判断方法是否是公开方法
ReflectionMethod::isStatic 判断方法是否是静态方法
ReflectionMethod::setAccessible 设置方法是否访问
~~~
接下来说一些反射在实际开发中比较常见的应用。
# 执行私有方法
其实反射不仅可以执行私有方法,还可以读取私有属性。这个主要应用在一些设计不合理的 SDK 里面,一些很好用的方法和属性却不对外开放。
~~~
class Student {
private $name;
private function setName($name)
{
$this->name = $name;
}
}
~~~
执行私有方法:
~~~
$stu = new Student();
$ref = new ReflectionClass($stu);
$method = $ref->getMethod('setName');
$method->setAccessible(true);
$method->invoke($stu, 'john');
~~~
读取私有属性:
~~~
$stu = new Student();
$ref = new ReflectionClass($stu);
$prop = $ref->getProperty('name');
$prop->setAccessible(true);
$val = $prop->getValue($stu);
var_dump($val);
~~~
# 动态代理
其实 PHP 有魔术方法,所以实现动态代理已经很简单了,但是通过魔术方法来实现的都不完美,个人理解最好的实现应该还是 JDK 中的动态代理,基于一个接口进行扫描实现实在 PHP 中也可以实现。我们先来看看动态代理在 JDK 中是怎么使用的:
1.首先定义一个实现类的接口,JDK 的动态代理必须基于接口(Cglib则不用)
~~~
package com.yao.proxy;
public interface Helloworld {
void sayHello();
}
~~~
2.定义一个实现类,这个类就是要被代理的对象
~~~
package com.yao.proxy;
import com.yao.HelloWorld;
public class HelloworldImpl implements HelloWorld {
public void sayHello() {
System.out.print("hello world");
}
}
~~~
3.调用被代理对象方法的实现类
~~~
package com.yao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler{
private Object target;
public MyInvocationHandler(Object target) {
this.target=target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置工作!");
Object obj = method.invoke(target,args);
System.out.println("后置工作!");
return obj;
}
~~~
4.测试
~~~
package com.yao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Demo {
public static void main(String[] args) {
HelloworldImpl realSubject = new HelloworldImpl();
MyInvocationHandler handler = new MyInvocationHandler(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
HelloworldImpl proxySubject = (HelloworldImpl) Proxy.newProxyInstance(loader, interfaces, handler);
String hello = proxySubject.sayHello();
}
}
~~~
JDK 的动态代理在底层实际上是扫描实现的接口,然后动态生成类的字节码文件。PHP 是动态语言,所以可以用 eval 来实现。
1.定义调度器接口
~~~
interface InvocationHandler
{
function invoke($method, array $arr_args);
}
~~~
2.动态代理实现
定义一个类的 stub:
~~~
return new Class($handler,$target) implements %s {
private $handler;
private $target;
public function __construct(InvocationHandler $handler, $target) {
$this->handler = $handler;
$this->target = $target;
}
%s
};
~~~
定义一个方法的 stub:
~~~
public function %s(%s) {
$args = func_get_args();
$method = explode("::", __METHOD__);
$this->handler->invoke(new ReflectionMethod($this->target, $method[1]), $args);
}
~~~
Proxy 实现:
~~~
final class Proxy
{
const CLASS_TEMPLATE = class_stub; //这里显示上面定义的,为了方便阅读
const FUNCTION_TEMPLATE = function_stub; //同上
public static function newProxyInstance($target, array $interfaces, InvocationHandler $handler) {
}
protected static function generateClass(array $interfaces) {
}
protected static function checkInterfaceExists(array $interfaces) {
}
}
~~~
其中 newProxyInstance 和 generateClass 代码:
~~~
public static function newProxyInstance($target, array $interfaces, InvocationHandler $handler) {
self::checkInterfaceExists ($interfaces);
$code = self::generateClass ($interfaces);
return eval($code);
}
protected static function generateClass(array $interfaces)
{
$interfaceList = implode(',', $interfaces);
$functionList = '';
foreach ($interfaces as $interface) {
$class = new ReflectionClass ($interface);
$methods = $class->getMethods();
foreach ($methods as $method){
$parameters = [];
foreach ($method->getParameters() as $parameter){
$parameters[] = '$' . $parameter->getName();
}
$functionList .= sprintf( self::FUNCTION_TEMPLATE, $method->getName(), implode( ',', $parameters ) );
}
}
return sprintf ( self::CLASS_TEMPLATE, $interfaceList, $functionList );
}
~~~
其中generateClass就是通过反射扫描接口方法,然后根据 stub 模板生成方法拼接成代码,最后通过 eval 执行。
2.测试
~~~
interface Test1{
public function t1();
}
interface Test2{
public function t2();
}
class TestImpl implements Test1,Test2{
public function t1(){
echo 't1';
}
public function t2(){
echo 't2';
}
}
$impl = new TestImpl();
class Handler implements InvocationHandler {
private $target;
public function __construct($impl){
$this->target = $impl;
}
function invoke(ReflectionMethod $method, array $arr_args){
echo '前置操作';
$method->invokeArgs($this->target, $arr_args);
echo '后置操作';
}
}
$proxy = Proxy::newProxyInstance($impl, ['Test1', 'Test2'], new Handler($impl));
$proxy->t1();
~~~
输出:
前置操作
t1
后置操作
# 依赖注入
依赖注入是现代化框架中非常常见的一个功能,它必须和服务容器结合使用。用过 Laravel 框架的童鞋应该很熟悉,我们可以在任意需要服务的地方通过类型提示声明,运行时框架就会自动帮我们注入所需要的对象。以 Laravel 框架的源码简单解析下:
在 Laravel 框架中,我们解析一个对象的方法可以这样:
~~~
$obj = App::make(ClassName);
~~~
make方法实际上底层也是调用了Illuminate\Container\Contaiern::build($concrete)这个方法,整理一下源码就是:
~~~
public function build($concrete){
$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instances = $this->resolveDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
~~~
实际代码很简单,反射类获取构造方法,然后解析依赖参数,传入执行。
- OAuth
- 简介
- 步骤
- 单点登录
- .user.ini
- 时间转换为今天昨天前天几天前
- 获取ip接口
- 协程
- 概念
- yield-from && return-values
- 协程与阻塞的思考
- 中间件
- mysqli异步与php的协程
- 代码片段
- pdo 执行的sql语句
- 二进制安全
- 捕捉异常中断
- global
- 利用cookie模拟登陆
- 解析非正常json
- 简单的对称加密算法
- RSA 加密
- 过滤掉emoji表情
- 判断远程图片是否存在
- 一分钟限制请求100次
- 文件处理
- 多文件上传
- 显示所有文件
- 文件下载和上面显示所有文件配合
- 文件的删除,统计,存数组等
- 图片处理
- 简介
- 验证码
- 图片等比缩放
- 批量添加水印
- beanstalkd
- 安装
- 使用
- RabbitMQ
- 简介
- debain安装
- centos安装
- 常用方法
- 入门
- 工作队列
- 订阅,发布
- 路由
- 主题
- 远程调用RPC
- 消息中间件的选型
- .htaccess
- isset、empty、if区别以及0、‘’、null
- php各版本
- php7.2 不向后兼容的改动
- php中的各种坑
- php7改变
- php慢日志
- 邮件
- PHPMailer实现发邮件
- 验证邮件地址真实性
- 文件下载
- FastCgi 与 PHP-fpm 之间的关系
- openssl 加解密
- 反射
- 钩子方法
- 查找插件
- opcode
- opcache使用
- opcache优化
- 分布式一致性hash算法
- 概念
- 哈希算法好坏的四个定义
- php实现
- java实现
- 数组
- jwt
- jwt简介
- 单点登录
- phpize
- GeoIP扩展
- php无法获得https网页内容的解决方案
- homestead运行的脚本
- Unicode和Utf-8转换
- php优化
- kafka
- fpm配置
- configure配置详解