If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login Subscribe¡Equipo del último capítulo! ¡Vamos a hacerlo!
Vale, ¿y si necesitamos un servicio desde dentro de nuestro comando? Por ejemplo, digamos que queremos utilizar MixRepository
para imprimir una recomendación de mezcla de vinilo. ¿Cómo podemos hacerlo?
Bueno, estamos dentro de un servicio y necesitamos acceder a otro servicio, lo que significa que necesitamos... la temida inyección de dependencia. Es broma, no es temible, ¡es fácil con el autocableado!
Añade public function __construct()
con private MixRepository $mixRepository
para crear y establecer esa propiedad de una sola vez.
... lines 1 - 4 | |
use App\Service\MixRepository; | |
... lines 6 - 17 | |
class TalkToMeCommand extends Command | |
{ | |
public function __construct( | |
private MixRepository $mixRepository | |
) | |
{ | |
... line 24 | |
} | |
... lines 26 - 55 | |
} |
Aunque, si pasas el ratón por encima de __construct()
, dice
Falta una llamada al constructor padre.
Para solucionarlo, llama a parent::__construct()
:
... lines 1 - 4 | |
use App\Service\MixRepository; | |
... lines 6 - 17 | |
class TalkToMeCommand extends Command | |
{ | |
public function __construct( | |
private MixRepository $mixRepository | |
) | |
{ | |
parent::__construct(); | |
} | |
... lines 26 - 55 | |
} |
Esta es una situación súper rara en la que la clase base tiene un constructor al que tenemos que llamar. De hecho, es la única situación que se me ocurre en Symfony como ésta... así que normalmente no es algo de lo que debas preocuparte.
Aquí abajo, vamos a dar salida a una recomendación de mezcla... pero hazlo aún más genial preguntando primero al usuario si quiere esta recomendación.
Podemos hacer preguntas interactivas aprovechando el objeto $io
. Diréif ($io->confirm('Do you want a mix recommendation?'))
:
... lines 1 - 17 | |
class TalkToMeCommand extends Command | |
{ | |
... lines 20 - 34 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
... lines 37 - 45 | |
$io->success($message); | |
if ($io->confirm('Do you want a mix recommendation?')) { | |
... lines 49 - 51 | |
} | |
... lines 53 - 54 | |
} | |
} |
Esto hará esa pregunta, y si el usuario responde "sí", devolverá true. El objeto $io
está lleno de cosas geniales como ésta, incluyendo la formulación de preguntas de opción múltiple y el autocompletado de las respuestas. Incluso podemos crear una barra de progreso
Dentro del if, obtén todas las mezclas con$mixes = $this->mixRepository->findAll()
. Luego... sólo necesitamos un poco de código feo - $mix = $mixes[array_rand($mixes)]
- para obtener una mezcla aleatoria.
Imprime la mezcla con un método más $io
$io->note()
pasando porI recommend the mix
y luego introduce $mix['title']
:
... lines 1 - 17 | |
class TalkToMeCommand extends Command | |
{ | |
... lines 20 - 34 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
... lines 37 - 45 | |
$io->success($message); | |
if ($io->confirm('Do you want a mix recommendation?')) { | |
$mixes = $this->mixRepository->findAll(); | |
$mix = $mixes[array_rand($mixes)]; | |
$io->note('I recommend the mix: ' . $mix['title']); | |
} | |
... lines 53 - 54 | |
} | |
} |
Y... ¡listo! Por cierto, ¿te has fijado en este return Command::SUCCESS
? Eso controla el código de salida de tu comando, así que siempre querrás tener Command::SUCCESS
al final de tu comando. Si hubiera un error, podrías return Command::ERROR
.
Bien, ¡probemos esto! Dirígete a tu terminal y ejecuta:
php bin/console app:talk-to-me --yell
Obtenemos la salida... y luego obtenemos:
¿Quieres una recomendación de mezcla?
¡Pues sí, la queremos! ¡Y qué excelente recomendación!
¡Muy bien, equipo! ¡Lo hemos conseguido! ¡Hemos terminado el que creo que es el tutorial de Symfony más importante de todos los tiempos! No importa lo que necesites construir en Symfony, los conceptos que acabamos de aprender serán la base para hacerlo.
Por ejemplo, si necesitas añadir una función o un filtro personalizado a Twig, ¡no hay problema! Lo haces creando una clase de extensión de Twig... y puedes usar MakerBundle para generarla por ti o construirla a mano. Es muy similar a la creación de un comando de consola personalizado: en ambos casos, estás construyendo algo para "engancharse" a una parte de Symfony.
Así que, para crear una extensión Twig, debes crear una nueva clase PHP, hacer que implemente cualquier interfaz o clase base que necesiten las extensiones Twig (la documentación te lo dirá)... y luego sólo tienes que rellenar la lógica... que no mostraré aquí.
Y ya está Entre bastidores, tu extensión Twig se vería automáticamente como un servicio, y la autoconfiguración se encargaría de integrarla en Twig... exactamente como el comando de la consola.
En el próximo curso, pondremos en práctica nuestros nuevos superpoderes añadiendo una base de datos a nuestra aplicación para poder cargar datos reales y dinámicos. Y si tienes alguna pregunta real y dinámica, estamos aquí para ti, como siempre, abajo en la sección de comentarios.
Muy bien, amigos. Muchas gracias por codificar conmigo y nos vemos la próxima vez.
Hey Orlando,
Thanks for your feedback! We're really happy to hear you liked this course and that it was helpful for you :)
Cheers!
Hey Ryan,
Thanks for these fun courses in Symfony 6 :)
Regarding the commands, is there a bundle that would allow commands to be run only once? I'm looking for behavior that's similar to doctrine migrations, where you put the migrations in the ./migrations
directory and they get marked as executed in the database. So I would like to do the same for commands and put them in the ./tasks
directory.
Currently I work around this by putting them in a ./src/Commands/Task/Run
directory and naming them TaskYYYYMMDDhhmmss.php
so that the console can "see" them and execute them (and mark it as executed in the database). I'm not sure on how to configure symfony's console in a way that it can pick up the commands from another (custom) directory.
So my question, is there an existing bundle which has this kind of behavior or where/how should I start if I want to configure this myself?
Regards,
Yves
Hey Senet,
That's an interesting question. I found this bundle that may fit your needs https://github.com/zenstruck/schedule-bundle/blob/master/doc/run-schedule.md
In case it does not fit your needs. I think you'll have to implement something similar to what Doctrine migrations do. In a database table, you'll have to keep track of all executed commands, and before executing a command, you'll have to check the table. Your naming strategy sounds good to me but keep in mind that you'll need a CRON job to run on your server every minute or so
For registering commands outside of the default directory, you can take advantage of Symfony's auto-configuration feature. You only need to ensure that the new commands directory is not excluded.
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"knplabs/knp-time-bundle": "^1.18", // v1.19.0
"symfony/asset": "6.1.*", // v6.1.0-RC1
"symfony/console": "6.1.*", // v6.1.0-RC1
"symfony/dotenv": "6.1.*", // v6.1.0-RC1
"symfony/flex": "^2", // v2.1.8
"symfony/framework-bundle": "6.1.*", // v6.1.0-RC1
"symfony/http-client": "6.1.*", // v6.1.0-RC1
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/runtime": "6.1.*", // v6.1.0-RC1
"symfony/twig-bundle": "6.1.*", // v6.1.0-RC1
"symfony/ux-turbo": "^2.0", // v2.1.1
"symfony/webpack-encore-bundle": "^1.13", // v1.14.1
"symfony/yaml": "6.1.*", // v6.1.0-RC1
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.0
},
"require-dev": {
"symfony/debug-bundle": "6.1.*", // v6.1.0-RC1
"symfony/maker-bundle": "^1.41", // v1.42.0
"symfony/stopwatch": "6.1.*", // v6.1.0-RC1
"symfony/web-profiler-bundle": "6.1.*" // v6.1.0-RC1
}
}
Thanks for this course !) I like your way of teaching