【问题标题】:Implications of Instantiating Objects with Dynamic Variables in PHP在 PHP 中使用动态变量实例化对象的含义
【发布时间】:2008-09-07 01:27:50
【问题描述】:

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

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

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

立即想到的问题是

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

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

【问题讨论】:

    标签: php performance oop


    【解决方案1】:

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

    只要你不做类似的事情

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

    你可能很好:-)

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

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

    【讨论】:

      【解决方案2】:

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

      <?php
      
      class Test {
          function __construct($x) {
              echo $x;
          }
      }
      
      $class = 'Test';
      $object = new $class('test'); // echoes "test"
      
      ?>
      

      这就是你的意思,对吧?

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

      【讨论】:

        【解决方案3】:

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

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

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

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

        【讨论】:

          【解决方案4】:

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

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

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

          【讨论】:

            【解决方案5】:

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

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

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

            【讨论】:

              【解决方案6】:

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

              一方面,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 完成,因为您不能使用这种方法来实例化新对象。

              HTH!

              【讨论】:

                【解决方案7】:

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

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

                【讨论】:

                  【解决方案8】:

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

                  <?php
                  $className = 'ClassName';
                  
                  $className::someStaticMethod(); //doesn't work
                  ?>
                  

                  【讨论】:

                  • eval('$result = ' . $className . '::someStaticMethod()'); var_dump($result);
                  • call_user_func(array($className, 'someStaticMethod'));
                  【解决方案9】:

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

                  【讨论】:

                    【解决方案10】:
                    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 enjoy..

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-12-18
                      • 1970-01-01
                      • 2012-12-21
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多