61

通过类名动态生成对象

 5 years ago
source link: https://www.tlanyan.me/dynamic-new-class-object/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

问题

前几天有人在PHP的QQ群里问生成对象的问题:

use A\B;
$b = new B();  // 正确
$str = "B";
$b = new $str(); // 错误,提示:类"B"未找到

类似问题五六年前碰到过,因此印象深刻。热心提示要用 “ 完全限定类名 ” 形式,可惜连说两遍,提问题的人都没理解我说的(或者认为我的回复与其问题无关):

classname1.png

不得已下,写下示范代码并 @ 提问题的人,终于让其明白:

classname2.png

原理

问题解决了,背后的原理是什么?

从人的角度看,代码意图非常明显:动态生成类 B 的实例。但从执行引擎的角度,完全是另外一回事。其实 new $classname() 背后的运作行为类似于:

// 伪代码
if (class_exists($str)) {
  $b = new $str();
  return $b;
}
throw ClassNotFoundException;

// 或者用反射
try {
  $reflectionClass = new ReflectionClass($str);
  $b = $reflectionClass.newInstance();
  return $b;
}
throw ClassNotFoundException;

要根据类名动态生成示例,首先要判断类是否存在吧?PHP中与之相关的是 class_exists 函数和 ReflectionClass 类。在上面的例子中,只传入字符串 “B”class_exists 回返回 true 吗?

答案是否定的。 class_existsReflectionClass 只会在全局类列表中根据名字查找,不会理会调用函数所在(或引入)的名字空间。同理,如果使用 use 引入类名并做别名( as ),别名类在 class_exists 中也会返回 false

那么PHP能否改进一下 class_existsReflectionClass 的行为,让其根据当前上下文判断?

可以这么做,但是代价很大,原因包括:

  1. class_existsReflectionClass 都没有指示程序上下文 Context 的参数;
  2. PHP比较坑的一点: 类名 不会像函数、常量一样往上逐级查找;
  3. 如果存在多个同名的类,加载哪个?如以下代码所示:

    carbon-code.png

    不管采取哪种行为,都会招致吐槽。

保持目前的情况,除动态生成实例时需要完全限定类名,并无其他槽点。并且实现上简单,行为明确且一致。

总结

作为一门脚本语言,PHP非常的灵活,但也会带来一些使用上的困惑。本文所讨论的根据类名动态生成对象,就要无视当前所在或引入的名字空间,必须使用完全限定类名形式。

作为对比,C++不能动态生成对象。Java要用 Class.forName 的方式获取 class 对象,然后再调用构造函数生成。Java不能直接 new 类名,避免了PHP中的坑,但 Class.forName 同样需要完全限定类名,避免不明确行为。

参考

  1. PHP回顾之反射
  2. PHP中的重载
  3. Using namespaces: fallback to global function/constant

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK