除了动态地修正常量外,我们还可以利用 runkit_constant_add() 、 runkit_constant_remove() 函数来动态地增加或者删除常量。
安装runkit 扩展的安装是须要在 github 下载然后进行正常的扩展编译即可,pecl 下载的已经由时了。
PHP5: http://github.com/zenovich/runkit
PHP7:https://github.com/runkit7/runkit7.git
clone 成功后进行正常的扩展编译安装步骤即可。
phpize./configuremakemakeinstall
不同的 PHP 版本须要安装不同版本的扩展,同时,runkit7 还在开拓中,有一些函数还没有支持,比如:
runkit_class_adoptrunkit_class_emancipaterunkit_importrunkit_lint_filerunkit_lintrunkit_sandbox_output_handlerrunkit_return_value_usedRunkit_SandboxRunkit_Sandbox_Parent在写这篇文章的测试代码时,上述函数或者类都是不支持的。大家可以用 PHP5 的环境测试下原版的扩展是否都能正常利用。
查看超全局变量键print_r(runkit_superglobals());//Array//(//[0]=>GLOBALS//[1]=>_GET//[2]=>_POST//[3]=>_COOKIE//[4]=>_SERVER//[5]=>_ENV//[6]=>_REQUEST//[7]=>_FILES//[8]=>_SESSION//)
这个函数实在便是查看下当前运行环境中的所有超全局变量键名。这些都是我们常用的一些超全局变量,就不一一阐明了。
方法干系操作方法操作就和常量操作一样,我们可以动态地添加、修正、删除以及重命名各种方法。首先还是来看一下我们最关心的在动态运行时来修正方法体里面的逻辑代码。
functiontestme(){echo"OriginalTestmeImplementation\n";}testme();//OriginalTestmeImplementationrunkit_function_redefine(39;testme','','echo"NewTestmeImplementation\n";');testme();//NewTestmeImplementation
定义了一个 testme() 方法,然后通过 runkit_function_redefine() 来修正它的实现,末了再次调用 testme() 时输出的便是新修正后的实现了。那么,我们能不能修正 PHP 自带的那些方法呢?
//php.inirunkit.internal_override=1runkit_function_redefine('str_replace','','echo"str_replacechanged!\n";');str_replace();//str_replacechanged!runkit_function_rename('implode','joinArr');var_dump(joinArr(",",['a','b','c']));//string(5)"a,b,c"array_map(function($v){echo$v,PHP_EOL;},[1,2,3]);//1//2//3runkit_function_remove('array_map');//array_map(function($v){//echo$v;//},[1,2,3]);//PHPFatalerror:UncaughtError:Calltoundefinedfunctionarray_map()
代码里的注释说的很清楚了,我们只须要在 php.ini 中设置 runkit.internal_override=1 ,就可以动态地修正 PHP 自带的那些方法函数了。比如第一段我们修正了 str_replace() 方法,让他直接就输出了一段笔墨。然后我们将 implode() 改名为 joinArr() ,就可以像 implode() 一样来利用这个 joinArr() 。末了,我们删除了 array_map() 方法,如果再次调用这个方法,就会报错。
类方法干系操作类内部方法函数的操作和上面变量方法操作是类似的,不过对付 PHP 自带的类我们无法进行修正之类的操作。这个大家可以自己考试测验一下。
//runkit_method_add('PDO','testAddPdo','','echo"ThisisPDOnewFunc!\n";');//PDO::testAddPdo();//PHPWarning:runkit_method_add():classPDOisnotauser-definedclass
从报错信息可以看出,PDO 类不是用户定义的类,以是无法利用 runkit 函数进行干系操作。那我们就来看看我们自定义的类是如何利用 runkit 来进行动态操作的吧。
classExample{}runkit_method_add('Example','func1','','echo"ThisisFunc1!\n";');runkit_method_add('Example','func2',function(){echo"ThisisFunc2!\n";});$e=newExample;$e->func1();//ThisisFunc1!$e->func2();//ThisisFunc2!runkit_method_redefine('Example','func1',function(){echo"NewFunc1!\n";});$e->func1();//NewFunc1!runkit_method_rename('Example','func2','func22');$e->func22();//ThisisFunc2!runkit_method_remove('Example','func1');//$e->func1();//PHPFatalerror:UncaughtError:CalltoundefinedmethodExample::func1()
我们定义了一个空类,然后动态给它添加了两个方法,之后修正了方法1,重命名了方法2,末了删除了方法1,一系列的操作实在和上面的普通方法的操作基本是一样的。
总结就像上面说过的一样,这个扩展是比较危险的一个扩展,特殊是如果开启了 runkit.internal_override 后,我们还能够修正 PHP 的原生函数。不过如果是必须要利用它的话,那么它的这些功能就非常有用。就像 访问者模式 一样,“大多时候你并不须要访问者模式,但当一旦你须要访问者模式时,那便是真的须要它了”,这一套 runkit 扩展也是一样的道理。
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202006/source/%E4%B8%80%E8%B5%B7%E5%AD%A6%E4%B9%A0PHP%E7%9A%84runkit%E6%89%A9%E5%B1%95%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8.php
参考文档:
https://www.php.net/manual/zh/book.runkit.php
https://www.php.net/manual/zh/book.runkit7.php