This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
05.

"Cableando" nuestro servicio Bundle

|

Share this awesome video!

|

Keep on Learning!

Hemos creado la clase que queremos utilizar como servicio en nuestro bundle, pero cuando intentamos inyectarla, obtenemos este error "no se puede autocablear el argumento...".

En tus aplicaciones finales, puede que estés acostumbrado a que esto funcione. Esto se debe a que la autoconexión de servicios está activada por defecto para todas las clases del directorio srcde tu aplicación.

Los bundles, sin embargo, son otra historia. Si llevas usando Symfony tanto tiempo como yo, puede que recuerdes que en los viejos tiempos, ¡la autoconexión no existía! Tenías que definir manualmente cada... servicio en un archivo YAML o XML. Era duro...

Por suerte, para las aplicaciones, esto ya no es así (en su mayor parte). Pero aún tienes que hacerlo para los bundles. Esto se debe a que los bundles están pensados para ser reutilizables, y quieres dar al usuario final la mayor flexibilidad posible.

Sin embargo, todavía hay algunas mejoras en este aspecto.

Crear un archivo de servicios

¿Recuerdas que creamos ese directorio vacío config en nuestro bundle? Aquí es donde definimos los servicios de nuestro bundle. En él, crea un archivo PHP normal (no una clase): services.php.

Si has escrito bundles en el pasado, puede que hayas utilizado XML para definir los servicios. Esta era la mejor práctica anterior. Pero hoy en día se prefiere utilizar PHP. En tus aplicaciones finales, si necesitas definir servicios manualmente, es probable que utilices YAML. No hay nada que te impida utilizar YAML, pero esto requeriría que tu bundle dependiera del componente YAML. Así pues, ¡que sea PHP!

Primero, añade namespace Symfony\Component\DependencyInjection\Loader\Configurator:

// ... lines 1 - 2
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
// ... lines 4 - 8

Esto nos permitirá utilizar las clases y funciones de ayuda a la definición del servicio que necesitamos sin necesidad de importar cada una de ellas.

A continuación, return static function (ContainerConfigurator $container):

// ... lines 1 - 4
return static function (ContainerConfigurator $container): void {
};

Dentro, escribe $container->services(). Encadenaremos nuestras definiciones de servicio a partir de esto:

// ... lines 1 - 4
return static function (ContainerConfigurator $container): void {
};

Definir los servicios del bundle

¡Ahora viene la parte divertida! Añade ->set() - el primer argumento es el ID del servicio. En tus aplicaciones, suele ser el nombre de la clase, pero en los bundles, utilizamos una cadena sin formato - de nuevo, para obtener la máxima flexibilidad. Este ID tiene que ser único, así que ponle como prefijo un espacio de nombres que tenga sentido para tu bundle. En este caso, utilizaremos symfonycasts.. Ahora el nombre de nuestro servicio: object_translator. El segundo argumento es el nombre completo de la clase: ObjectTranslator::class (asegúrate de importarlo):

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
;
};

Informar a Symfony sobre el servicio

Ahora tenemos este archivo... más o menos... aleatorio services.php en nuestro bundle, ¡así que tenemos que decírselo a Symfony!

En ObjectTranslationBundle, anula el método loadExtension() deAbstractBundle. Este método es llamado cuando Symfony carga el bundle.

El método padre está vacío, así que podemos eliminar esta llamada. Importa nuestro archivoservices.php utilizando $container->import(). La ruta es relativa a nuestro archivo actual, así que escribe ../config/services.php:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
;
};

¿Esto es todo lo que necesitamos? Veamos. Vuelve al navegador y actualiza la página de error. Hmm, el mismo error. Pero ahora tenemos más detalles: "Tal vez deberías asignar un alias de esta clase al servicio symfonycasts.object_translatorexistente"

Esto sigue siendo un progreso: puesto que Symfony sugiere el ID del servicio, ¡eso significa que lo conoce!

Alias de nuestro servicio

En ArticleController::show(), donde estamos intentando inyectar ObjectTranslator, podríamos añadir el atributo #[Autowire] con el ID del servicio... Esto funcionaría... ¡pero podemos hacerlo mejor! Quiero que este servicio sea autoconectable. Lo hacemos estableciendo el nombre de la clase como alias del servicio.

En services.php, debajo de set(), escribe ->alias(). El primer argumento es el alias que queremos crear: ObjectTranslator::class. El segundo argumento es el ID del servicio que definimos antes: symfonycasts.object_translator:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
// ... lines 9 - 10
->alias(ObjectTranslator::class, 'symfonycasts.object_translator')
;
};

De vuelta en el navegador, actualiza. Y... sigue apareciendo un error, pero diferente. "Demasiados pocos argumentos pasados a ObjectTranslator".

Definir los argumentos del servicio

Recuerda que en ObjectTranslator, tenemos dos dependencias necesarias:LocaleAwareInterface, un servicio, y $defaultLocale, un parámetro contenedor. Tenemos que decirle a Symfony cómo suministrarlos.

Sé que podemos autoconectar LocaleAwareInterface, pero en los bundles, necesitamos configurarlo manualmente, por lo que necesitamos su ID de servicio. Para encontrarlo, en tu terminal, ejecuta:

symfony console debug:autowiring LocaleAware

No necesitamos el nombre completo, basta con la primera parte. Perfecto, aquí está: translation.locale_switcher. Cópialo.

Vuelve a services.php, justo debajo de set(), haz una sangría para mantener esto organizado, y escribe ->args() con una matriz. Estos elementos coinciden con el orden de los argumentos del constructor, así que el primer argumento es el servicio. Utiliza la función service() y pega el ID del servicio que acabamos de encontrar:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
->args([
service('translation.locale_switcher'),
// ... line 12
])
// ... lines 14 - 15
;
};

Ahora el segundo argumento, $defaultLocale. Se trata de un parámetro contenedor. Podemos listar todos los parámetros en el terminal ejecutando:

symfony console debug:container --parameters

Lista grande... fíltralo ejecutando el mismo comando pero con | grep locale al final:

symfony console debug:container --parameters | grep locale

kernel.default_locale ¡es lo que buscamos! Cópialo.

De vuelta en services.php, para el segundo elemento de la matriz args, utiliza la función param() y... pega:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
->args([
// ... line 11
param('kernel.default_locale'),
])
// ... lines 14 - 15
;
};

Volvemos a nuestro navegador... refrescamos... ¡y éxito! ¡Ningún error significa que el servicio está correctamente definido e inyectado!

A continuación, veremos cómo nuestro bundle almacenará las traducciones.