2008-09-07 12 views
10

¿Cuáles son las prestaciones, la seguridad, o "otras" consecuencias de utilizar el siguiente formulario para declarar una nueva instancia de la clase en PHPImplicaciones de instanciar objetos con variables dinámicas en PHP

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

Este es un ejemplo artificial, pero he visto esta forma utilizada en Factories (OOP) para evitar tener una declaración if/switch grande.

problemas que vienen inmediatamente a la mente son

  1. Se pierde la capacidad para pasar argumentos a un constructor (mentiras. Gracias Jeremy)
  2. Huele a eval(), con todos los problemas de seguridad que trae a la mesa (pero no necesariamente las preocupaciones de rendimiento?)

¿Qué otras implicaciones existen, o qué términos de motor de búsqueda aparte de "Rank PHP Hackery" alguien puede utilizar para investigar esto?

Respuesta

8

Uno de los problemas con la resolución en tiempo de ejecución es que hace que sea muy difícil para los cachés de código de operación (como APC). Aún así, por ahora, hacer algo como lo describe en su pregunta es una manera válida si necesita cierta cantidad de indirección al instanciar cosas.

Mientras no se hace algo así como

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

que son probablemente muy bien :-)

(Mi punto es: dinámicamente mirando hacia arriba una clase aquí y luego no hace daño. Si lo haces a menudo, lo hará).

Además, asegúrese de que $ classname nunca se pueda configurar desde el exterior; querrá tener cierto control sobre la clase exacta que creará la instancia.

8

Parece que todavía puede pasar argumentos al constructor, que aquí es mi código de prueba:

<?php 

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

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

?> 

Eso es lo que quería decir, ¿verdad?

Así que el único otro problema que mencionaste y que puedo pensar es su seguridad, pero no debería ser demasiado difícil hacerlo seguro, y obviamente es mucho más seguro que usar eval().

4

Puede haber un golpe de rendimiento para tener que resolver el nombre de la variable antes de buscar la definición de la clase. Pero, sin declarar las clases dinámicamente, no tiene una forma real de hacer una programación "dianémica" o "meta". No podría escribir programas de generación de código ni nada parecido a una construcción de lenguaje específica de dominio.

Utilizamos esta convención por todas partes en algunas de las clases principales de nuestro marco interno para que funcione la URL a las asignaciones del controlador. También lo he visto en muchas aplicaciones comerciales de código abierto (intentaré buscar un ejemplo y lo publicaré). De todos modos, el punto de mi respuesta es que parece valer la pena lo que probablemente sea una pequeña disminución en el rendimiento si hace un código más flexible y dinámico.

La otra desventaja que debo mencionar, sin embargo, es ese rendimiento aparte, hace que el código sea menos obvio y legible a menos que sea muy cuidadoso con sus nombres de variable. La mayoría del código se escribe una vez, y se vuelve a leer y modificar muchas veces, por lo que la legibilidad es importante.

0

Utilizo la creación de instancias dinámicas en mi marco personalizado. El controlador de mi aplicación necesita crear una instancia de un subcontrolador basado en la solicitud, y sería simplemente ridículo usar una declaración de cambio gigantesca y en constante cambio para administrar la carga de esos controladores. Como resultado, puedo agregar controlador tras controlador a mi aplicación sin tener que modificar el controlador de la aplicación para llamarlos. Siempre que mis URI sigan las convenciones de mi framework, el controlador de la aplicación puede usarlos sin tener que saber nada hasta el tiempo de ejecución.

Estoy usando este marco en una aplicación de carrito de compras de producción en este momento, y el rendimiento es bastante favorable, también. Dicho esto, solo estoy usando la selección de clase dinámica en uno o dos puntos en toda la aplicación. Me pregunto en qué circunstancias necesitarías usarlo con frecuencia, y si esas situaciones son o no las que sufren el deseo de un programador de hacer un resumen excesivo de la aplicación (he sido culpable de esto antes).

2

Alan, no hay nada de malo con la inicialización dinámica de la clase. Esta técnica está presente también en el lenguaje Java, donde se puede convertir una cadena a clase usando el método Class.forClass('classname'). También es bastante útil diferir la complejidad del algoritmo en varias clases en lugar de tener una lista de condiciones de if. Los nombres dinámicos de clase son especialmente adecuados en situaciones donde desea que su código permanezca abierto para la extensión sin la necesidad de modificaciones.

Yo mismo a menudo uso diferentes clases junto con las tablas de la base de datos. En una columna, guardo el nombre de clase que se usará para manejar el registro. Esto me da un gran poder de agregar nuevos tipos de registros y manejarlos de manera única sin cambiar un solo byte en el código existente.

No debería preocuparse por el rendimiento. Casi no tiene sobrecarga y los objetos son muy rápidos en PHP. Si necesita generar miles de objetos idénticos, utilice el patrón de diseño Flyweight para reducir la huella de memoria. Especialmente, no debe sacrificar su tiempo como desarrollador solo para ahorrar milisegundos en el servidor. Además, los optimizadores op-code funcionan a la perfección con esta técnica. Los guiones compilados con Zend Optimizer no se portaron mal.

5

Yo añadiría que también se puede instanciar con una serie de parámetros dinámicos usando:

<?php 

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

?> 

Pero, por supuesto, añadir un poco más de riesgo al hacer esto.

Acerca del problema de seguridad No creo que importe demasiado, al menos no es nada comparado con eval(). En el peor de los casos, la clase incorrecta se instancia, por supuesto, esta es una potencial brecha de seguridad pero mucho más difícil de explotar, y es fácil filtrar usando una variedad de clases permitidas, si realmente necesita la entrada del usuario para definir el nombre de la clase.

0

Uno de los problemas es que no se puede hacer frente a los miembros estáticos como que, por ejemplo

<?php 
$className = 'ClassName'; 

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

eval ('$ result ='. $ ClassName. ':: someStaticMethod()'); var_dump ($ resultado); –

+3

call_user_func (array ($ className, 'someStaticMethod')); – bd808

0

@coldFlame: IIRC puede utilizar call_user_func(array($className, 'someStaticMethod') y call_user_func_array() pasar params

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

Este truco funciona tanto en php4 como en php5: D disfruta ...

1

Me he encontrado recientemente con esto y quería dar mi opinión sobre las "otras" implicaciones del uso de la creación de instancias dinámicas.

Por un lado, func_get_args() da un poco de una llave en las cosas. Por ejemplo, quiero crear un método que actúe como constructor para una clase específica (por ejemplo, un método de fábrica). Tendría que poder pasar los parámetros pasados ​​a mi método de fábrica al constructor de la clase que estoy instanciando.

Si lo hace:

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

y luego llama:

$ fábrica> myFactoryMethod ('parametro', 'valor');

En realidad está pasando una matriz como el primer/único parámetro, que es el mismo que new SomeClass(array('foo', 'bar')) Esto obviamente no es lo que queremos.

La solución (como se ha señalado por @Seldaek) nos obliga a convertir la matriz en params de un constructor:

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

Nota: Esto no podría lograrse utilizando call_user_func_array, porque no se puede utilizar este enfoque para crear instancias de objetos nuevos.

HTH!

Cuestiones relacionadas