Debo reconocer que hasta ahora no me había pasado tener la necesidad inevitable y «real» de crear clases «on the fly» (clases al vuelo) hasta que el objeto colector de Europio Engine me enfrentó a ello.
Les explico el problema para que puedan entender cuál fue la motivación para crear este tipo de clases:
Europio Engine, cuenta con objetos genéricos necesarios en cualquier aplicación. Entre ellos, CollectorObject
: un objeto colector.
Como explico en el capítulo XVII de mi libro sobre Teoría de Objetos, el objeto colector para un tipo dado, debe ser un Singleton, es decir un objeto de instancia única, pues solo podrá existir una -y solo una- colección para un mismo objeto.
En Europio Engine CollectorObject
es un colector genérico. Llamando a CollectorObject::get('NombreDelObjeto')
se puede obtener toda la colección de objetos NombreDelObjeto
persistentes en el sistema a través de CollectorObject::$collection
.
Sin embargo, al ser un objeto de instancia única, si la colección no era recuperada ANTES de solicitar una nueva colección, la última llamada sobrescribía a la primera.
Es entonces que no solo a nivel práctico se hacía necesario poder crear «on the fly» un NombreDelObjetoCollection
sino además, ¡a nivel conceptual! Pues un colector genérico, no puede ser de instancia única. Pero, si dejaba de ser de instancia única, dejaba de ser un colector. Así nació la necesidad de crear una clase al vuelo y después de mucho investigar, descubrí que la única forma de hacerlo «sin rodeos ni códigos raros», era utilizando eval()
.
Con una simple línea como esta que sigue, quedaba todo resuelto:
# ...
$nombre_del_objeto = "Producto";
$nombre_de_la_nueva_clase = "{$nombre_del_objeto}Collection";
eval("class $nombre_de_la_nueva_clase extends CollectorObject {};");
# ...
Lo anterior, creará al vuelo la clase ProductoCollection
produciendo el siguiente código:
class ProductoCollection extends CollectorObject { }
Que al heredar de CollectorObject
generaría un Singleton Colector sin más rodeos. Y a raíz de esto, comencé a pensar en la Standard PHP Library (SPL) y su tan discutido ArrayObject
.
Para quiénes no estén del todo al tanto, ArrayObject
es una clase de PHP que permite tratar a un array como si fuese un objeto (sé que es discutible pero no viene al caso ya que solo se trata de experimentar). Para que se entienda mejor, lo mostraré directamente con el código:
# Ejemplo Nº1: iniciar un array-objeto vacío
$persona = new ArrayObject(array(), 2); # array-objeto inicializado
$persona->nombre = "Eugenia"; # Agrego la "propiedad" nombre al array-objeto
$persona->apellido = "Bahit";
$persona->edad = 34;
print_r($persona);
Lo anterior producirá:
ArrayObject Object
(
[storage:ArrayObject:private] => Array
(
[nombre] => Eugenia
[apellido] => Bahit
[edad] => 34
)
)
Otro ejemplo, pero esta vez, creando un objeto desde un array asociativo:
# Ejemplo Nº2: iniciar un array-objeto partiendo de un aray asociativo existente
$datos = array(
"nombre" => "Eugenia",
"apellido" => "Bahit",
"edad" => 34
);
$persona = new ArrayObject($datos, 2);
print $persona->nombre; # Salida: Eugenia
Como pueden ver, la clase ArrayObject
no es para nada compleja de entender e incorporándola a eval()
, me podría dar la posibilidad de crear clases al vuelo y prácticamente anónimas, produciendo objetos de diversos tipos. ¿Cómo? Creando una simple clase «estática» que se encargue de todo:
class Object {
public static function set($clsName, $array=array()) {
if(!class_exists($clsName)) {
eval("
class $clsName extends ArrayObject {
public function __construct($array=array()) {
parent::__construct($array, 2);
}
}
;");
}
return new $clsName($array);
}
}
De esta forma, crear una clase al vuelo, podría hacerse de dos formas:
# Omitiendo crear la instancia al nuevo (y por qué no, anónimo) objeto
$persona = Object::get('Persona');
# Un objeto Persona ya se encuentra disponible
print_r($persona);
/*
Persona Object
(
[storage:ArrayObject:private] => Array
(
)
)
*/
# Creando la instancia al nuevo objeto para que la cosa no parezca tan anónima
Object::set('Persona'); # Creo la clase al vuelo
$persona = new Persona(); # Creo una instancia del nuevo objeto Persona que antes no existía
print_r($persona);
/*
Persona Object
(
[storage:ArrayObject:private] => Array
(
)
)
*/
Y por supuesto, vale aclarar que en cualquiera de los dos casos, tanto la llamada a Object::set('ClassName');
como la instancia al nuevo objeto $obj = new ClassName();
podrán recibir como parámetro, un array asociativo que emule las propiedades del nuevo objeto.
¡Espero que disfruten del experimento!