new - Implicaciones de la creación de instancias de objetos con variables dinámicas en PHP
php class constructor (10)
¿Cuáles son las implicaciones de rendimiento, seguridad o "otras" al usar el siguiente formulario para declarar una nueva instancia de clase 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 gran instrucción if / switch.
Los problemas que vienen inmediatamente a la mente son
-
Pierdes la habilidad de pasar argumentos a un constructor(MENTIRAS. Gracias Jeremy) - Huele a eval (), con todas las preocupaciones de seguridad que trae a la mesa (¿pero no necesariamente las preocupaciones sobre el rendimiento?)
¿Qué otras implicaciones existen, o qué términos de los motores de búsqueda aparte de "Rank PHP Hackery" alguien puede usar para investigar esto?
De hecho, puede haber un golpe de rendimiento por 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.
Usamos esta convención por todas partes en algunas de las clases centrales de nuestro marco interno para que funcionen las correlaciones URL a controladores. 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 a un lado, hace que el código sea menos obvio y legible a menos que sea muy cuidadoso con sus nombres de variables. 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.
Parece que todavía se pueden pasar argumentos al constructor, aquí está 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ías decir, ¿verdad?
Entonces, el único otro problema que mencionas 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 ().
Uno de los problemas con la resolución en tiempo de ejecución es que lo hace realmente 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 hagas algo como
$classname = ''SomeClassName'';
for ($x = 0; $x < 100000; $x++){
$object = new $classname;
}
probablemente estés bien :-)
(Mi punto es: busca dinámicamente una clase aquí y luego no duele. Si lo haces a menudo, lo hará).
Además, asegúrate de que $ classname nunca se pueda establecer desde afuera; querrás tener cierto control sobre la clase exacta que crearás.
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 utilizando 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).
Alan, no hay nada de malo con la inicialización dinámica de clases. Esta técnica también está presente en el lenguaje Java, donde uno puede convertir una cadena en una clase usando el método Class.forClass(''classname'')
. También es bastante útil diferir la complejidad del algoritmo para varias clases en lugar de tener una lista de condiciones de if. Los nombres de clase dinámicos son especialmente adecuados en situaciones en las que desea que su código permanezca abierto para la extensión sin 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, use 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.
@coldFlame: IIRC puede usar call_user_func(array($className, ''someStaticMethod'')
y call_user_func_array()
para pasar los parámetros
Un problema es que no puede abordar miembros estáticos así, por ejemplo
<?php
$className = ''ClassName'';
$className::someStaticMethod(); //doesn''t work
?>
Yo agregaría que también puedes instanciarlo con un número dinámico de parámetros usando:
<?php
$class = "Test";
$args = array(''a'', ''b'');
$ref = new ReflectionClass($class);
$instance = $ref->newInstanceArgs($args);
?>
Pero, por supuesto, agrega un poco más de sobrecarga al hacer esto.
Sobre el problema de seguridad, no creo que importe demasiado, al menos no es nada en comparación 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.
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 ...
Así que recientemente me encontré con esto y quise dar mi opinión sobre las "otras" implicaciones del uso de la creación de instancias dinámicas.
Por un lado, func_get_args()
arroja 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 haces:
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:
$ factory-> myFactoryMethod (''foo'', ''bar'');
En realidad, está pasando una matriz como el primer / único parámetro, que es el mismo que el new SomeClass( array( ''foo'', ''bar'' ) )
Esto obviamente no es lo que queremos.
La solución (como lo señala @Seldaek) requiere que conviertamos 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 se pudo lograr usando call_user_func_array
, porque no puede usar este enfoque para crear instancias de objetos nuevos.
HTH!