使用以下表单在PHP中声明新类实例的性能,安全性或“其他”含义是什么
<?php
$class_name = 'SomeClassName';
$object = new $class_name;
?>
这是一个人为的例子,但我已经看到工厂(OOP)中使用的这种形式,以避免使用大的if / switch语句。
立刻想到的问题是
还有什么其他含义,或者“Rank PHP Hackery”之外的搜索引擎术语可以用来研究这个?
答案 0 :(得分:8)
看起来你仍然可以将参数传递给构造函数,这是我的测试代码:
<?php
class Test {
function __construct($x) {
echo $x;
}
}
$class = 'Test';
$object = new $class('test'); // echoes "test"
?>
这就是你的意思,对吧?
所以你提到的唯一另一个我能想到的问题是它的安全性,但是要保证它的安全性并不是太难,而且它显然比使用eval()更安全。
答案 1 :(得分:8)
运行时解决的一个问题是你使操作码缓存(如APC)变得非常困难。不过,就目前而言,如果你在实现内容时需要一定的间接性,那么在你的问题中做一些像你所描述的那样是一种有效的方式。
只要你不做像
这样的事情$classname = 'SomeClassName';
for ($x = 0; $x < 100000; $x++){
$object = new $classname;
}
你可能没事: - )
(我的观点是:在这里动态地查找一个班级,然后不会受到伤害。如果你经常这样做,它会。)
另外,请确保永远不能从外部设置$ classname - 您希望能够控制将要实例化的确切类。
答案 2 :(得分:5)
我想补充一点,你也可以使用以下方法用动态数量的参数实现它:
<?php
$class = "Test";
$args = array('a', 'b');
$ref = new ReflectionClass($class);
$instance = $ref->newInstanceArgs($args);
?>
但是你当然会通过这样做增加一些开销。
关于安全问题我认为这不重要,至少与eval()相比没什么。在最坏的情况下,错误的类会被实例化,当然这是一个潜在的安全漏洞,但是更难以利用,如果你真的需要用户输入来定义类名,那么很容易使用允许类的数组进行过滤。
答案 3 :(得分:4)
在查找类定义之前,必须解决变量名称确实会有性能损失。但是,如果没有动态声明类,你就没有真正的方法去做“dyanmic”或“meta”编程。您将无法编写代码生成程序或类似域特定语言构造的任何内容。
我们在内部框架的一些核心类中使用此约定来使控制器映射的URL工作。我也在许多商业开源应用程序中看到它(我会尝试挖掘一个例子并发布它)。无论如何,我的答案是,如果它能够提供更灵活,更动态的代码,那么看起来很可能会有轻微的性能下降。
我应该提到的另一个权衡是,除了你对变量名称非常小心之外,它确实使代码略显不易明显。大多数代码只编写一次,并重新读取和修改多次,因此可读性非常重要。
答案 4 :(得分:2)
Alan,动态类初始化没有任何问题。这种技术也存在于Java语言中,可以使用Class.forClass('classname')
方法将字符串转换为类。将算法复杂度推迟到几个类而不是具有if条件列表也是非常方便的。动态类名特别适用于您希望代码保持打开以进行扩展而无需修改的情况。
我自己经常将不同的类与数据库表结合使用。在一列中,我保留了用于处理记录的类名。这使我有能力添加新类型的记录并以独特的方式处理它们,而无需更改现有代码中的单个字节。
你不应该担心表现。它几乎没有开销,对象本身在PHP中超级快。如果需要生成数千个相同的对象,请使用Flyweight设计模式来减少内存占用。特别是,您不应该牺牲自己作为开发人员的时间来节省服务器上的毫秒数。操作码优化器也可以与此技术无缝协作。使用Zend Optimizer编译的脚本没有行为不端。
答案 5 :(得分: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;
}
然后致电:
$出厂&GT; myFactoryMethod( '富', '酒吧');
你实际上是将一个数组作为第一个/唯一的参数传递,这与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
无法完成此操作,因为您无法使用此方法来实例化新对象。
HTH!
答案 6 :(得分:0)
我在自定义框架中使用动态实例化。我的应用程序控制器需要根据请求实例化一个子控制器,使用一个巨大的,不断变化的switch语句来管理这些控制器的加载将是非常荒谬的。因此,我可以将控制器之后的控制器添加到我的应用程序,而无需修改应用程序控制器来调用它们。只要我的URI符合我的框架的约定,app控制器就可以使用它们而不必知道任何事情,直到运行时。
我现在正在生产购物车应用程序中使用此框架,性能也非常优惠。话虽这么说,我只在整个应用程序中的一个或两个位置使用动态类选择。我想知道在什么情况下你需要经常使用它,以及这些情况是否是程序员想要过度抽象应用程序的问题(我之前已经犯了这个错误)。
答案 7 :(得分:0)
一个问题是你无法解决这类静态成员,例如
<?php
$className = 'ClassName';
$className::someStaticMethod(); //doesn't work
?>
答案 8 :(得分:0)
@coldFlame:IIRC您可以使用call_user_func(array($className, 'someStaticMethod')
和call_user_func_array()
来传递参数
答案 9 :(得分: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享受..