在一天的工作中,我为我工作的项目上的业务逻辑编写了单元测试。 我的任务是使用特定的值初始化类的某些私有属性。
不能使用普通的二传手,因为那里拼写了一些逻辑。 继承或锁定一个类也不起作用,因为它被声明为final。 甚至反思也不合适。 因此,我开始寻找解决此问题的方法。
我找到了一篇有趣的文章 ,描述了如何使用dg / bypass-finals库锁定最终类。 我喜欢这个选项,并尝试实现它。 不幸的是,我没有成功,因为该项目使用的是旧版本的PHPUnit。
经过反思,我想起了Closure
类,尤其是它的静态bind()
方法,该方法可以在类的所需对象的上下文中实现匿名函数。 有关此的更多信息,请参见官方文档 。 因此,我创建了一个我在测试中使用的特征(也许有人也会有用)
trait PrivatePropertySetterTrait { protected function assignValue($object, string $attribute, $value) { $setter = function ($value) use ($attribute) { $this->$attribute = $value; }; $setterClosure = \Closure::bind($setter, $object, \get_class($object)); $setterClosure($value); } }
此特征采用一个类对象,要在其中设置值的属性的名称,实际上是值本身。 接下来,声明一个简单的匿名函数,该函数使用$this
指针将结果值分配给class属性。 接下来, Closure
类带有其静态bind()
方法。 该方法接受类对象,上述匿名函数以及类的全名。 因此,一个匿名函数被嵌入对象的上下文中, bind()
方法返回给我们Closure
类的对象,我们可以将其作为常规函数调用,因为它定义了魔术__invoke()
方法。 瞧!
最后,我设法解决了问题,然后想起了Singleton设计模式。 是否有可能以相同的方式实现将创建类的新对象的匿名函数? 我当然去检查了!
通过编写一小段代码
带有代码的沙盒
<?php final class Singleton { private static $instance; public static function getInstance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { } private function __clone() { } private function __wakeup() { } } $s1 = Singleton::getInstance(); \var_dump(\spl_object_id($s1)); $createNewInstance = function () { return new self(); }; $newInstanceClosure = Closure::bind($createNewInstance, $s1, Singleton::class); $s2 = $newInstanceClosure(); \var_dump(\spl_object_id($s2));
它以相同的原理工作,但不是使用class属性分配值,而是使用new
运算符创建了一个新对象。 \spl_object_id()
函数返回对象的唯一标识符。 有关此功能的更多信息,请参见文档 。 使用spl_object_id()
和var_dump()
我得出了对象的唯一标识符,发现它们是不同的! 我仍然设法证实了这一理论,并创建了Singleton类的新实例!
在本文中,我想与PHP社区分享我非常好奇的发现。
感谢您的关注!