在PHP中使用动态变量实例化对象的含义

时间:2021-05-10 17:06:44

What are the performance, security, or "other" implications of using the following form to declare a new class instance in PHP

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

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

This is a contrived example, but I've seen this form used in Factories (OOP) to avoid having a big if/switch statement.

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

Problems that come immediately to mind are

立刻想到的问题是

  1. You lose the ability to pass arguments into a constructor (LIES. Thanks Jeremy)
  2. 你失去了将参数传递给构造函数的能力(LIES。感谢Jeremy)

  3. Smells like eval(), with all the security concerns it brings to the table (but not necessarily the performance concerns?)
  4. 闻起来像eval(),它带来了所有安全问题(但不一定是性能问题?)

What other implications are there, or what search engine terms other than "Rank PHP Hackery" can someone use to research this?

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

10 个解决方案

#1


8  

One of the issues with the resolving at run time is that you make it really hard for the opcode caches (like APC). Still, for now, doing something like you describe in your question is a valid way if you need a certain amount of indirection when instanciating stuff.

在运行时解决问题的一个问题是你使操作码缓存(如APC)变得非常困难。但是,就目前而言,如果在实例化时需要一定量的间接,那么在你的问题中做一些像你描述的那样是一种有效的方法。

As long as you don't do something like

只要你不做那样的事情

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

you are probably fine :-)

你可能没事:-)

(my point being: Dynamically looking up a class here and then doesn't hurt. If you do it often, it will).

(我的观点是:在这里动态地查找一个班级然后不会受到伤害。如果你经常这样做,它会)。

Also, be sure that $classname can never be set from the outside - you'd want to have some control over what exact class you will be instantiating.

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

#2


8  

It looks you can still pass arguments to the constructor, here's my test code:

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

<?php

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

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

?>

That is what you meant, right?

这就是你的意思,对吗?

So the only other problem you mentioned and that I can think of is the security of it, but it shouldn't be too difficult to make it secure, and it's obviously a lot more secure than using eval().

所以你提到的唯一另一个我能想到的问题是它的安全性,但要保证它的安全性并不是太难,而且它显然比使用eval()更安全。

#3


5  

I would add that you can also instanciate it with a dynamic number of parameters using :

我想补充一点,您还可以使用以下动态数量的参数实现它:

<?php

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

?>

But of course you add some more overhead by doing this.

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

About the security issue I don't think it matters much, at least it's nothing compared to eval(). In the worst case the wrong class gets instanciated, of course this is a potential security breach but much harder to exploit, and it's easy to filter using an array of allowed classes, if you really need user input to define the class name.

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

#4


4  

There may indeed be a performance hit for having to resolve the name of the variable before looking up the class definition. But, without declaring classes dynamically you have no real way to do "dyanmic" or "meta" programming. You would not be able to write code generation programs or anything like a domain-specific language construct.

在查找类定义之前,必须解决变量名称可能确实存在性能损失。但是,如果没有动态声明类,你就没有真正的方法去做“dyanmic”或“meta”编程。您将无法编写代码生成程序或类似特定于域的语言构造。

We use this convention all over the place in some of the core classes of our internal framework to make the URL to controller mappings work. I have also seen it in many commercial open source applications (I'll try and dig for an example and post it). Anyway, the point of my answer is that it seems well worth what is probably a slight performance decrease if it makes more flexible, dynamic code.

我们在内部框架的一些核心类中使用此约定来使控制器映射的URL工作。我也在许多商业开源应用程序中看到过它(我会尝试挖掘一个例子并发布它)。无论如何,我的回答是,如果它产生更灵活,动态的代码,那么看起来很可能是一个轻微的性能下降。

The other trade-off that I should mention, though, is that performance aside, it does make the code slightly less obvious and readable unless you are very careful with your variable names. Most code is written once, and re-read and modified many times, so readability is important.

不过,我应该提到的另一个权衡是,除了你对变量名称非常小心之外,它确实会使代码稍微不那么明显和可读。大多数代码只编写一次,并重新读取和修改多次,因此可读性非常重要。

#5


2  

Alan, there's nothing wrong with dynamic class initialisation. This technique is present also in Java language, where one can convert string to class using Class.forClass('classname') method. It is also quite handy to defer algorithm complexity to several classes instead of having list of if-conditions. Dynamic class names are especially well suited in situations where you want your code to remain open for extension without the need for modifications.

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

I myself often use different classes in conjunction with database tables. In one column I keep class name that will be used to handle the record. This gives me great power of adding new types of records and handle them in unique way without changing a single byte in existing code.

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

You shouldn't be concerned about the performance. It has almost no overhead and objects themselves are super fast in PHP. If you need to spawn thousands of identical objects, use Flyweight design pattern to reduce memory footprint. Especially, you should not sacrifice your time as a developer just to save milliseconds on server. Also op-code optimisers work seamlessly with this technique. Scripts compiled with Zend Optimizer did not misbehave.

你不应该担心性能。它几乎没有开销,对象本身在PHP中超级快。如果需要生成数千个相同的对象,请使用Flyweight设计模式来减少内存占用。特别是,您不应该牺牲自己作为开发人员的时间来节省服务器上的毫秒数。操作码优化器也可以与此技术无缝协作。使用Zend Optimizer编译的脚本并没有出错。

#6


1  

So I've recently encountered this, and wanted to give my thoughts on the "other" implications of using dynamic instantiation.

所以我最近遇到过这个问题,并想对使用动态实例化的“其他”含义进行思考。

For one thing func_get_args() throws a bit of a wrench into things. For example I want to create a method that acts as a constructor for a specific class (e.g. a factory method). I'd need to be able to pass along the params passed to my factory method to the constructor of the class I'm instantiating.

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

If you do:

如果你这样做:

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

and then call:

然后打电话:

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

You're actually passing an array as the first/only param, which is the same as new SomeClass( array( 'foo', 'bar' ) ) This is obviously not what we want.

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

The solution (as noted by @Seldaek) requires us to convert the array into params of a constructor:

解决方案(由@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;
}

Note: This could not be accomplished using call_user_func_array, because you can't use this approach to instantiate new objects.

注意:使用call_user_func_array无法完成此操作,因为您无法使用此方法实例化新对象。

HTH!

#7


0  

I use dynamic instantiation in my custom framework. My application controller needs to instantiate a sub-controller based on the request, and it would be simply ridiculous to use a gigantic, ever-changing switch statement to manage the loading of those controllers. As a result, I can add controller after controller to my application without having to modify the app controller to call them. As long as my URIs adhere to the conventions of my framework, the app controller can use them without having to know anything until runtime.

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

I'm using this framework in a production shopping cart application right now, and the performance is quite favorable, too. That being said, I'm only using the dynamic class selection in one or two spots in the whole app. I wonder in what circumstances you would need to use it frequently, and whether or not those situations are ones that are suffering from a programmer's desire to over-abstract the application (I've been guilty of this before).

我现在正在生产购物车应用程序中使用此框架,性能也非常优惠。话虽这么说,我只在整个应用程序中的一个或两个位置使用动态类选择。我想知道在什么情况下你需要经常使用它,以及这些情况是否是程序员想要过度抽象应用程序的问题(我之前已经犯了这个错误)。

#8


0  

One problem is that you can't address static members like that, for instance

例如,一个问题是您不能解决这样的静态成员问题

<?php
$className = 'ClassName';

$className::someStaticMethod(); //doesn't work
?>

#9


0  

@coldFlame: IIRC you can use call_user_func(array($className, 'someStaticMethod') and call_user_func_array() to pass params

@coldFlame:IIRC你可以使用call_user_func(array($ className,'someStaticMethod')和call_user_func_array()来传递params

#10


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';

this trick works in both php4 and php5 :D enjoy..

这个技巧适用于php4和php5:D享受..

#1


8  

One of the issues with the resolving at run time is that you make it really hard for the opcode caches (like APC). Still, for now, doing something like you describe in your question is a valid way if you need a certain amount of indirection when instanciating stuff.

在运行时解决问题的一个问题是你使操作码缓存(如APC)变得非常困难。但是,就目前而言,如果在实例化时需要一定量的间接,那么在你的问题中做一些像你描述的那样是一种有效的方法。

As long as you don't do something like

只要你不做那样的事情

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

you are probably fine :-)

你可能没事:-)

(my point being: Dynamically looking up a class here and then doesn't hurt. If you do it often, it will).

(我的观点是:在这里动态地查找一个班级然后不会受到伤害。如果你经常这样做,它会)。

Also, be sure that $classname can never be set from the outside - you'd want to have some control over what exact class you will be instantiating.

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

#2


8  

It looks you can still pass arguments to the constructor, here's my test code:

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

<?php

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

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

?>

That is what you meant, right?

这就是你的意思,对吗?

So the only other problem you mentioned and that I can think of is the security of it, but it shouldn't be too difficult to make it secure, and it's obviously a lot more secure than using eval().

所以你提到的唯一另一个我能想到的问题是它的安全性,但要保证它的安全性并不是太难,而且它显然比使用eval()更安全。

#3


5  

I would add that you can also instanciate it with a dynamic number of parameters using :

我想补充一点,您还可以使用以下动态数量的参数实现它:

<?php

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

?>

But of course you add some more overhead by doing this.

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

About the security issue I don't think it matters much, at least it's nothing compared to eval(). In the worst case the wrong class gets instanciated, of course this is a potential security breach but much harder to exploit, and it's easy to filter using an array of allowed classes, if you really need user input to define the class name.

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

#4


4  

There may indeed be a performance hit for having to resolve the name of the variable before looking up the class definition. But, without declaring classes dynamically you have no real way to do "dyanmic" or "meta" programming. You would not be able to write code generation programs or anything like a domain-specific language construct.

在查找类定义之前,必须解决变量名称可能确实存在性能损失。但是,如果没有动态声明类,你就没有真正的方法去做“dyanmic”或“meta”编程。您将无法编写代码生成程序或类似特定于域的语言构造。

We use this convention all over the place in some of the core classes of our internal framework to make the URL to controller mappings work. I have also seen it in many commercial open source applications (I'll try and dig for an example and post it). Anyway, the point of my answer is that it seems well worth what is probably a slight performance decrease if it makes more flexible, dynamic code.

我们在内部框架的一些核心类中使用此约定来使控制器映射的URL工作。我也在许多商业开源应用程序中看到过它(我会尝试挖掘一个例子并发布它)。无论如何,我的回答是,如果它产生更灵活,动态的代码,那么看起来很可能是一个轻微的性能下降。

The other trade-off that I should mention, though, is that performance aside, it does make the code slightly less obvious and readable unless you are very careful with your variable names. Most code is written once, and re-read and modified many times, so readability is important.

不过,我应该提到的另一个权衡是,除了你对变量名称非常小心之外,它确实会使代码稍微不那么明显和可读。大多数代码只编写一次,并重新读取和修改多次,因此可读性非常重要。

#5


2  

Alan, there's nothing wrong with dynamic class initialisation. This technique is present also in Java language, where one can convert string to class using Class.forClass('classname') method. It is also quite handy to defer algorithm complexity to several classes instead of having list of if-conditions. Dynamic class names are especially well suited in situations where you want your code to remain open for extension without the need for modifications.

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

I myself often use different classes in conjunction with database tables. In one column I keep class name that will be used to handle the record. This gives me great power of adding new types of records and handle them in unique way without changing a single byte in existing code.

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

You shouldn't be concerned about the performance. It has almost no overhead and objects themselves are super fast in PHP. If you need to spawn thousands of identical objects, use Flyweight design pattern to reduce memory footprint. Especially, you should not sacrifice your time as a developer just to save milliseconds on server. Also op-code optimisers work seamlessly with this technique. Scripts compiled with Zend Optimizer did not misbehave.

你不应该担心性能。它几乎没有开销,对象本身在PHP中超级快。如果需要生成数千个相同的对象,请使用Flyweight设计模式来减少内存占用。特别是,您不应该牺牲自己作为开发人员的时间来节省服务器上的毫秒数。操作码优化器也可以与此技术无缝协作。使用Zend Optimizer编译的脚本并没有出错。

#6


1  

So I've recently encountered this, and wanted to give my thoughts on the "other" implications of using dynamic instantiation.

所以我最近遇到过这个问题,并想对使用动态实例化的“其他”含义进行思考。

For one thing func_get_args() throws a bit of a wrench into things. For example I want to create a method that acts as a constructor for a specific class (e.g. a factory method). I'd need to be able to pass along the params passed to my factory method to the constructor of the class I'm instantiating.

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

If you do:

如果你这样做:

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

and then call:

然后打电话:

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

You're actually passing an array as the first/only param, which is the same as new SomeClass( array( 'foo', 'bar' ) ) This is obviously not what we want.

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

The solution (as noted by @Seldaek) requires us to convert the array into params of a constructor:

解决方案(由@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;
}

Note: This could not be accomplished using call_user_func_array, because you can't use this approach to instantiate new objects.

注意:使用call_user_func_array无法完成此操作,因为您无法使用此方法实例化新对象。

HTH!

#7


0  

I use dynamic instantiation in my custom framework. My application controller needs to instantiate a sub-controller based on the request, and it would be simply ridiculous to use a gigantic, ever-changing switch statement to manage the loading of those controllers. As a result, I can add controller after controller to my application without having to modify the app controller to call them. As long as my URIs adhere to the conventions of my framework, the app controller can use them without having to know anything until runtime.

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

I'm using this framework in a production shopping cart application right now, and the performance is quite favorable, too. That being said, I'm only using the dynamic class selection in one or two spots in the whole app. I wonder in what circumstances you would need to use it frequently, and whether or not those situations are ones that are suffering from a programmer's desire to over-abstract the application (I've been guilty of this before).

我现在正在生产购物车应用程序中使用此框架,性能也非常优惠。话虽这么说,我只在整个应用程序中的一个或两个位置使用动态类选择。我想知道在什么情况下你需要经常使用它,以及这些情况是否是程序员想要过度抽象应用程序的问题(我之前已经犯了这个错误)。

#8


0  

One problem is that you can't address static members like that, for instance

例如,一个问题是您不能解决这样的静态成员问题

<?php
$className = 'ClassName';

$className::someStaticMethod(); //doesn't work
?>

#9


0  

@coldFlame: IIRC you can use call_user_func(array($className, 'someStaticMethod') and call_user_func_array() to pass params

@coldFlame:IIRC你可以使用call_user_func(array($ className,'someStaticMethod')和call_user_func_array()来传递params

#10


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';

this trick works in both php4 and php5 :D enjoy..

这个技巧适用于php4和php5:D享受..