这是我参与更文挑战的第10天,活动详情查看: 更文挑战
下钩子hooks与魔术方法
在php类的定义中, 还有一种特殊的成员方法, 他们有特殊的触发方式, 以实现一些特殊的应用, 他们以双下划线开头"__", 称为魔术方法, 我们今天就来看看魔术方法以及他们是如何下钩子的.
钩子函数
所谓钩子函数, 是在正常的程序执行过程中, 插入一个钩点(hook), 然后通过这个钩点, 先去执行钩子函数, 之后再回来执行主程序, 或者干脆不执行主程序. 简单地讲,就是“要想从这过,留下买路财".
在PHP中将这种下钩子的行为成为"overloading", 不过这和我们普通意义上的overloading是两回事儿, 传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。而PHP其实是不支持重载的!
所以在官方文档介绍overloading的时候, PHPer们更倾向于把它称作"下钩子"(hook).
PHP中是通过魔术方法来实现下钩子的: 简单讲, 就是在类定义时, 加进一些魔术方法, 这些魔术方法会针对一些特殊的关键词, 比如new, echo, var_export() 或者当调用一些不可访问的属性和方法时, 自动触发, 实现一些非常规的输出结果.
我们来详细介绍一些常见的魔术方法.
__toString()
这个魔术方法可以允许一个类去决定, 当它被当作一个字符串时, 如何响应. 比如说"echo $obj;"时要输出什么.
如果类中没有定义__toString()方法, 直接打印对象的话, 会报错. 如果定义了该方法, 那么当使用echo/print/printf打印对象时, 都会调用__toString();
举个例子:
class ABC
{
private $ab = 'DIT';
public $cd= '123';
protected $ef='true';
public function __toString()
{
return (string)var_dump($this); //将结果转换为字符串格式
}
}
$bba=new ABC();
echo $bba; //output:object(ABC)#5 (3) {
["ab":"ABC":private]=>
string(3) "DIT"
["cd"]=>
string(3) "123"
["ef":protected]=>
string(4) "true"}
在使用__toString()时, 返回值必须是字符串类型, 否则会报错. 另外这个方法只有在打印对象时会被调用, 打印其他类型对象时不会被调用.
__set_state()
这个魔术方法可以让一个对象的属性赋给另一个对象, 不过使用起来稍微有些复杂:
-
输出, 首先当调用 var_export() 导出对象$a时,此静态方法会被调用. 比如var_export($a, true)返回值是一个字符串, 是关于变量$a的结构信息.
-
转存, 然后__set_state(array $properties)会将$a的属性自动存放到$properties这个数组当中, 属性名=>属性值的格式, 但此时, 整个函数的表达式仍然是字符串格式, 是无法执行的.
-
执行, 将var_export()放到eval()函数中, 执行整个语句.
我们来看一下例子:
class A
{
public $var1;
public $var2;
public static function __set_state($an_array)
{
$obj = new A;
$obj->var1 = $an_array['var1'];
$obj->var2 = $an_array['var2'];
return $obj;
}
}
$a = new A;
$a->var1 = 5;
$a->var2 = 'foo';
eval('$b = ' . var_export($a, true) . ';');
var_dump($b);
//output: object(A)#5 (2) {
["var1"]=>
int(5)
["var2"]=>
string(3) "foo"}
我们来一步步分析一下:
-
输出, var_export($a, true), 会返回以下字符串:
string(60) "A::__set_state(array( 'var1' => 5, 'var2' => 'foo', ))"
-
转存, 输出的语句调用了__set_state($an_array)方法, 并将$a的两个属性作为元素, 放到了$an_array数组中, 并按照魔术方法, 对临时变量$obj对象的属性进行了赋值;
-
执行, 通过eval('$b = ' . var_export($a, true) . ';');执行var_export()输出的字符串, 并进行赋值$b, 所以最后$b的数据结构是对象类型;
如果没有eval()去执行的话, 直接改为$b=var_export($a, true), 那么$b会是字符串类型, 大家可以在本地验证一下.
最后注意在声明这个魔术方法的时候, 一定要加static关键词, 因为它是静态的.
__debugInfo()
这个魔术方法的作用是: 当调用var_dump()输出一个对象时, 这个方法会被调用来显示应该被展示的属性. 他的返回类型是数组.
__debugInfo(): array
当然, 你可以通过这个方法, 返回任意内容的数组. 我们来看个例子:
class C {
private $prop;
public function __construct($val)
{
$this->prop = $val;
}
public function __debugInfo() {
return ['result'=>$this->prop*2];
}
}
var_dump(new C(42)); //object(C)#6 (1) { ["result"]=>int(84) }
通过这个魔术方法, 阻断了var_dump打印所有属性, 只输出了指定的属性, 并对属性值进行了修改.
构造与析构函数
__construct(), __destruct()
对, 我们在本系列之前专门用一章介绍过这两个函数, 他们会在创建对象时自动调用, 为对象属性初始化进行初始化赋值, 因为其是创建对象时第一个运行的方法, 还可以实现一些其他功能, 比如打印欢迎语.
而析构函数, 是当对象的所有引用都被删除, 或者程序已经运行完毕后, 自动运行, 清理对象释放内存.
详细的介绍可以看PHP类的秘密(五) 构造函数详解, 在这里就不再过多介绍了.
几个不太建议使用的魔术方法
属性重写
_`_set(string $name, mixed $value) 在给不可访问属性赋值时,__set() 会被调用。
__get(string $name) 读取不可访问属性的值时,__get() 会被调用。
__isset(string $name), 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
__unset(string $name), 当对不可访问属性调用 unset() 时,__unset() 会被调用。`
方法重写
`__call(string $name, array $arguments) 在对象中调用一个不可访问方法时,__call() 会被调用。
static __callStatic(string $name, array $arguments) 在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。`
为什么说这几个魔术方法不太建议使用呢? 因为这几个方法会严重影响代码的预期结果, 并且会导致花更多的时间去debug, 并且在结对编程, 代码集成的时候, 发生难以预料的bug和问题.
举个例子:
class Student
{
private $a;
private $b=5;
protected $d=10;
function __get($name)
{
return 123;
}
function __set($name, $value) {}
function __call($n, $m){}
private function veryPrivateMethod(){}
}
$s=new Student;
$s->a=10 //从类外访问私有属性
var_dump($s->a) //int(123), 调用__get($name);
$s->veryPrivateMethod() //类外调用私有成员方法, 调用__call($n, $m), 无报错信息
可以看到, 上例中通过这些魔术方法, 将PHP类的规范完全颠覆了, 几乎可以为所欲为. 系统失去了判断能力, 自废了武功. 而且输出的结果会让人非常迷惑. 在这里强烈建议大家不要使用这些魔术方法.
感谢阅读, 如有不准确和错误的地方请留言指正, 我会及时修正, 拜谢!
总结不易, 请勿私自转载, 否则别怪老大爷不客气!
欢迎各位爱研究的小伙伴儿和我交流, 互相学习, 一起成长!
参考资料:
WWW.PHP.NET PHP官方文档
程序员们之间常说的钩子“hook”是啥意思?
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!