PHP如何让工厂函数每次调用返回全新独立的匿名类
问题
尝试通过工厂函数实例化匿名类时遇到问题:每次调用函数返回的都是同一个类,使用静态变量时会出现数据互相覆盖的异常。
PHP官方文档明确说明:
同一匿名类声明创建的所有对象,都属于这个完全相同的类。
这意味着如下代码运行后,永远会判定两次调用anonymous_class()返回的对象属于同一个类:
function anonymous_class() { return new class {}; } if (get_class(anonymous_class()) === get_class(anonymous_class())) echo 'same class';
需求是找到可行方案,让同一个工厂函数每次调用都能生成全新的独立类,从而实现预期业务逻辑,避免静态变量被覆盖。
当前编写的factory函数代码如下,两次传入不同参数调用后,两次调用getStaticVar()都输出最后一次赋值的"Hello Nurse",不符合预期:
function factory(string $param) { return new class($param) { public string $param; public static Closure $getter; public function __construct(string $param) { $this->param = $param; static::$getter = function () { return $this->param; }; } public static function getStaticVar(): string { $getter = static::$getter; return $getter(); } }; } $world = factory("Hello World"); $nurse = factory("Hello Nurse"); echo $world::getStaticVar(); //Output: Hello Nurse echo $nurse::getStaticVar(); //Output: Hello Nurse
如果不通过工厂函数,手动复制两份相同的匿名类代码分别实例化,就能得到符合预期的输出,两次调用分别返回对应传入的参数值,但手动重复复制类代码的方式维护成本太高,手动实现的参考代码如下:
$world = new class("Hello World") { public string $param; public static Closure $getter; public function __construct(string $param) { $this->param = $param; static::$getter = function () { return $this->param; }; } public static function getStaticVar(): string { $getter = static::$getter; return $getter(); } }; $nurse = new class("Hello Nurse") { public string $param; public static Closure $getter; public function __construct(string $param) { $this->param = $param; static::$getter = function () { return $this->param; }; } public static function getStaticVar(): string { $getter = static::$getter; return $getter(); } }; echo $world::getStaticVar(); //Output: Hello World echo $nurse::getStaticVar(); //Output: Hello Nurse
解决方案
方案一:动态生成独立类声明(非可信输入场景禁用)
PHP中匿名类的唯一性和代码中的声明位置绑定,同一位置编写的匿名类无论调用多少次,都只会被解析为一个类。要实现每次调用生成全新独立类,只需要让类声明每次都在全新的解析上下文中执行即可,eval()可以实现这个效果:
function factory(string $param) { $safeParam = var_export($param, true); return eval(<<<CODE return new class($safeParam) { public string \$param; public static Closure \$getter; public function __construct(string \$param) { \$this->param = \$param; static::\$getter = function () { return \$this->param; }; } public static function getStaticVar(): string { \$getter = static::\$getter; return \$getter(); } }; CODE ); } $world = factory("Hello World"); $nurse = factory("Hello Nurse"); echo $world::getStaticVar(); // 输出 Hello World echo $nurse::getStaticVar(); // 输出 Hello Nurse
注意:该方案存在代码注入风险,仅当传入factory的参数完全可控、无任何用户输入内容时才可使用。
方案二:调整代码设计,规避类级别全局状态冲突(推荐)
静态属性本身是类级别的全局状态,既然需要每个实例拥有独立的访问入口,完全不需要强行依赖动态生成类,通过实例唯一标识做状态隔离即可,性能更好也无安全风险:
function factory(string $param) { return new class($param) { private string $param; private static array $instanceStorage = []; public function __construct(string $param) { $this->param = $param; self::$instanceStorage[spl_object_hash($this)] = $this->param; } // 实例调用方式 public function getVar(): string { return self::$instanceStorage[spl_object_hash($this)]; } // 兼容静态调用方式 public static function getStaticVar(object $instance): string { $key = spl_object_hash($instance); if (!isset(self::$instanceStorage[$key])) { throw new InvalidArgumentException("传入实例不属于当前类"); } return self::$instanceStorage[$key]; } // 实例销毁时自动清理存储,避免内存泄漏 public function __destruct() { unset(self::$instanceStorage[spl_object_hash($this)]); } }; } $world = factory("Hello World"); $nurse = factory("Hello Nurse"); echo $world->getVar(); // 输出 Hello World echo $nurse->getVar(); // 输出 Hello Nurse echo $world::getStaticVar($world); // 输出 Hello World echo $nurse::getStaticVar($nurse); // 输出 Hello Nurse
内容的提问来源于stack exchange,提问作者guyshitz
相关产品推荐
相关产品推荐

