Inyección de dependencias
1Visión general
1.1Principio
La inyección de dependencias permite desacoplar la lógica entre objetos apoyándose en el principio de inversión de control.
Temma crea automáticamente un componente (llamado “loader”) para gestionar la inyección de dependencias de los objetos de negocio.
Este componente es la columna vertebral en la que todos los objetos de tu aplicación pueden apoyarse para acceder unos a otros.
Está disponible en los controladores a través del atributo privado $_loader.
El loader puede instanciar automáticamente los objetos que se le solicitan, pero también puede contener valores (escalares u objetos) que se le proporcionan explícitamente.
El comportamiento habitual del loader es proporcionar siempre la misma instancia para un tipo de objeto dado.
Cuando se solicita un objeto al loader:
- Si el loader todavía no conoce ese objeto, lo instancia, guarda la instancia en su caché y la devuelve.
- Si el loader ya conoce ese objeto (ya sea porque se le proporcionó o porque el loader ya lo instanció), el loader devuelve la instancia.
1.2Uso
El loader puede usarse de dos formas distintas: como un service locator, o mediante autowiring.
Service locator
Este es el uso típico en los controladores. El loader se usa para solicitar los objetos necesarios. En tus controladores, usas el loader para obtener las instancias de los objetos que necesitas.
Ejemplo de uso:
// en un controlador => service locator
class MyController extends \Temma\Web\Controller {
public function __invoke() {
// usando un objeto de negocio a través del loader
$this['data'] = $this->_loader->MyObject->process();
}
}
- Línea 5: El loader se usa para obtener una instancia del objeto MyObject, sobre la cual se llama al método process(). Si el loader ya tiene esta instancia en caché, la devuelve; en caso contrario, instancia el objeto antes de devolverlo.
Autowiring
Tus objetos (excepto los controladores) reciben sus dependencias como parámetros de sus constructores. Si es necesario, el loader instanciará los objetos esperados antes de proporcionárselos al constructor.
// en un objeto de negocio => autowiring
class MyObject {
// Inyección de dependencias mediante autowiring.
// El loader instancia primero los objetos que deben pasarse al constructor.
public function __construct(
private UserDao $userDao,
private BucketService $bucketService
) {
}
// usando las dependencias en otros métodos
public function process() : array {
$users = $this->userDao->getList();
$buckets = $this->bucketService->getFromUsers($users);
return $buckets;
}
}
-
Líneas 5 a 8: El constructor del objeto espera dos parámetros, de tipo UserDao y
BucketService. Cuando el objeto MyObject es instanciado automáticamente
por el loader, este proporciona al constructor los parámetros esperados, instanciándolos primero si es necesario.
Aquí se usa la promoción de constructor para almacenar estos parámetros en propiedades privadas del objeto. - Líneas 13 y 14: Se usan las propiedades privadas.
2Datos disponibles
Por defecto, el componente contiene los siguientes elementos:
- $loader->session: Objeto de gestión de sesión (ver sesiones).
- $loader->config: Objeto de gestión de la configuración, que da acceso a todas las directivas de configuración.
- $loader->request: Objeto de gestión de la petición entrante, que permite manipular el flujo de ejecución del framework.
- $loader->response: Objeto usado por el framework para gestionar la respuesta enviada al cliente.
- $loader->controller: Instancia del plugin o del controlador actualmente en uso.
Las conexiones a las fuentes de datos son accesibles de dos formas diferentes:
-
$loader->dataSources es un Registro que contiene los objetos de conexión
(ver la documentación de controladores).
Por ejemplo, una conexión MySQL llamada db será accesible usando $loader->dataSources->db. -
Si el nombre de la fuente de datos aún no está presente en el componente (como session, config, etc.),
es directamente accesible desde el componente.
Por ejemplo, una conexión MySQL llamada db será accesible usando $loader->db.
3Acceso alternativo
En el ejemplo anterior, el loader (en modo service locator) se usó suponiendo que los objetos gestionados por el componente están en el namespace raíz (es decir, no están en un namespace explícito), y que sus archivos fuente están en el directorio lib/ del proyecto.
Pero a veces querrás usar objetos que se encuentran en namespaces más profundos. En ese caso, los objetos deben accederse usando una sintaxis de array asociativo, en lugar de una orientada a objetos.
Por ejemplo, si quieres usar el método add() del objeto \Math\Base\Calculator, debes escribir:
$res = $this->_loader['\Math\Base\Calculator']->add(3, 4);
También es posible usar el método get():
$res = $this->_loader->get('\Math\Base\Calculator')->add(3, 4);
Más adelante verás cómo simplificar esta sintaxis usando alias y prefijos.
4Almacenamiento
Los datos almacenados en el loader se identifican mediante una clave (la mayoría de las veces, el tipo del objeto). Si al principio de la clave hay una barra invertida (\), se elimina.
Este procesamiento existe para que las tres notaciones siguientes sean equivalentes:
$res = $loader['\Math\Base\Calculator']->add(3, 4);
$res = $loader['Math\Base\Calculator']->add(3, 4);
$res = $loader[\Math\Base\Calculator::class]->add(3, 4);
Caso especial: el propio loader satisface cualquier solicitud de su propia clase,
o de una de sus clases padre (hasta \Temma\Base\Loader).
El atajo TµLoader se acepta como sinónimo de
\Temma\Base\Loader.
Por lo tanto, las siguientes notaciones devuelven todas la propia instancia del loader:
$l = $loader['\Temma\Base\Loader'];
$l = $loader['Temma\Base\Loader'];
$l = $loader[\Temma\Base\Loader::class];
$l = $loader['TµLoader'];
5Configuración del loader
5.1Precarga
Es posible configurar el loader para proporcionarle valores que él mismo no tendrá que instanciar (o que no podría instanciar).
Así, en el archivo etc/temma.php, puedes definir valores fijos que estarán accesibles en toda la aplicación:
<?php
return [
'x-loader' => [
'preload' => [
// añade una instancia de ZipArchive
'zipManager' => new ZipArchive(),
// añade una string obtenida del entorno
'appPassword' => getenv('APP_PASSWORD'),
// añade un número que será accesible globalmente
'maxArticles' => 100,
]
]
];
Estos valores podrán ser usados directamente por el loader.
Así, será posible escribir:
print("Número máximo de artículos: " . $this->_loader->maxArticles);
Un valor registrado de esta forma también podrá ser usado por el loader en el contexto del autowiring.
Encontrarás más información sobre cómo añadir datos explícitamente en la sección dedicada.
5.2Alias
Es posible definir alias de nomenclatura, que se usarán cuando se llame a un objeto a través del loader.
Por ejemplo, con el siguiente archivo etc/temma.php:
<?php
return [
'x-loader' => [
'aliases' => [
'UserService' => '\MyApp\User\UserService',
'TµEmail' => '\Temma\Utils\Email',
]
]
];
Podrás escribir el siguiente código:
$list = $this->_loader->UserService->getList();
// es equivalente a
$list = $this->_loader['\MyApp\User\UserService']->getList();
$this->_loader->TµEmail->textMail($from, $to, $title, $message);
// es equivalente a
$this->_loader['\Temma\Utils\Email']->textMail($from, $to, $title, $message);
Encontrarás más información sobre los alias en la sección dedicada.
5.3Prefijos
También es posible definir prefijos de nomenclatura, que resumen namespaces (o partes de namespaces). Estos prefijos pueden usarse luego al principio del nombre de un objeto gestionado por el loader.
Ejemplo de un archivo etc/temma.php:
<?php
return [
'x-loader' => [
'prefixes' => [
'global' => '\MyApp',
'App' => '\OtherApp\Extension\OtherApp',
'€x' => '\Europa\Source\Service',
]
]
];
Podrás entonces escribir el siguiente código:
$object = $this->_loader->globalArticles;
// equivalente a
$object = $this->_loader['\MyApp\Articles'];
$object = $this->_loader->AppWidget;
// equivalente a
$object = $this->_loader['\OtherApp\Extension\OtherApp\Widget'];
$object = $this->_loader->€xUser;
// equivalente a
$object = $this->_loader['\Europa\Source\Service\User'];
Encontrarás más información sobre los prefijos en la sección dedicada más abajo.
6Autowiring: desacoplamiento
6.1Principio del autowiring
El autowiring es la capacidad del loader de detectar las dependencias que un objeto espera en su constructor.
Así, cualquier objeto puede llamarse a través del loader. En la primera llamada, el loader instanciará el objeto, pasándole sus dependencias como parámetros. Si el loader todavía no contiene instancias de esas dependencias, las creará sobre la marcha.
A continuación, un ejemplo de dos objetos, siendo uno dependencia del otro:
/**
* Objeto usado para calcular hashes a partir de identificadores.
*/
class Hasher {
/**
* Método que devuelve un hash a partir de una string.
* @param string $text Texto de entrada.
* @return string Hash calculado.
*/
public function hash(string $text) : string {
return hash('sha256', $text);
}
}
/**
* Objeto que gestiona los usuarios en la base de datos.
*/
class UserDao {
/**
* Constructor.
* @param \Temma\Datasources\Redis $ndb Conexión a la base de datos.
* @param Hasher $hasher Objeto de hash.
*/
public function __construct(
private \Temma\Datasources\Redis $ndb,
private Hasher $hasher,
) {
}
/**
* Devuelve la información de un usuario.
* @param int $id Identificador del usuario.
* @return array Array asociativo.
*/
public function get(int $id) : array {
$user = $this->ndb["user-$id"];
$user['hash'] = $this->hasher->hash($user['email']);
return $user;
}
}
- Líneas 4 a 13: Objeto utilitario Hasher, que no tiene dependencias.
-
Líneas 18 a 40: Objeto UserDao.
- Líneas 24 a 28: Constructor, que espera dos dependencias.
- Línea 36: Uso de la conexión a la base de datos Redis.
- Línea 37: Uso del objeto Hasher.
Y aquí está el código del controlador que llama al objeto UserDao:
class Account extends \Temma\Web\Controller {
public function show(int $id) {
$this['user'] = $this->_loader->UserDao->get($id);
}
}
- Línea 3: El loader se usa para llamar al objeto UserDao. Se crea una instancia sobre la marcha, y para crearla, el loader usa la conexión Redis ya existente y crea una instancia del objeto Hasher.
6.2Gestión de dependencias
Cuando el loader instancia automáticamente un objeto, recorre los parámetros esperados por el constructor.
Para un parámetro dado (por ejemplo \App\UserManager $userManager), hay tres casos posibles:
- Si el loader ya contiene una entrada con el nombre del tipo (por ejemplo \App\UserManager), se usa esa.
- Si hay una entrada con el nombre del parámetro (por ejemplo $userManager), se usa esa.
- Si el tipo del parámetro (por ejemplo \App\UserManager) es instanciable, el loader crea una instancia y la usa.
Una dependencia no tiene por qué ser necesariamente un objeto instanciable. Por ejemplo, si el loader contiene una string name, y un constructor tiene un parámetro string $name, el loader usará ese valor.
Los parámetros tipados como \Temma\Base\Loader (o con la clase del loader actual, o una de sus clases padre) son un caso especial:
- El loader actual siempre se inyecta. Así, cualquier objeto puede recibir el componente de inyección de dependencias, simplemente declarándolo en su constructor.
- El loader nunca crea una nueva instancia de loader. Si el parámetro está tipado con una clase de loader que no coincide con el loader actual, se lanza una excepción \Temma\Exceptions\Loader (o se inyecta el valor null si el parámetro admite valores nulos).
7Service locator: optimización del rendimiento
Aunque el autowiring es muy sencillo y cómodo de usar, tiene el inconveniente de ser costoso en términos de rendimiento.
Para una aplicación en la que el rendimiento sea crítico, puedes preferir el enfoque service locator.
En ese caso, un objeto no recibirá sus dependencias como parámetros del constructor. En su lugar, recibirá el loader,
que usará directamente para acceder a los objetos que necesita.
Para ello, debe implementar la interfaz \Temma\Base\Loadable, que exige que su constructor reciba un único parámetro, de tipo \Temma\Base\Loader.
Ten en cuenta que implementar esta interfaz no es estrictamente necesario para recibir el loader: como se vio antes, el autowiring inyecta el loader actual en cualquier parámetro de constructor tipado como \Temma\Base\Loader. La ventaja de la interfaz \Temma\Base\Loadable es que evita el uso de reflexión: el loader instancia directamente el objeto, pasándole su propia instancia, lo cual es mejor en términos de rendimiento.
A continuación, un ejemplo de un objeto que usa el loader para acceder a sus dependencias:
class SpecialLogger implements \Temma\Base\Loadable {
/** Constructor. */
public function __construct(private \Temma\Base\Loader $loader) {
}
/**
* Método que añade líneas al archivo 'var/list.txt'.
* @param string $text Texto a escribir.
*/
public function write(string $text) : void {
$dest = $this->loader->config->varPath . '/list.txt';
file_put_contents($dest, "$text\n", FILE_APPEND);
}
/**
* Método que usa el objeto OtherObject.
*/
public function process() : void {
$value = $this->loader->OtherObject->doCalculation();
$this->write($value);
}
}
- Línea 1: El objeto implementa la interfaz \Temma\Base\Loadable.
- Línea 3: Constructor del objeto, que recibe como parámetro una instancia del componente de inyección de dependencias. Se almacena como propiedad privada.
- Línea 11: En el método write(), $this->loader->config se usa para acceder al objeto de configuración (y su propiedad varPath).
-
Línea 19: En el método process(), se llama a $this->loader->OtherObject.
Si el loader ya tenía una instancia de OtherObject, se usa esa; en caso contrario, se crea
y se devuelve una nueva instancia.
OtherObject puede usar autowiring o implementar la interfaz \Temma\Base\Loadable.
Para que este objeto pueda ser cargado por el loader, debe ser accesible a través de las rutas de inclusión del proyecto. Por defecto, esto significa que debe registrarse en un archivo llamado SpecialLogger.php, colocado en el directorio lib/ del proyecto.
A partir de ese momento, el objeto está disponible directamente, como si fuera una propiedad del loader. Solo se creará una instancia del objeto (en la primera llamada).
A continuación, un ejemplo de controlador que usa el objeto SpecialLogger a través del loader:
class Homepage extends \Temma\Web\Controller {
/** Acción raíz. */
public function __invoke() {
// escribe en el archivo
$this->_loader->SpecialLogger->write('Funciona');
}
}
- Línea 5: El loader se usa para acceder al objeto SpecialLogger, y luego a su método write().
Ahora imagina que creamos otro objeto de negocio, cargable a través del loader, que usa el objeto SpecialLogger.
class Calculator implements \Temma\Base\Loadable {
/** Constructor. */
public function __construct(private \Temma\Base\Loader $loader) {
}
/**
* Función que realiza una suma.
* @param int $i Primer operando.
* @param int $j Segundo operando.
* @return int Resultado de la suma.
*/
public function add(int $i, int $j) : int {
$result = $i + $j;
$this->loader->SpecialLogger->write("Calculado: $result");
return ($result);
}
}
- Línea 3: Constructor del objeto, que almacena el loader como propiedad privada.
- Línea 14: El loader se usa para llamar al objeto SpecialLogger.
Esto ilustra cómo los objetos de negocio pueden llamarse entre sí, con el componente gestionando el acceso y la instanciación.
8Añadir datos al loader
8.1Adiciones explícitas
Puedes crear manualmente un objeto y añadirlo al loader especificando el nombre que quieres darle:
// crea la instancia del objeto
$calculator = new \Math\Base\Calculator($this->_loader);
// añade la instancia al componente
// (las tres sintaxis son equivalentes)
// - sintaxis orientada a objetos
$this->_loader->calculator = $calculator;
// - sintaxis de array asociativo
$this->_loader['calculator'] = $calculator;
// - usando el método set()
$this->_loader->set('calculator', $calculator);
Luego es posible recuperar este objeto desde el loader (usado como service locator):
// ahora que la instancia está registrada en el componente,
// puede usarse en cualquier lugar donde el componente sea accesible
$res = $this->_loader->calculator->add(3, 4);
Esto también funciona con autowiring (que aquí se basa en el nombre calculator en lugar del tipo):
class MyObject {
// constructor: recibe la dependencia añadida previamente al loader
public function __construct(private \Math\Base\Calculator $calculator) {
}
public function process(int $i, int $j) : int {
// usa el objeto privado
return $this->calculator->add($i, $j);
}
}
Puedes añadir cualquier tipo de elemento al loader (no solo objetos):
$this->_loader->anInteger = 3;
$this->_loader->anArray = ['a', 'b', 'c'];
$this->_loader->anObject = new \Toto();
8.2Adiciones por callback
También es posible asignar una función anónima. Esta función se ejecutará la primera vez que se acceda al loader; el valor que devuelva se almacenará entonces en el loader, y la función anónima no volverá a llamarse nunca más.
La función anónima recibe la instancia del loader como parámetro. Debe devolver el valor que sustituirá a la función anónima en el loader, y que será devuelto por el loader en cada llamada posterior para la misma clave.
Es posible pasar el nombre de una función, una función anónima, una string de llamada estática (por ejemplo 'MyObject::myMethod'), o un array de callback sobre un objeto instanciado (por ejemplo [$object, 'myMethod']).
A continuación, un controlador de ejemplo:
class Homepage extends \Temma\Web\Controller {
// inicialización
public function __wakeup() {
// añade la clave 'calc' asociada a una función anónima
$this->_loader->calc = function($loader) {
if ($this['param'] == 'base')
return (new \Math\Base\Calculator($loader));
return (new \Math\Other\Calculator($loader));
};
}
// acción
public function compute(string $type) {
$this['param'] = $type;
// la función anónima se ejecuta para generar la clave 'calc'
$this['res'] = $this->_loader->calc->add(3, 4);
// el valor de 'calc' se recupera directamente
$this['zzz'] = $this->_loader->calc->add(5, 6);
}
// otra acción
public function compute2() {
$this['res'] = $this->_loader->calc->add(3, 4);
}
}
- Línea 3: El método __wakeup() se usa para inicializar el controlador.
- Línea 5: La clave calc se crea en el loader y se le asigna una función anónima.
- Líneas 6 a 8: La variable $this hace referencia al propio controlador. La variable de plantilla param se usa para determinar qué implementación se devuelve.
- Línea 14: A la variable de plantilla param se le asigna el valor recibido en el parámetro $type.
- Línea 17: El loader se usa sin preocuparse de qué implementación se selecciona.
- Línea 20: El loader se usa para recuperar la misma instancia que en la llamada anterior.
- Línea 25: El loader se usa. Aquí siempre se usará el objeto \Math\Other\Calculator.
8.3Adiciones por callback dinámico
Con las adiciones por callback (vistas en la sección anterior), la función anónima se ejecuta una sola vez, y el valor que devuelve se almacena en el loader para sustituir la función anónima.
Pero a veces quieres que la función anónima se ejecute dinámicamente cada vez que se acceda al loader,
sin que se almacene el valor devuelto.
En ese caso, debes usar el método dynamic() del loader,
proporcionándole la clave y una función anónima:
// añade un valor dinámico al loader
$loader->dynamic('randomValue', function() {
return mt_rand(0, 255);
});
// muestra el valor varias veces; se regenerará cada vez
print($loader->randomValue . "\n");
print($loader->randomValue . "\n");
print($loader->randomValue . "\n");
print($loader->randomValue . "\n");
8.4Adiciones por builder
Un builder es una función encargada de gestionar las instanciaciones, y que se registra usando
el método setBuilder() del loader.
Esta función recibe dos parámetros: el primero es la instancia del loader; el segundo es el nombre del objeto
al que se está accediendo.
A continuación, un ejemplo:
// definición del builder
$this->_loader->setBuilder(function($loader, $key) {
// comprueba si el nombre del objeto solicitado termina
// en "Service" o en "Dao"
if (str_ends_with($key, 'Service')) {
$className = '\MyApp\Service\\' . substr($key, 0, -7);
return new $className($loader);
} else if (str_ends_with($key, 'Dao')) {
$className = '\MyApp\Dao\\' . substr($key, 0, -3);
return new $className($loader);
}
});
// esta llamada usará el objeto \MyApp\Service\Mail
$this->_loader->MailService->send();
// esta llamada usará el objeto \MyApp\Dao\User
$this->_loader->UserDao->remove($userId);
8.5Adiciones de DAO
Los objetos DAO pueden instanciarse en los controladores usando su método _loadDao(). Fuera de los controladores, el componente de inyección de dependencias puede usarse para crear instancias de objetos DAO que hayas desarrollado.
Por ejemplo, si has creado un objeto UserDao (en el archivo lib/UserDao.php), puedes usarlo así:
$user = $this->_loader->UserDao->getFromEmail($email);
9Gestión de alias
9.1Definición de alias
Como se vio antes, es posible definir alias de nomenclatura en el archivo de configuración.
También puedes declarar alias directamente en el loader usando el método alias():
// definición de un alias
$this->_loader->alias('UserService', '\MyApp\User\UserService');
// uso del alias
$list = $this->_loader->UserService->getList();
// es equivalente a
$list = $this->_loader['\MyApp\User\UserService']->getList();
También puedes declarar varios alias pasando un array asociativo:
// definición de un array de alias
$this->_loader->alias([
'UserService' => '\MyApp\User\UserService',
'TµEmail' => '\Temma\Utils\Email',
]);
// uso de los alias
$list = $this->_loader->UserService->getList();
$this->_loader->TµEmail->textMail($from, $to, $title, $message);
// es equivalente a
$list = $this->_loader['\MyApp\User\UserService']->getList();
$this->_loader['\Temma\Utils\Email']->textMail($from, $to, $title, $message);
Es posible eliminar un alias definido anteriormente proporcionando el valor null para ese mismo nombre de alias al método alias().
9.2Uso de alias para pruebas
Al ejecutar pruebas automatizadas sobre una aplicación Temma, puede que necesites sustituir un objeto por un mock, un “objeto stub” que simula el comportamiento del objeto original.
Con los alias de nomenclatura, resulta muy fácil crear un archivo etc/temma.test.php (si el entorno de ejecución se llama test), que redefine los objetos cargados para un nombre determinado.
Ejemplo de un archivo etc/temma.php:
<?php
return [
'x-loader' => [
'aliases' => [
'UserService' => '\MyApp\User\UserService'
]
]
];
Ejemplo de un archivo etc/temma.test.php:
<?php
return [
'x-loader' => [
'aliases' => [
'UserService' => '\MyMock\UserService',
'\Temma\Utils\Email' => '\MyMock\Email',
]
]
];
Tu código de aplicación puede contener:
// esta línea de código:
$list = $this->_loader->UserService->getList();
// en condiciones normales, es equivalente a:
$list = $this->_loader['\MyApp\User\UserService']->getList();
// en el entorno de pruebas, es equivalente a:
$list = $this->_loader['\MyMock\UserService']->getList();
// esta línea de código:
$this->_loader['\Temma\Utils\Email']->textMail($from, $to, $title, $message);
// en el entorno de pruebas, es equivalente a:
$this->_loader['\MyMock\Email']->textMail($from, $to, $title, $message);
10Gestión de prefijos
10.1Visión general de los prefijos
Además de los alias, también es posible definir prefijos de nomenclatura, que resumen namespaces. Estos prefijos pueden usarse luego al principio del nombre de un objeto gestionado por el loader.
Advertencia: Evita definir demasiados prefijos, ya que todos ellos se comprueban cada vez que se solicita un objeto por primera vez. Esto puede tener un impacto en el rendimiento.
10.2Definición de prefijo
Puedes declarar un prefijo usando el método prefix():
// definición de un prefijo
$this->_loader->prefix('global', '\MyApp');
// uso del prefijo
$object = $this->_loader->globalArticles;
// es equivalente a
$object = $this->_loader['\MyApp\Articles'];
// segundo prefijo
$this->_loader->prefix('App', '\OtherApp\Extension\OtherApp');
// uso del prefijo
$object = $this->_loader->AppWidget;
// es equivalente a
$object = $this->_loader['\OtherApp\Extension\OtherApp\Widget'];
// tercer prefijo
$this->_loader->prefix('€x', '\Europa\Source\Service');
// uso del prefijo
$object = $this->_loader->€xUser;
// es equivalente a
$object = $this->_loader['\Europa\Source\Service\User'];
También puedes declarar varios prefijos pasando un array asociativo:
// definición de un array de prefijos
$this->_loader->prefix([
'global' => '\MyApp',
'App' => '\OtherApp\Extension\OtherApp',
'€x' => '\Europa\Source\Service',
]);
Es posible eliminar un prefijo definido anteriormente proporcionando el valor null para ese mismo prefijo.
10.3Gestión de prefijos con callbacks
Al definir un prefijo (ya sea mediante el método prefix() o en el archivo de configuración), es posible asignarle un valor de tipo callable. En ese caso, cuando se solicita el elemento al loader, se ejecuta la función referenciada (pasándole como parámetros la instancia del loader y el nombre del objeto solicitado sin su prefijo), y su valor de retorno se usa como el valor asociado al nombre solicitado.
Ejemplo de código usando una función:
// definición de una función que devuelve un objeto según la configuración
function sinclairManager(\Temma\Base\Loader $loader, string $name) {
if ($name == 'Spectrum')
return new \Sinclair\Computers\ZxSpectrum();
if ($name == '81')
return new \Sinclair\Computers\Zx81();
if ($name == 'QL')
return new \Sinclair\Computers\QuantumLeap();
return (null);
}
// registro del prefijo
$this->_loader->setPrefix('Zx', 'sinclairManager');
// uso
$computer = $this->_loader->ZxSpectrum;
// es equivalente a
$computer = $this->_loader['\Sinclair\Computers\ZxSpectrum'];
// otro uso
$computer = $this->_loader->Zx81;
// es equivalente a
$computer = $this->_loader['\Sinclair\Computers\Zx81'];
// otro uso
$computer = $this->_loader->ZxQL;
// es equivalente a
$computer = $this->_loader['\Sinclair\Computers\QuantumLeap'];
Es posible pasar el nombre de una función, una función anónima, una string de llamada estática (por ejemplo 'MyObject::myMethod'), o un array de callback sobre un objeto instanciado (por ejemplo [$object, 'myMethod']).
11Sustitución del loader mediante configuración
En lugar de llamar explícitamente al método setBuilder(), es posible especificar un objeto loader en el archivo de configuración etc/temma.php (ver la documentación de configuración). Este objeto debe extender la clase \Temma\Base\Loader y contener un método protegido builder(). Este método debe recibir como parámetro el nombre del objeto a instanciar, y devolver una instancia de él.
A continuación, un ejemplo. Primero, la configuración de Temma en el archivo etc/temma.php:
<?php
return [
'application' => [
'loader' => 'MyLoader'
]
];
Luego, en el archivo lib/MyLoader.php:
class MyLoader extends \Temma\Base\Loader {
protected function builder(string $key) {
if ($key == 'User')
return new \MyApp\Dao\User($this);
else if ($key == 'HttpClient')
return new \Utils\Http\Client($this);
throw new Exception("Unknown object '$key'.");
}
}
Entonces resulta posible escribir:
// usa \MyApp\Dao\User
$user = $this->_loader->User->get($userId);
// usa \Utils\Http\Client
$this->_loader->HttpClient->post($url, $data);
Ten en cuenta que un loader personalizado también satisface las solicitudes de su propia clase o de la clase \Temma\Base\Loader, devolviendo su propia instancia. Esto también funciona con autowiring: un objeto puede tipar uno de los parámetros de su constructor como MyLoader para recibir el loader actual, correctamente tipado.