gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
We just installed KnpTimeBundle. Hooray! Um... but... uh... what does that mean? What did doing that give us?
The number one thing that a bundle gives us is... services! What services does this bundle give us? Well, we could, of course, read the documentation, blah, blah. Well, ok, you should do that... but, come on! Let's venture ahead recklessly and learn by exploring!
In the last tutorial, we learned about a command that shows us all of the services in our app: debug:autowiring
:
php bin/console debug:autowiring
For example, if we search for "logger", there's apparently a service called LoggerInterface
. We also learned that we can autowire any service in this list into our controller by using its type. By using this LoggerInterface
type - which is actually Psr\Log\LoggerInterface
- Symfony knows to pass us this service. Then, down here, we call methods on it like $logger->info()
.
We installed KnpTimeBundle
a moment ago, so let's search for "time":
php bin/console debug:autowiring time
And... hey! Look at this! We have a new DateTimeFormatter
service! That's from the new bundle and I bet that's what we're looking for. Let's go use it in our controller.
The type-hint we need is Knp\Bundle\TimeBundle\DateTimeFormatter
. Ok! In VinylController
, find browse()
, then add the new argument.
By the way, the order of the arguments does not matter... except when it comes to optional arguments. I made the $slug
argument optional and you typically need your optional arguments at the end of the list. So I'll add DateTimeFormatter
right here and hit "tab" to add the use
statement on top.
We can name the argument anything we want, like $sherlockHolmes
or $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 | |
} |
To use this, loop over the mixes - 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 | |
} |
then, on each, add a new ago
key: $mixes[$key]['ago'] =
... and this is where we need the new service. How do we use the DateTimeFormatter
? I have no idea! But we used its type, so PhpStorm should tell us what methods it has. Type $timeFormatter->
... and ok! It has 4 public methods.
The one we want is formatDiff()
. Pass it the "from" time... which is $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 | |
} |
That's all we need! We're looping over these $mixes
, taking the createdAt
key, which is a DateTime
object, passing it to the formatDiff()
method, which should return a string in the "ago" format. To see if this is working, below, 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 | |
} |
Let's try it! Spin over, refresh... and let's open it up. Yes! Look at that: "ago" => "7 months ago"
... "ago" => "18 days ago"
... It works. So remove that dump:
... 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 | |
} |
And now that each mix has a new ago
field, in browse.html.twig
, replace the mix.createdAt|date
code with 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 |
And now... much better.
So: we had a problem... and knew it needed to be solved by a service... because services do work. We didn't have a service that did what we needed yet, so we went out, found one, and installed it. Problem solved! Symfony itself has a ton of different packages, and each of them gives us several services. But sometimes you'll need a third party bundle like this one to get the job done. Typically, you can just search online for the problem you're trying to solve, plus "Symfony bundle", to find it.
In addition to the nice DateTimeFormatter
service that we just used, this bundle also gave us another service. But, this isn't a service that we're meant to use directly, like in the controller. Nope! This service is meant to be used by Twig itself... to power a brand new Twig filter! That's right! You can add custom functions, filters... or anything to Twig.
To see the new filter, let's try another useful debugging command:
php bin/console debug:twig
This prints a list of all of the functions, filters, and tests in Twig, along with the one global Twig variable we have. If you go up to Filters, there's a new one called "ago"! That was not there before we installed KnpTimeBundle
.
So, all of the work we did in our controller is perfectly fine ... but it turns out that there's an easier way to do all of this. Delete the foreach
... remove the DateTimeFormatter
service... and, though it's optional, clean up the extra use
statement on top:
... 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 | |
} |
In browse.html.twig
, we don't have an ago
field anymore... but we still have a createdAt
field. Instead of piping this into the date
filter, pipe it to 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 |
That's all we need! Back over on the site refresh and... we get the exact same result.
By the way, we won't do it in this tutorial, but by the end, you'll be able to easily follow the documentation to create your own custom Twig functions and filters.
Ok, so our app does not have a database yet... and it won't until the next episode. But to make things more interesting, let's get our mixes data by making an HTTP call to a special GitHub repository. That's next.
// 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
}
}
Hey,
I get at using this bundle the exception ClassNotFoundError in the file mixed_vinyl\vendor\symfony\translation\LocaleSwitcher.php at line 37.