PHP类的秘密(七) 下钩子hooks与魔术方法

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

下钩子hooks与魔术方法

在PHP类的定义中, 还有一种特殊的成员方法, 他们有特殊的触发方式, 以实现一些特殊的应用, 他们以双下划线开头”__”, 称为魔术方法, 我们今天就来看看魔术方法以及他们是如何下钩子的.

钩子函数

所谓钩子函数, 是在正常的程序执行过程中, 插入一个钩点(hook), 然后通过这个钩点, 先去执行钩子函数, 之后再回来执行主程序, 或者干脆不执行主程序. 简单地讲,就是“要想从这过,留下买路财”.

image.png

在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()

这个魔术方法可以让一个对象的属性赋给另一个对象, 不过使用起来稍微有些复杂:

  1. 输出, 首先当调用 var_export() 导出对象$a时,此静态方法会被调用. 比如var_export($a, true)返回值是一个字符串, 是关于变量$a的结构信息.

  2. 转存, 然后__set_state(array $properties)会将$a的属性自动存放到$properties这个数组当中, 属性名=>属性值的格式, 但此时, 整个函数的表达式仍然是字符串格式, 是无法执行的.

  3. 执行, 将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"}
  
复制代码

我们来一步步分析一下:

  1. 输出, var_export($a, true), 会返回以下字符串:

     string(60) "A::__set_state(array(
    'var1' => 5,
    'var2' => 'foo',
     ))"
    复制代码
  2. 转存, 输出的语句调用了__set_state($an_array)方法, 并将$a的两个属性作为元素, 放到了$an_array数组中, 并按照魔术方法, 对临时变量$obj对象的属性进行了赋值;

  3. 执行, 通过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”是啥意思?

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享