Crear tu propio Servicio
Sabemos que los servicios funcionan, y sabemos que Symfony está lleno de servicios que podemos utilizar. Si Ejecutas:
php bin/console debug:autowiring
Obtenemos el menú de servicios, en el que puedes pedir cualquiera de ellos añadiendo un argumento de tipo con la clase o interfaz correspondiente.
Por supuesto, también hacemos trabajo en nuestro código... con suerte. Ahora mismo, todo ese trabajo se realiza dentro de nuestro controlador, como la creación de los datos de la Nave Estelar. Claro, esto está codificado ahora mismo, pero imagina que fuera trabajo real: como una consulta compleja a una base de datos. Poner la lógica dentro de un controlador está "bien"... pero ¿y si quisiéramos reutilizar este código en otro sitio? ¿Y si, en nuestra página de inicio, quisiéramos obtener un recuento dinámico de las naves estelares tomando estos mismos datos?
Crear la clase de servicio
Para ello, tenemos que trasladar este "trabajo" a un servicio propio que puedan utilizar ambos controladores. En el directorio src/, crea un nuevo directorio Repository y una nueva clase PHP en su interior llamada StarshipRepository.
| // ... lines 1 - 2 | |
| namespace App\Repository; | |
| class StarshipRepository | |
| { | |
| } |
Al igual que cuando creamos nuestra clase Starship, esta nueva clase no tiene absolutamente nada que ver con Symfony. Es sólo una clase que hemos decidido crear para organizar nuestro trabajo. Por lo tanto, a Symfony no le importa cómo se llama, dónde vive o qué aspecto tiene. Yo la llamé StarshipRepository y la puse en un directorio Repository porque es un nombre de programación común para una clase cuyo "trabajo" es obtener un tipo de datos, como los datos de la nave estelar.
Autocableado del nuevo servicio
Vale, antes de hacer nada aquí, vamos a ver si podemos utilizar esto dentro de un controlador. Y, ¡buenas noticias! Sólo con crear esta clase, ya está disponible para autocableado. Añade un argumento StarshipRepository $repository y, para asegurarte de que funciona, dd($repository).
| // ... lines 1 - 5 | |
| use App\Repository\StarshipRepository; | |
| // ... lines 7 - 11 | |
| class StarshipApiController extends AbstractController | |
| { | |
| // ... line 14 | |
| public function getCollection(LoggerInterface $logger, StarshipRepository $repository): Response | |
| { | |
| $logger->info('Starship collection retrieved'); | |
| dd($repository); | |
| // ... lines 19 - 43 | |
| } | |
| } |
Muy bien, gira, vuelve a hacer clic en nuestra ruta, y... ya está. Qué guay! Symfony ha visto la sugerencia de tipo StarshipRepository, ha instanciado ese objeto y nos lo ha pasado. Borra el dd()... y movamos los datos de la nave estelar dentro. Cópialo... y crea una nueva función pública llamada, qué tal, findAll(). Dentro, return, y pégala.
| // ... lines 1 - 4 | |
| use App\Model\Starship; | |
| class StarshipRepository | |
| { | |
| public function findAll(): array | |
| { | |
| return [ | |
| new Starship( | |
| 1, | |
| 'USS LeafyCruiser (NCC-0001)', | |
| 'Garden', | |
| 'Jean-Luc Pickles', | |
| 'taken over by Q' | |
| ), | |
| new Starship( | |
| 2, | |
| 'USS Espresso (NCC-1234-C)', | |
| 'Latte', | |
| 'James T. Quick!', | |
| 'repaired', | |
| ), | |
| new Starship( | |
| 3, | |
| 'USS Wanderlust (NCC-2024-W)', | |
| 'Delta Tourist', | |
| 'Kathryn Journeyway', | |
| 'under construction', | |
| ), | |
| ]; | |
| } | |
| } |
De vuelta en StarshipApiController, borra eso... y queda maravillosamente sencillo:$starships = $repository->findAll().
| // ... lines 1 - 4 | |
| use App\Repository\StarshipRepository; | |
| // ... lines 6 - 10 | |
| class StarshipApiController extends AbstractController | |
| { | |
| ('/api/starships') | |
| public function getCollection(LoggerInterface $logger, StarshipRepository $repository): Response | |
| { | |
| $logger->info('Starship collection retrieved'); | |
| $starships = $repository->findAll(); | |
| // ... lines 18 - 19 | |
| } | |
| } |
¡Listo! Cuando lo probamos, sigue funcionando... y ahora el código para obtener naves estelares está bien organizado en su propia clase y es reutilizable en toda nuestra aplicación.
Autocableado del Constructor
Con esta victoria en nuestro haber, vamos a hacer algo más difícil. ¿Qué pasaría si, desde dentro de StarshipRepository, necesitáramos acceder a otro servicio que nos ayudara a hacer nuestro trabajo? ¡No hay problema! ¡Podemos utilizar el autocableado! Intentemos autocablear de nuevo el servicio logger.
La única diferencia esta vez es que no vamos a añadir el argumento a findAll(). Te explicaré por qué en un minuto. En lugar de eso, añade un nuevo public function __construct()y realiza el autocableado allí: private LoggerInterface $logger.
| // ... lines 1 - 5 | |
| use Psr\Log\LoggerInterface; | |
| class StarshipRepository | |
| { | |
| public function __construct(private LoggerInterface $logger) | |
| { | |
| } | |
| // ... lines 13 - 41 | |
| } |
A continuación, para utilizarlo, copia el código de nuestro controlador, bórralo, pégalo aquí y actualízalo a $this->logger.
| // ... lines 1 - 5 | |
| use Psr\Log\LoggerInterface; | |
| class StarshipRepository | |
| { | |
| public function __construct(private LoggerInterface $logger) | |
| { | |
| } | |
| public function findAll(): array | |
| { | |
| $this->logger->info('Starship collection retrieved'); | |
| // ... lines 17 - 40 | |
| } | |
| } |
¡Genial! En el controlador, podemos eliminar ese argumento porque ya no lo vamos a utilizar.
¡Hora de probar! ¡Actualiza! No hay error: buena señal. Para ver si se ha registrado algo, ve a /_profiler, haz clic en la petición superior, Registros, y... ¡ahí está!
Te explicaré por qué hemos añadido el argumento de servicio al constructor. Si queremos obtener un servicio -como el registrador, una conexión a una base de datos, lo que sea-, ésta es la forma correcta de utilizar el autocableado: añadir un método __construct dentro de otro servicio. El truco que vimos antes -en el que añadimos el argumento a un método normal- sí, eso es especial y sólo funciona para los métodos del controlador. Es una comodidad adicional que se añadió al sistema. Es una gran característica, pero la forma del constructor... así es como funciona realmente el autocableado.
Y esta forma "normal", funciona incluso en un controlador. Podrías añadir un método __construct()con un argumento autocableable y funcionaría perfectamente.
La cuestión es: si estás en un método controlador, claro, añade el argumento al método, ¡está bien! Sólo recuerda que es algo especial que sólo funciona aquí. En cualquier otra parte, autowire a través del constructor.
Utilizar el Servicio en otra Página
Celebremos nuestro nuevo servicio utilizándolo en la página principal. AbreMainController. Este $starshipCount codificado es tan de hace 30 minutos. AutocableaStarshipRepository $starshipRepository, luego di$ships = $starshipRepository->findAll() y cuéntalos con count().
| // ... lines 1 - 4 | |
| use App\Repository\StarshipRepository; | |
| // ... lines 6 - 9 | |
| class MainController extends AbstractController | |
| { | |
| ('/') | |
| public function homepage(StarshipRepository $starshipRepository): Response | |
| { | |
| $ships = $starshipRepository->findAll(); | |
| $starshipCount = count($ships); | |
| // ... lines 17 - 22 | |
| } | |
| } |
Ya que estamos aquí, en lugar de esta matriz $myShip codificada, vamos a coger un objeto Starship al azar. Podemos hacerlo diciendo $myShip igual a$ships[array_rand($ships)]
| // ... lines 1 - 4 | |
| use App\Repository\StarshipRepository; | |
| // ... lines 6 - 9 | |
| class MainController extends AbstractController | |
| { | |
| ('/') | |
| public function homepage(StarshipRepository $starshipRepository): Response | |
| { | |
| $ships = $starshipRepository->findAll(); | |
| $starshipCount = count($ships); | |
| $myShip = $ships[array_rand($ships)]; | |
| // ... lines 18 - 22 | |
| } | |
| } |
¡Vamos a probarlo! Busca en tu navegador y dirígete a la página de inicio. ¡Ya está! Vemos el barco que cambia aleatoriamente aquí abajo, y el número de barco correcto aquí arriba... porque lo estamos multiplicando por 10 en la plantilla.
Imprimiendo objetos en Twig
¡Y acaba de ocurrir algo alucinante! Hace un momento, myShip era una matriz asociativa. Pero lo hemos cambiado para que sea un objeto Starship. Y aún así, el código de nuestra página siguió funcionando. Acabamos de ver accidentalmente un superpoder de Twig. Ve atemplates/main/homepage.html.twig y desplázate hasta el final. Cuando dicesmyShip.name, Twig es realmente inteligente. Si myShip es una matriz asociativa, cogerá la clave name. Si myShip es un objeto, como lo es ahora, cogerá la propiedad name. Pero aún más, si miras Starship, la propiedad name es privada, por lo que no podemos acceder a ella directamente. Twig se da cuenta de ello. Mira la propiedadname, ve que es privada, pero también ve que hay unagetName() pública. Así que llama a esa.
Todo lo que tenemos que decir es myShip.name... y Twig se encarga de los detalles de cómo obtenerlo, lo cual me encanta.
Vale, un último pequeño ajuste. En lugar de pasar el starshipCount a nuestra plantilla, podemos hacer el recuento dentro de Twig. Elimina esta variable y, en su lugar, pasa una variable ships.
| // ... lines 1 - 9 | |
| class MainController extends AbstractController | |
| { | |
| // ... line 12 | |
| public function homepage(StarshipRepository $starshipRepository): Response | |
| { | |
| $ships = $starshipRepository->findAll(); | |
| $myShip = $ships[array_rand($ships)]; | |
| // ... line 17 | |
| return $this->render('main/homepage.html.twig', [ | |
| 'myShip' => $myShip, | |
| 'ships' => $ships, | |
| ]); | |
| } | |
| } |
En la plantilla, ahí lo tenemos, para el recuento, podemos decir ships, que es una matriz, y luego utilizar un filtro Twig: |length.
| // ... lines 1 - 4 | |
| {% block body %} | |
| // ... lines 6 - 9 | |
| <div> | |
| Browse through {{ ships|length * 10 }} starships! | |
| {% if ships|length > 2 %} | |
| // ... lines 14 - 17 | |
| {% endif %} | |
| </div> | |
| // ... lines 20 - 42 | |
| {% endblock %} |
Así queda bien. Hagamos lo mismo aquí abajo... y cambiémoslo a mayor que 2. Pruébalo. ¡Nuestro sitio sigue funcionando!
Lo siguiente: creemos más páginas y aprendamos a hacer rutas aún más inteligentes.
37 Comments
Is there a difference?
1.
2.
Hey John,
Technically, the 2nd is the new PHP 8 syntax called "Constructor Property Promotion", but eventually in both examples a private Class property is created and set to $logger. If you're on a newer PHP version - it's better to use the new syntax as it requires less code to write and easier to read :)
Cheers!
How can the 2nd one work?
I'm getting Warning: Undefined property: App\Repository\StarshipRepository::$logger
~/Projects/starshop(master)$ php -v
PHP 8.3.21 (cli) (built: May 9 2025 06:27:43) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.21, Copyright (c) Zend Technologies
with Zend OPcache v8.3.21, Copyright (c), by Zend Technologies
Hey Martin,
It's difficult to say exactly what the problem is with your code without seeing it. Could you share the code snippet related to the line that shows you that warning?
Cheers!
well, I'm wondering how could the second form of constructor work without setting the private property?
$this->logger = $logger;
Undefined property: App\Repository\StarshipRepository::$logger
Hey Martin,
If you're asking about this specific code block:
The answer is the new PHP syntax, that's how PHP work. You may want to read more in the official docs: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion
In short, that just sets the property on the class with the name of the variable you're using in the constructor method signature. I mean, that's the official PHP syntax that do that job for you instead of writing that manually like in my first example. That's it :)
I hope that helps!
Cheers!
well, why do I get the error then? I follow your code and versions, defined in composer.json
Hey Martin,
Most probably, the error is not related to the code I shared with you. Could you share the code for the specific line where you have that warning? The warning should give you the line in the file that triggers it, right? Do you see it?
Cheers!
you define logger argument public function __construct(private LoggerInterface $logger)
and at the same time access class property via $this->logger in findAll() (I'm old Java guy)
so, that's why I'm getting that error and I had to add line
$this->logger = $logger;
to constructor in order to call
$this->logger->info('Starship collection retrieved');
in findAll() method.
Hey Martin,
I'm not sure I understand how you wrote that exactly in code... so if you could share the exact code, just literally copy the code of your constructor and paste in the message - that would help a lot. But if that works for you that way - just go that way, not much important.
Cheers!
is it possible to send you screenshot?
Hey Martin,
Yes, you can upload your screenshot to a public image server like "Imgur" and then link to it in the message.
But I probably know what you mean, do you mean PhpStorm warning? I thought you were talking about PHP warning. If you have a valid syntax, like the one I shared above, and you try it and it works for you in the browser but PhpStorm still shows you some warnings complaining about that syntax and do not consider it valid - then most probably you should specify the exact PHP version in the PhpStorm settings.
Open PhpStorm Settings, and then find "PHP". Set the "PHP language level" parameter to your actual PHP version, which I see should be 8.3. Also, set the "CLI Interpreter" to the actual PHP binary path. As soon as you choose the correct PHP level there - you should not see those warnings about invalid syntax anymore.
Cheers!
oh, my bad ... please forgive me, now I see it... I forgot to write access modifier (private) in front of the LoggerInterface in the case of constructor property promotion. All of a sudden even PHPStorm is happy. Sorry again, I didn't notice it...
Hey Martin,
No problem, I'm happy to hear you were able to find the problem! Yes, that access modifier is required to make the Constructor Property Promotion work, otherwise it will be just a var inside the constructor :)
Cheers!
Hello,
In repository, findAll() function is returning an array. Because of that, when we use our variable in twig, it asks the index number of variable to get data.
my controller is as below:
` #[Route('/tasks')]
here, $tasklist is an array because of repository. repository is as below:
`<?php
namespace App\Repository;
use App\Model\Tasks;
class TaskRepository
{
}
`
so, in twig, when I used
<h3 class="card-title">{{ NumberofTask.taskheader }}</h3>,it gives key error. so instead of that, I used as this line:<h3 class="card-title">{{ NumberofTask[0].taskheader }}</h3>do you think I should continue like this or shall ı change returned array of Tasks as obje with any method? If it is, could you please share me which can be used ? In video I see that after array_rand() is used, it is changed to object from array.
Hey @Mahmut-A,
This feels a bit awkward to me. Are you listing all the tasks in this twig template? If so, using a
forwould be better:thank you so much. I was using for loop in different way. your way is easiest:)
Hi,
yes there is task list file in behind, long data file.
so each task is listed to page.
Hi ,
In the code challenge: https://symfonycasts.com/screencast/symfony/create-service/activity/763
Answer:
But "static method" - What do you mean?
public static function(){}? Or Method injection?BR.
Ruslan.
Hey Ruslan,
Yeah, about the "static method" we mean something like
public static function methodName()declaration, but that option is incorrect anyway so you had to discard it :)Method injection is meant with another option which is "By adding it as a method argument inside the controller". So in the "By declaring it as a static method in the controller" option we didn't mean method injection.
But yeah, I see what you mean... we will try to improve that option, thanks!
Cheers!
Thanks for your course.
Hey Ruslan,
You're welcome! Our challenges are polished and reviewed with a few people, so they should be correct in most cases, but if you ever notice any issues with them - feel free to let us know! Sometimes it's easy to miss something due to human factor :)
Cheers!
my
dd($repository->findAll());prints:but
$this->json($repository->findAll())prints[{},{},{}]in the browser. Any idea what could be wrong?This is repo function
my controller
Hey @Krishnadas-Pc
Do you have the Symfony Serializer installed in your project? You need something that knows how to transform Starship objects into an array
Cheers!
Did you explained adding model class in the previous videos?
Hey @Krishnadas-Pc
Yes, a couple of chapters ago.
Here: https://symfonycasts.com/screencast/symfony/json-api
Cheers!
Thanks for the quick reply. I might have missed it but able to add it my own.
Hi,
I am using a service in my environment. This service is starting a CLI command and RESPONSE is occuring in 10-12 minutes. After RESPONSE is generated, user is able to see the response in json format with the aid of controller which I did in controller. It is actually an API .
But, I need a small help. In random time, we should be able to check this RESPONSE without creating again and again. Something will check if RESPONSE is occured or not. If there is not RESPONSE then ok, it is turning icon etc. If RESPONSE exits, we should be able to fetch RESPONSE data, for example only STATUS 200.
Since my below service is working properly and creating RESPONSE, I thought that I can use this RESPONSE in another service. and new api request will trigger this new service, if there is RESPONSE it is ok, if not, it is not ok etc.
But I could not find to way, how to use this RESPONSE in other service.
Do you suggest anything about this problem for me?
thank you
`public function execDeployCommand($fromButton)
{
}
`
Hey @Mahmut-A
Perhaps there are fancy ways to handle this case, but a quick thing to try that comes to mind is to create something like a
ReportStatusentity, where you track the status of your process. Then, in another service, you check for it and print on what status it currently isCheers!
hi
I want to contribute that I found a way to write file the status as below. so another api in controller can able to check this status.
I believe and ı learned from a master:) that ı can also use mam-cache
if ($fromButton === $topology->getActionDeploy()) {
Great! Thank you for sharing it
Hi
I have two questions for these episodes:
/_profiler gives below log. I installed php8.2-intl in ubuntu. but it is same. shall ı reboot the all system? do you have any clue for this?
5:53:47.000 PM<br />deprecation Please install the "intl" PHP extension for best performance.<br />Show context Show traceIn StarshipRepository class, we defined __construct function with parameter $logger. when we use this $logger variable under findAll() function, we used
->logger(without $). maybe this is basic question but I am new for this php and symfony. Also, in StarshipApiController, we called $repository as it is. like$repository->findAll()(with $).is it because of that since we use $logger in different function or normal behavior to use without $?
Hey Mahmut,
symfony check:reqcommand to see if you need more changes in your setup for this Symfony app.private LoggerInterface $loggerinstead of doing it manually. And so, because that's the class property - you call it via$this->loggeras you do for class properties. But about$repository- that's just a method argument, i.e. a simple variable, not a class property, and so you call all variables via$.Cheers!
Hi, until the minute 1.42 does not work for me. i am getting this error:
Cannot resolve argument $respository of "App\Controller\StarshipApiController::getCollection()": Cannot determine controller argument for "App\Controller\StarshipApiController::getCollection()": the $respository argument is type-hinted with the non-existent class or interface: "App\Repository\StarshipsRepository".can you help with that?
Hey @Luis-HG
Double-check if the repository exists in the
src/Repositoryfolder. Oh, by the way, the name isStarshipRepository(singular) without ansCheers!
~/symfony7/starshop (master)$ tree src
src
├── Controller
│ ├── MainController.php
│ └── StarshipApiController.php
├── Kernel.php
├── Model
│ └── Starship.php
└── Repository
Cool, so the repository is there. The error is the extra "s" you have on its name
public function getCollection(LoggerInterface $logger, StarshipRepository $repository)Cheers!
"Houston: no signs of life"
Start the conversation!