[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配置詳解