PHP 7.0-7.3中eval函数的异常行为及解决方案求助
PHP 7.0-7.3中eval动态创建匿名类出现输出异常的问题
问题描述
以下代码在PHP 7.4及以上版本运行时输出符合预期,但在PHP 7.0至7.3版本中出现异常:多次调用相同参数的e()函数,返回的匿名类实例的test()方法返回值出现混淆,不符合传入的$n参数预期。
<?php function e($m, $n) { return eval( 'return new class() { // '. str_repeat('a', $m). ' public function test() { return "'. $n. '"; } };' ); } for ($i = 131; $i < 140; $i++) { printf("%d: %d, %d, %d, %d, %d, %d\n", $i, e($i, 1)->test(), e($i, 2)->test(), e($i, 1)->test(), e($i, 2)->test(), e($i + 1, 1)->test(), e($i + 1, 2)->test() ); }
不同版本输出对比
PHP 7.4/8.* 预期输出
131: 1, 2, 1, 2, 1, 2 132: 1, 2, 1, 2, 1, 2 133: 1, 2, 1, 2, 1, 2 134: 1, 2, 1, 2, 1, 2 135: 1, 2, 1, 2, 1, 2 136: 1, 2, 1, 2, 1, 2 137: 1, 2, 1, 2, 1, 2 138: 1, 2, 1, 2, 1, 2 139: 1, 2, 1, 2, 1, 2
PHP 7.0-7.3 异常输出
131: 1, 2, 2, 2, 1, 1 132: 1, 1, 1, 1, 1, 2 133: 1, 2, 1, 2, 1, 2 134: 1, 2, 1, 2, 1, 2 135: 1, 2, 1, 2, 1, 1 136: 1, 1, 1, 1, 1, 1 137: 1, 1, 1, 1, 1, 1 138: 1, 1, 1, 1, 1, 1 139: 1, 1, 1, 1, 1, 1
问题分析
这是PHP 7.0-7.3版本的匿名类缓存机制缺陷导致的:当通过eval()动态生成匿名类时,PHP会根据类代码内容生成哈希值作为缓存键,但早期版本中,哈希计算可能忽略注释部分的差异或出现哈希碰撞,导致不同参数生成的匿名类被错误复用之前缓存的类定义,最终出现返回值混淆。
最初推测异常与eval代码开头的相同字符数有关,但复杂场景下该推测不成立,核心问题还是早期版本对动态匿名类的缓存逻辑不完善。
解决方案
方案1:用动态命名类替代匿名类
通过生成唯一类名避免缓存冲突,确保每个参数组合对应不同的类:
<?php function e($m, $n) { // 结合参数生成唯一类名,避免重复 $className = 'DynamicClass_' . md5($m . '_' . $n); if (!class_exists($className)) { eval( 'class ' . $className . ' { public function test() { return "' . $n . '"; } }' ); } return new $className(); } for ($i = 131; $i < 140; $i++) { printf("%d: %d, %d, %d, %d, %d, %d\n", $i, e($i, 1)->test(), e($i, 2)->test(), e($i, 1)->test(), e($i, 2)->test(), e($i + 1, 1)->test(), e($i + 1, 2)->test() ); }
方案2:给匿名类添加唯一标识的属性逻辑
通过构造函数传递参数,同时让类结构包含依赖参数的唯一逻辑,确保生成的类代码内容唯一:
<?php function e($m, $n) { return eval( 'return new class(' . $n . ') { private $value; public function __construct($value) { $this->value = $value; } // '. str_repeat('a', $m). ' public function test() { return $this->value; } };' ); } for ($i = 131; $i < 140; $i++) { printf("%d: %d, %d, %d, %d, %d, %d\n", $i, e($i, 1)->test(), e($i, 2)->test(), e($i, 1)->test(), e($i, 2)->test(), e($i + 1, 1)->test(), e($i + 1, 2)->test() ); }
这种方式避免了将参数硬编码在方法返回值中,同时每个类的初始化逻辑唯一,不会触发错误的缓存复用。
方案3:升级PHP版本到7.4+
PHP官方在7.4版本中修复了匿名类的缓存逻辑问题,升级到7.4或更高版本可以彻底解决该问题。
内容的提问来源于stack exchange,提问作者Alexandar Popov
相关产品推荐
相关产品推荐

