12

使用以下形式在 PHP 中声明新类实例的性能、安全性或“其他”含义是什么

<?php
  $class_name = 'SomeClassName';
  $object = new $class_name;
?>

这是一个人为的例子,但我已经看到在工厂 (OOP) 中使用这种形式来避免使用大的 if/switch 语句。

立即想到的问题是

  1. 你失去了将参数传递给构造函数的能力(谎言。谢谢杰里米)
  2. 闻起来像 eval(),它带来了所有的安全问题(但不一定是性能问题?)

还有什么其他含义,或者有人可以使用“Rank PHP Hackery”以外的搜索引擎术语来研究这个?

4

10 回答 10

8

看起来您仍然可以将参数传递给构造函数,这是我的测试代码:

<?php

class Test {
    function __construct($x) {
        echo $x;
    }
}

$class = 'Test';
$object = new $class('test'); // echoes "test"

?>

这就是你的意思,对吧?

所以你提到的唯一另一个我能想到的问题是它的安全性,但让它变得安全应该不会太难,而且它显然比使用 eval() 安全得多。

于 2008-09-07T02:01:32.037 回答
8

在运行时解决的问题之一是您使操作码缓存(如 APC)变得非常困难。不过,就目前而言,如果您在实例化内容时需要一定量的间接性,那么做您在问题中描述的事情是一种有效的方式。

只要你不做类似的事情

$classname = 'SomeClassName';
for ($x = 0; $x < 100000; $x++){
  $object = new $classname;
}

你可能很好:-)

(我的意思是:在这里动态查找课程,然后不会受到伤害。如果您经常这样做,它会的)。

此外,请确保永远不能从外部设置 $classname - 您希望对要实例化的确切类有一些控制。

于 2008-09-07T20:10:44.163 回答
5

我要补充一点,您还可以使用动态数量的参数来实例化它:

<?php

$class = "Test";
$args = array('a', 'b');
$ref = new ReflectionClass($class);
$instance = $ref->newInstanceArgs($args);

?>

但是当然,这样做会增加一些开销。

关于安全问题,我认为这并不重要,至少与 eval() 相比没什么。在最坏的情况下,错误的类会被实例化,这当然是一个潜在的安全漏洞,但更难利用,如果你真的需要用户输入来定义类名,使用允许的类数组很容易过滤。

于 2008-09-15T12:26:37.537 回答
4

在查找类定义之前必须解析变量的名称可能确实会影响性能。但是,如果不动态声明类,您就没有真正的方法来进行“动态”或“元”编程。您将无法编写代码生成程序或类似领域特定语言结构的任何东西。

我们在内部框架的一些核心类中到处使用这个约定,以使 URL 到控制器映射工作。我还在许多商业开源应用程序中看到了它(我将尝试挖掘一个示例并发布它)。无论如何,我的回答的重点是,如果它使代码更灵活、更动态,那么可能稍微降低性能似乎是值得的。

不过,我应该提到的另一个权衡是除了性能之外,它确实使代码不太明显和可读性,除非您对变量名非常小心。大多数代码都是一次编写的,并且可以多次重新阅读和修改,因此可读性很重要。

于 2008-09-07T02:55:38.223 回答
2

艾伦,动态类初始化没有错。这种技术也出现在 Java 语言中,可以使用Class.forClass('classname')方法将字符串转换为类。将算法复杂性推迟到几个类而不是 if 条件列表也很方便。动态类名特别适用于您希望代码保持对扩展开放而无需修改的情况。

我自己经常将不同的类与数据库表结合使用。在一列中,我保留将用于处理记录的类名。这使我能够添加新类型的记录并以独特的方式处理它们,而无需更改现有代码中的单个字节。

你不应该担心性能。它几乎没有开销,而且对象本身在 PHP 中的速度非常快。如果您需要生成数千个相同的对象,请使用享元设计模式来减少内存占用。特别是,您不应该为了节省服务器上的毫秒数而牺牲作为开发人员的时间。操作码优化器也可以无缝地使用这种技术。使用 Zend Optimizer 编译的脚本没有异常行为。

于 2008-09-08T12:39:21.620 回答
1

所以我最近遇到了这个问题,并想就使用动态实例化的“其他”含义发表我的想法。

一方面func_get_args(),这会给事情带来一些麻烦。例如,我想创建一个方法来充当特定类的构造函数(例如工厂方法)。我需要能够将传递给我的工厂方法的参数传递给我正在实例化的类的构造函数。

如果你这样做:

public function myFactoryMethod() 
{
  $class = 'SomeClass'; // e.g. you'd get this from a switch statement
  $obj = new $class( func_get_args() );
  return $obj;
}

然后调用:

$factory->myFactoryMethod('foo','bar');

您实际上是在传递一个数组作为第一个/唯一的参数,这与new SomeClass( array( 'foo', 'bar' ) ) 这显然不是我们想要的相同。

解决方案(如@Seldaek 所述)要求我们将数组转换为构造函数的参数:

public function myFactoryMethod() 
{
  $class = 'SomeClass'; // e.g. you'd get this from a switch statement
  $ref = new ReflectionClass( $class );
  $obj = $ref->newInstanceArgs( func_get_args() );
  return $obj;
}

注意:这不能使用 来完成call_user_func_array,因为您不能使用这种方法来实例化新对象。

于 2013-01-07T20:14:01.800 回答
0

我在我的自定义框架中使用动态实例化。我的应用程序控制器需要根据请求实例化一个子控制器,而使用巨大的、不断变化的 switch 语句来管理这些控制器的加载简直是荒谬的。因此,我可以将控制器一个接一个地添加到我的应用程序中,而无需修改应用程序控制器来调用它们。只要我的 URI 遵循我的框架的约定,应用程序控制器就可以使用它们,而无需在运行时之前知道任何事情。

我现在在生产购物车应用程序中使用这个框架,性能也相当不错。话虽如此,我只在整个应用程序的一两个位置使用动态类选择。我想知道在什么情况下你需要经常使用它,以及这些情况是否是程序员过度抽象应用程序的愿望(我以前对此感到内疚)。

于 2008-09-07T20:17:52.260 回答
0

一个问题是你不能像这样处理静态成员,例如

<?php
$className = 'ClassName';

$className::someStaticMethod(); //doesn't work
?>
于 2008-09-15T12:31:45.683 回答
0

@coldFlame:您可以使用 IIRCcall_user_func(array($className, 'someStaticMethod')call_user_func_array()传递参数

于 2008-09-15T14:32:35.360 回答
0
class Test {
    function testExt() {
    print 'hello from testExt :P';
    }
    function test2Ext()
    {
    print 'hi from test2Ext :)';
    }
}


$class = 'Test';
$method_1 = "testExt";
$method_2 = "test2Ext";
$object = new $class(); // echoes "test"
$object->{$method_2}(); // will print 'hi from test2Ext :)'
$object->{$method_1}(); // will print 'hello from testExt :P';

这个技巧在 php4 和 php5 中都有效:D 享受..

于 2009-01-20T14:40:19.483 回答