Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Objetos Servicio

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

En realidad, Symfony tiene dos partes... y acabamos de aprender una de ellas.

La primera parte es el sistema ruta-controlador. Y espero que te sientas muy cómodo: crea una ruta, la ruta ejecuta una función del controlador, regresamos una respuesta.

La segunda mitad de Symfony es todo sobre los múltiples "objetos útiles" que flotan alrededor de Symfony. Por ejemplo, cuando hacemos un render de un template, lo que en realidad hacemos es aprovecharnos del objeto twig y decirle que haga el render. El método render() es solo un atajo para utilizar ese objeto. También existe un objeto logger, el objeto del caché y muchos otros, como el objeto de la conexión con la base de datos y un objeto que ayuda a hacer llamados HTTP a otras APIs.

Básicamente... cada cosa que Symfony realiza - o nosotros - realmente es hecha por uno de estos objetos útiles. Demonios, incluso el rúter es un objeto que busca cuál ruta se empareja con el request actual.

En el mundo de Symfony - bueno, en realidad, en el mundo de programación orientada a objetos - estos "objetos que hacen algún trabajo" se les otorga un nombre especial: servicios. Pero no permitas que te confunda: cuando escuches "servicio", solo piensa:

¡Hey! Es un objeto que hace algún trabajo - como el objeto logger o el objeto que hace consultas a la base de datos.

Listando Todos los Servicios

Dentro del CommentController, vamos a registrar un log. Para hacerlo, necesitamos el servicio "logger". ¿Cómo lo podemos obtener?

Encuentra tu terminal y corre:

php bin/console debug:autowiring

Saluda a uno de los comandos más importantes de bin/console. Esto nos muestra una lista de todos los objetos servicio en nuestra app. Bueno, está bien, estos no son todos: pero es una lista que contiene todos los servicios que probablemente necesites.

Incluso en nuestra pequeña app, hay muchas cosas aquí: hay algo llamado Psr\Log\LoggerInterface, hay cosas para el caché y mucho más. Conforme instalamos más bundles, esta lista va a crecer. Más servicios significa más herramientas.

Para encontrar qué servicio nos permite crear "logs", corre:

php bin/console debug:autowiring log

Esto retorna un montón de cosas... pero ignora todos los de aquí abajo por ahora y enfocate en la línea de arriba. Esto nos dice que hay un objeto servicio logger y su clase implementa una Psr\Log\LoggerInterface. ¿Por qué es esto importante? Porque para pedir el servicio logger, lo haces utilizando este type-hint. Se le llama "autowiring".

Usando Autowiring para el Servicio del Logger

Así es como obtienes un servicio desde un controlador. Agrega un tercer argumento a tu método - aunque el orden de los argumentos no importa. Escribe LoggerInterface - autocompleta el del Psr\Log\LoggerInterface - y $logger.

... lines 1 - 4
use Psr\Log\LoggerInterface;
... lines 6 - 9
class CommentController extends AbstractController
{
... lines 12 - 14
public function commentVote($id, $direction, LoggerInterface $logger)
{
... lines 17 - 28
}
}

Esto agregó el import arriba de la clase para Psr\Log\LoggerInterface, el cual es él mismo type-hint que el debug:autowiring nos dijo que usáramos. Gracias a este type-hint, cuando Symfony hace un render de nuestro controlador, sabrá que queremos que nos pase el servicio del logger a este argumento.

Entonces... si: ahora existen dos tipos de argumentos que puedes agregar a tus métodos del controlador. Primero, puedes tener un argumento que se empareja con un comodín de tu ruta. Y segundo, puedes tener un argumento cuyo type-hint sea el mismo a una de las clases o interfaces listadas en debug:autowiring. CacheInterface es otro type-hint que podemos usar para tener el servicio de caché.

Utilizando el Servicio del Logger

Así que... ¡Vamos a usar este objeto! ¿Qué métodos nos permite llamar? ¡No tengo idea! Pero como escribimos el type-hint apropiado, podemos decir $logger-> y PhpStorm nos dice exactamente cuales métodos tiene. Utilicemos $logger->info() para decir "Voting up!". Cópialo y di "Voting down!" en el else.

... lines 1 - 9
class CommentController extends AbstractController
{
... lines 12 - 14
public function commentVote($id, $direction, LoggerInterface $logger)
{
... lines 17 - 19
if ($direction === 'up') {
$logger->info('Voting up!');
... line 22
} else {
$logger->info('Voting down!');
... line 25
}
... lines 27 - 28
}
}

¡Es hora de probarlo! Refresca la página y... Hagamos click en arriba, abajo, arriba. Esto... por lo menos no parece que esté roto.

Mueve el mouse sobre la parte del AJAX de la herramienta web debug y abre el profiler para uno de estos llamados. El profiler tiene una sección de "Logs", la cual ofrece una forma fácil de ver los logs para un solo Request. ¡Ahí está! "Voting up!". También puedes encontrar esto en el archivo var/log/dev.log.

El punto es: Symfony tiene muchos, muchos objetos útiles, digo "servicios". Y poco a poco, vamos a empezar a utilizar más de ellos... Cada vez agregando un type-hint para decirle a Symfony cual servicio queremos.

Autowiring & Utilizando el Servicio de Twig

Veamos otro ejemplo. El primer servicio que usamos en nuestro código es el servicio de Twig. Lo usamos... de forma "indirecta" al llamar $this->render(). En realidad, ese método es un atajo para utilizar el servicio Twig detrás de escenas. Y eso no debería de sorprenderte. Como dije antes, todo lo que se realiza en Symfony es hecho en realidad por un servicio.

Como reto, vamos a suponer que la función render() no existe. Gasp! En el controlador del homepage() comentariza la línea render().

Entonces... ¿Cómo podemos utilizar el servicio de Twig directamente para hacer un render de un template? ¡No lo sé! Definitivamente podemos encontrar algo de documentación al respecto... pero vamos a ver si podemos descubrirlo por nosotros mismos con la ayuda del comando debug:autowiring

php bin/console debug:autowiring twig

Y, ¡Voilà! Aparentemente existe una clase llamada Twig\Environment que podemos usar como "type-hint" para obtener el servicio de Twig. En nuestro controlador, escribe Environment y presiona tab para agregar el import arriba. Voy a nombrar al argumento $twigEnvironment.

... lines 1 - 7
use Twig\Environment;
... line 9
class QuestionController extends AbstractController
{
... lines 12 - 14
public function homepage(Environment $twigEnvironment)
{
... lines 17 - 21
//return $this->render('question/homepage.html.twig');
}
... lines 24 - 40
}

Dentro, escribe $html = $twigEnvironment->. De nuevo, sin leer nada de documentación, gracias al hecho de que estamos escribiendo código responsablemente y usamos type-hints, PhpStorm nos muestra todos los métodos de esta clase. ¡Mira! ¡Este método render() parece que es el que necesitamos! Pasa el mismo nombre del template de antes.

... lines 1 - 9
class QuestionController extends AbstractController
{
... lines 12 - 14
public function homepage(Environment $twigEnvironment)
{
// fun example of using the Twig service directly!
$html = $twigEnvironment->render('question/homepage.html.twig');
... lines 19 - 21
//return $this->render('question/homepage.html.twig');
}
... lines 24 - 40
}

Cuando usas twig directamente, en vez de retornar un objeto tipo Response, retorna un string con el HTML. No hay problema: termina con return new Response() - la de HttpFoundation - y pasa $html.

... lines 1 - 5
use Symfony\Component\HttpFoundation\Response;
... lines 7 - 9
class QuestionController extends AbstractController
{
... lines 12 - 14
public function homepage(Environment $twigEnvironment)
{
// fun example of using the Twig service directly!
$html = $twigEnvironment->render('question/homepage.html.twig');
return new Response($html);
//return $this->render('question/homepage.html.twig');
}
... lines 24 - 40
}

Esto ahora está haciendo exactamente lo mismo que $this->render(). Para probarlo, haz click en la página de inicio. Todavía funciona.

Ahora en realidad, más allá de ser un "gran ejercicio" para entender los servicios, no hay razón para tomar el camino más largo. solo quiero que entiendas que los servicios realmente son las "cosas" que hacen el trabajo detrás de escenas. Y si quisieras hacer algo - como un log o un render de un template - lo que realmente necesitas es encontrar que servicios hacen ese trabajo. Confía en mi, esta es la clave para liberar todo tu potencial de Symfony.

Pongamos de vuelta el código anterior más corto, y comentariza el otro ejemplo.

... lines 1 - 9
class QuestionController extends AbstractController
{
... lines 12 - 14
public function homepage(Environment $twigEnvironment)
{
/*
// fun example of using the Twig service directly!
$html = $twigEnvironment->render('question/homepage.html.twig');
return new Response($html);
*/
return $this->render('question/homepage.html.twig');
}
... lines 26 - 42
}

Muy bien, ya casi has terminado el primer tutorial de symfony. ¡Eres el mejor! Como premio, vamos a terminar con algo divertido: Una introducción al sistema llamado Webpack Encore que te va a permitir hacer cosas alocadas con tu CSS y JavaScript.

Leave a comment!

5
Login or Register to join the conversation
Default user avatar
Default user avatar Boran Alsaleh | posted hace 2 años

Hi when we send a request to our Controller for example index(LoggerInterface $logger) , does symfony create a new logger object at this moment and inject it to the controller or is this object already created before “e.g in kernel “and just we are using an existing object from symfony behind the seen could you please explain me that & how symfony is creating this obj inject it into our . Thanks in Advance

1 Reply

Hey Boran,

Good question! All the autowiring things are related to Symfony's Dependency Injection Container. That container knows everything about creating those objects, i.e. how to create that Logger object, etc. So the container is responsible for the creation of them. And yes, if we're talking about LoggerInterface - it will either created a new instance of it if it has not been created yet, or just will pass the instance that was already created before. For example, if you have an event listener that is called earlier than that controller, and that listener also requires the same LoggerInterface object - the same object will be passed to the controller. But if no code requires the logger before the controller - a new instance will be created and passed.

In shorts, learn more about Symfony's Dependency Injection and Dependency Injection Container to know more about how it works.

I hope this helps!

Cheers!

1 Reply
Peter-K Avatar

We had to inject loggerinterface but we are not injecting environment. Why is it coded this way? Both objects are services but one of them is not injected. Why accessing approach is not consistent? In other words why it is not designed that way that I can just type $this->info(). There seems to be more of them.

'router' => '?'.RouterInterface::class,
'request_stack' => '?'.RequestStack::class,
'http_kernel' => '?'.HttpKernelInterface::class,
'serializer' => '?'.SerializerInterface::class,
'session' => '?'.SessionInterface::class,
'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class,
'twig' => '?'.Environment::class,
'doctrine' => '?'.ManagerRegistry::class,
'form.factory' => '?'.FormFactoryInterface::class,
'security.token_storage' => '?'.TokenStorageInterface::class,
'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class,
'parameter_bag' => '?'.ContainerBagInterface::class,
'message_bus' => '?'.MessageBusInterface::class,
'messenger.default_bus' => '?'.MessageBusInterface::class,

Why one of them is not logger => '?'.LoggerInterface::class?

Reply

Hey Peter,

Those are just different approaches that allow you to achieve same results. Controllers are special, and allow you to use method injection instead of constructor injection - this gives you some more flexibility as on the request only those services will be booted (created) that are used directly in the specific controller's action. If you would use construction injection - most probably it would mean that you would use only some of those methods in a specific action, and some other in another specific action... but they all will be booted in the constructor no matter what action is hit. So in some cases it makes sense to use constructor injection when you want to share services across the whole class, and in some cases - use method injection to share some services across the specific method only, i.e. make them to be booted and available in a specific method only.

Anyway, you can choose whatever method you like more, or whatever you're comfortable with - we just try to show you all possible options you have in our tutorials ;)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.9
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}
userVoice