Los servicios
Recordatorio sobre los espacios de nombres
Sabemos que obtenemos las clases de los objetos que manipulamos gracias a sus espacios de nombres (namespace) y a Composer.
Una clase está localizada por su espacio de nombres, definido antes de ella (instrucción use).
Por ejemplo, si queremos usar la clase User en un controlador, es necesario que esta clase esté definida por su espacio de nombres: App\Entity\User.
Sabemos que el espacio de nombres se determina por defecto por la ruta hacia la ubicación física de la clase. La clase User está en la carpeta src/Entity; por lo tanto, su espacio de nombres se declara de la siguiente manera:
Clase User:
<?php
namespace App\Entity;
...
class User implements UserInterface
{
...
}
Cabe destacar que usamos el alias App para definir la carpeta src. Este espacio de nombres raíz se ha definido en el archivo composer.json. Bajo la etiqueta «autoload», encontramos la definición de App:
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
Puede darle otro nombre si lo desea, o agregar nuevos espacios de nombres...
¿Qué es un servicio?
Su aplicación está llena de múltiples objetos reutilizables. Por ejemplo, el objeto «Mailer» le permite enviar correos electrónicos, otro objeto le permite almacenar elementos en la base de datos, etc.
Cada vez que utiliza estas clases recurrentes, está obligado a instanciar las clases.
Existe una superclase en Symfony llamada contenedor de servicios. Su función es encargarse de la instanciación de las clases que usted desee antes de ejecutar la aplicación.
En pocas palabras, ¡lo hace todo por usted!
No solo le permite instanciar automáticamente las clases, sino que también instanciará sus dependencias. Por ejemplo, imaginemos una clase NewsletterManager que gestiona los boletines informativos. Esta clase utilizará la clase Mailer para enviar dichos boletines.
Si declaramos la clase NewsletterManager como servicio, el contenedor de servicios instanciará esta clase, así como la clase Mailer.
Otra ventaja del contenedor de servicios es que solo instanciará la clase una vez.
Si usted reutiliza un servicio que ya ha utilizado, le devolverá el objeto ya instanciado una vez, sin crear uno nuevo.
Probablemente ya percibe la utilidad de usar el contenedor de servicios, sobre todo para clases que se utilizan con frecuencia en el código.
Usamos el contenedor de servicios para clases que ofrecen...
Uso de los servicios
Ya hay varios servicios disponibles por defecto en su aplicación. ¿Cómo saber cuáles son?
Solo tiene que ejecutar el siguiente comando en el terminal:
php bin/console debug:autowiring
Los servicios listados son aquellos que pueden usarse mediante inyección de dependencias en la acción de un controlador, por ejemplo (autowiring).
Hay muchos más servicios en el contenedor. Para obtener una lista completa, puede ejecutar el comando:
php bin/console debug:container
Tomemos el servicio LoggerInterface como ejemplo, que permite enviar mensajes al registro de logs. Observamos su clase ejecutando el primer comando:
php bin/console debug:container
La clase está definida por su espacio de nombres: Psr\Log\LoggerInterface, pero también por un identificador único. Por ejemplo, la clase base está definida por el identificador monolog.logger. Se define un identificador particular para cada tipo de log, como, por ejemplo, monolog.logger.cache para los mensajes almacenados en caché.
Podemos utilizar el servicio LoggerInterface directamente usando su espacio de nombres mediante lo que llamamos autowiring. Esto permite inyectar el servicio directamente en un método (por ejemplo, una acción de un controlador).
Probemos este método en nuestro controlador TestController:
<?php
...
use Symfony\Component\HttpFoundation\Response;
use Psr\Log\LoggerInterface; ...
Creación de su propio servicio
Es posible registrar sus propias clases como servicios en el contenedor de servicios.
Vamos a crear un servicio que genere mensajes aleatorios.
Creemos una carpeta Service dentro de la carpeta src y creemos el archivo src/Service/MessageGenerator.php:
<?php
namespace App\Service;
class MessageGenerator
{
public function getHappyMessage()
{
$messages = [
'¡Bravo, buen trabajo!',
'Este es el mejor servicio jamás visto',
'¡Esto es una auténtica genialidad! ',
];
$index = array_rand($messages);
return $messages[$index];
}
}
El método array_rand() devuelve aleatoriamente un mensaje del array $messages.
¿Cómo se declara esta clase en el contenedor de servicios?
¡Ya está hecho! Es sorprendente, pero, por defecto, Symfony...
Cómo inyectar un servicio dentro de otro servicio
Para inyectar un servicio en el constructor de otro servicio (inyección de dependencias), lo definiremos como argumento en el archivo config/services.yml.
Tomemos como ejemplo nuestro servicio: App\Service\MessageGenerator. Deseamos que este servicio utilice el servicio LoggerInterface mediante autowiring. Podemos pasarle el identificador del servicio como argumento.
Recuerde que puede encontrar la lista de servicios y sus identificadores mediante la instrucción php/bin/console debug.
En el archivo config/services.yaml, añadamos estas líneas:
App\Service\MessageGenerator:
arguments:
$logger: '@monolog.logger'
El signo @ indica a Symfony que estamos pasando un identificador de servicio como argumento, y no una cadena de caracteres.
El parámetro $logger será inyectado automáticamente en el constructor del servicio App\Service\MessageGenerator. Modifiquemos esta clase para que pueda utilizar LoggerInterface:
<?php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
public function __construct(LoggerInterface $logger)
{
} ...