gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Acabamos de instalar KnpTimeBundle. ¡Hurra! Pero... eh... ¿qué significa eso? ¿Qué nos da eso?
Lo primero que nos da un bundle son... ¡servicios! ¿Qué servicios nos da este bundle? Bueno, podríamos, por supuesto, leer la documentación, bla, bla. Bueno, vale, deberías hacerlo... pero, ¡vamos! ¡Aventurémonos con temeridad y aprendamos explorando!
En el último tutorial, conocimos un comando que nos muestra todos los servicios de nuestra aplicación: debug:autowiring
:
php bin/console debug:autowiring
Por ejemplo, si buscamos "logger", parece que hay un servicio llamadoLoggerInterface
. También aprendimos que podemos autoconectar cualquier servicio de esta lista en nuestro controlador utilizando su tipo. Usando este tipo LoggerInterface
-que en realidad es Psr\Log\LoggerInterface
- Symfony sabe que debe pasarnos este servicio. Luego, aquí abajo, llamamos a métodos sobre él como $logger->info()
.
Hemos instalado KnpTimeBundle
hace un momento, así que busquemos "tiempo":
php bin/console debug:autowiring time
Y... ¡eh! ¡Mira esto! ¡Tenemos un nuevo servicio DateTimeFormatter
! Es del nuevo bundle y seguro que es lo que buscamos. Vamos a utilizarlo en nuestro controlador.
La pista de tipo que necesitamos es Knp\Bundle\TimeBundle\DateTimeFormatter
. De acuerdo EnVinylController
, busca browse()
, y añade el nuevo argumento.
Por cierto, el orden de los argumentos no importa... excepto cuando se trata de argumentos opcionales. He hecho que el argumento $slug
sea opcional y normalmente necesitas tus argumentos opcionales al final de la lista. Así que añadiré DateTimeFormatter
justo aquí y pulsaré "tab" para añadir la declaración use
en la parte superior.
Podemos nombrar el argumento como queramos, como $sherlockHolmes
o$timeFormatter
:
... lines 1 - 4 | |
use Knp\Bundle\TimeBundle\DateTimeFormatter; | |
... lines 6 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(DateTimeFormatter $timeFormatter, string $slug = null): Response | |
{ | |
... lines 34 - 45 | |
} | |
... lines 47 - 71 | |
} |
Para usar esto, haz un bucle sobre las mezclas - foreach ($mixes as $key => $mix)
... lines 1 - 4 | |
use Knp\Bundle\TimeBundle\DateTimeFormatter; | |
... lines 6 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(DateTimeFormatter $timeFormatter, string $slug = null): Response | |
{ | |
... lines 34 - 36 | |
foreach ($mixes as $key => $mix) { | |
... line 38 | |
} | |
... lines 40 - 45 | |
} | |
... lines 47 - 71 | |
} |
luego, en cada una, añade una nueva clave ago
: $mixes[$key]['ago'] =
... y aquí es donde necesitamos el nuevo servicio. ¿Cómo utilizamos el DateTimeFormatter
? ¡No tengo ni idea! Pero hemos utilizado su tipo, así que PhpStorm debería decirnos qué métodos tiene. Escribe$timeFormatter->
... y ¡bien! Tiene 4 métodos públicos.
El que queremos es formatDiff()
. Pásale el tiempo "desde"... que es$mix['createdAt']
:
... lines 1 - 4 | |
use Knp\Bundle\TimeBundle\DateTimeFormatter; | |
... lines 6 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(DateTimeFormatter $timeFormatter, string $slug = null): Response | |
{ | |
... lines 34 - 36 | |
foreach ($mixes as $key => $mix) { | |
$mixes[$key]['ago'] = $timeFormatter->formatDiff($mix['createdAt']); | |
} | |
... lines 40 - 45 | |
} | |
... lines 47 - 71 | |
} |
¡Eso es todo lo que necesitamos! Estamos haciendo un bucle sobre estos $mixes
, tomando la clave createdAt
, que es un objeto DateTime
, pasándolo al método formatDiff()
, que debería devolver una cadena con el formato "ago". Para ver si esto funciona, a continuación,dd($mixes)
:
... lines 1 - 4 | |
use Knp\Bundle\TimeBundle\DateTimeFormatter; | |
... lines 6 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(DateTimeFormatter $timeFormatter, string $slug = null): Response | |
{ | |
... lines 34 - 36 | |
foreach ($mixes as $key => $mix) { | |
$mixes[$key]['ago'] = $timeFormatter->formatDiff($mix['createdAt']); | |
} | |
dd($mixes); | |
... lines 41 - 45 | |
} | |
... lines 47 - 71 | |
} |
¡Vamos a probarlo! Gira, refresca... y abramos. ¡Sí! Mira esto: "ago" => "7 months ago"
... "ago" => "18 days ago"
... Funciona. Así que elimina ese volcado:
... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
... lines 13 - 31 | |
public function browse(DateTimeFormatter $timeFormatter, string $slug = null): Response | |
{ | |
... lines 34 - 36 | |
foreach ($mixes as $key => $mix) { | |
$mixes[$key]['ago'] = $timeFormatter->formatDiff($mix['createdAt']); | |
} | |
return $this->render('vinyl/browse.html.twig', [ | |
... lines 42 - 43 | |
]); | |
} | |
... lines 46 - 70 | |
} |
Y ahora que cada mezcla tiene un nuevo campo ago
, en browse.html.twig
, sustituye el códigomix.createdAt|date
por mix.ago
:
... lines 1 - 3 | |
<div class="container"> | |
... lines 5 - 25 | |
<div> | |
<h2 class="mt-5">Mixes</h2> | |
<div class="row"> | |
{% for mix in mixes %} | |
<div class="col col-md-4"> | |
<div class="mixed-vinyl-container p-3 text-center"> | |
... lines 32 - 35 | |
<span>{{ mix.genre }}</span> | |
| | |
<span>{{ mix.ago }}</span> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
</div> | |
... lines 45 - 46 |
Y ahora... mucho mejor.
Así que: teníamos un problema... y sabíamos que había que resolverlo con un servicio... porque los servicios sí funcionan. Todavía no teníamos un servicio que hiciera lo que necesitábamos, así que salimos, encontramos uno y lo instalamos. ¡Problema resuelto! El propio Symfony tiene un montón de paquetes diferentes, y cada uno de ellos nos proporciona varios servicios. Pero a veces necesitarás un bundle de terceros como éste para hacer el trabajo. Normalmente, puedes buscar en Internet el problema que intentas resolver, más "Symfony bundle", para encontrarlo.
Además del bonito servicio DateTimeFormatter
que acabamos de utilizar, este bundle también nos proporcionó otro servicio. Pero no es un servicio que debamos utilizar directamente, como en el controlador. No Este servicio está destinado a ser utilizado por el propio Twig... ¡para alimentar un nuevo filtro Twig! ¡Así es! Puedes añadir funciones personalizadas, filtros... o cualquier cosa a Twig.
Para ver el nuevo filtro, vamos a probar otro útil comando de depuración:
php bin/console debug:twig
Esto imprime una lista de todas las funciones, filtros y pruebas de Twig, junto con la única variable global de Twig que tenemos. Si subes a Filtros, ¡hay uno nuevo llamado "hace"! Eso no estaba allí antes de que instaláramos KnpTimeBundle
.
Así que todo el trabajo que hicimos en nuestro controlador está perfectamente bien ... pero resulta que hay una manera más fácil de hacer todo esto. Elimina el foreach
... elimina el servicio DateTimeFormatter
... y, aunque es opcional, limpia la declaración extra use
de la parte superior:
... lines 1 - 9 | |
class VinylController extends AbstractController | |
{ | |
... lines 12 - 29 | |
'/browse/{slug}', name: 'app_browse') ( | |
public function browse(string $slug = null): Response | |
{ | |
$genre = $slug ? u(str_replace('-', ' ', $slug))->title(true) : null; | |
$mixes = $this->getMixes(); | |
return $this->render('vinyl/browse.html.twig', [ | |
'genre' => $genre, | |
'mixes' => $mixes, | |
]); | |
} | |
... lines 41 - 65 | |
} |
En browse.html.twig
, ya no tenemos un campo ago
... pero seguimos teniendo un campocreatedAt
. En lugar de canalizarlo en el filtro date
, canalízalo en ago
:
... lines 1 - 3 | |
<div class="container"> | |
... lines 5 - 25 | |
<div> | |
<h2 class="mt-5">Mixes</h2> | |
<div class="row"> | |
{% for mix in mixes %} | |
<div class="col col-md-4"> | |
<div class="mixed-vinyl-container p-3 text-center"> | |
... lines 32 - 35 | |
<span>{{ mix.genre }}</span> | |
| | |
<span>{{ mix.createdAt|ago }}</span> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
</div> | |
... lines 45 - 46 |
¡Eso es todo lo que necesitamos! Volvemos a la actualización del sitio y... obtenemos exactamente el mismo resultado.
Por cierto, no lo haremos en este tutorial, pero al final, podrás seguir fácilmente la documentación para crear tus propias funciones y filtros Twig personalizados.
Vale, nuestra aplicación aún no tiene una base de datos... y no la tendrá hasta el próximo episodio. Pero para hacer las cosas más interesantes, vamos a obtener los datos de nuestras mezclas haciendo una llamada HTTP a un repositorio especial de GitHub. Eso a continuación.
Hey Francois,
Well, almost :) In the 2nd case if the browse()
method will be called without arguments - the $timeFormatter
will be, by default, set to new DateTimeFormatter
while in the first case, it will throw because the 1st argument is required there :) So, it's just the default value for the first argument in your 2nd example.
And so, the second case should be written as:
public function browse(DateTimeFormatter $timeFormatter = new DateTimeFormatter, string $slug = null): Response
In case you want to allow only DateTimeFormatter
instances as the 1st argument.
Cheers!
My original concern is that the $timeFormatter
function argument seems to pop out of nowhere (i.e. I do not see such kind of variable out there).
I will try to recap my thoughts here:
DateTimeFormatter
class is a service, i.e., it has no propertyHey Francois,
Then I'm still not sure I understand you. I suppose you wrote that $timeFormatter = new DateTimeFormatter
in your method signature, no? Because I don't see it in this video.
the DateTimeFormatter class is a service, i.e., it has no property
Great, then the Symfony Dependency Injection Container will create and inject it for you. But still don't see what you mean about "has no property".
does it mean that its methods can be called without instantiating the class (similarly to one can do with a static class)?
Static methods can be called without instantiating a class because they are static. For normal methods - you can call them only from an instance of a class of course.
I hope it helps!
Cheers!
I looked at the documentation for Dependency Injection. If I understand well, this is not a base level PHP behavior, but something extra built on top of PHP. Services use dependency injection.
Is this right?
François
Hey Francois,
Yes, sure! you may think of dependency injection (DI) and dependency injection container (DIC) as patterns. And yes, it's not a native PHP function but a behavior implemented in a Symfony component.
Cheers!
Hey,
I get at using this bundle the exception ClassNotFoundError in the file mixed_vinyl\vendor\symfony\translation\LocaleSwitcher.php at line 37.
Hey MolloKhan,
thanks for your reply.
No, i hadn't installed this extension. After installing it in the php.ini and restarting the local web server it works. Thank you.
// 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
}
}
I have a question about
Is it the same as