Login to bookmark this video
Buy Access to Course
19.

`translate_object` Twig Filter

|

Share this awesome video!

|

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

This Chapter isn't quite ready...

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com

We have our ObjectTranslator service fully working. It can be injected and used in PHP code like we're doing in ArticleController::show(). Now, let's spice things up a bit and create a Twig filter for it.

Twig Extension Class

In object-translation-bundle/src, create a new directory called Twig. Inside, a new PHP class named ObjectTranslatorExtension.

Mark it as final and add a class-level docblock. In it, write @internal. This is another trick to let users know this class isn't meant to be used directly in their apps. Shh... it's a secret!

Next, have it extend AbstractExtension from Twig:

// ... lines 1 - 7
/**
* @internal
*/
final class ObjectTranslatorExtension extends AbstractExtension
{
// ... lines 13 - 18
}

Now, override a single method: getFilters(), and mark the return type as array. Inside, return an array with a single element: new TwigFilter(). The first argument is the name of our filter, translate_object:

// ... lines 1 - 10
final class ObjectTranslatorExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('translate_object'),
];
}
}

Wiring Up the Extension

Time to wire this baby up! In your bundle's services.php, below the alias(), define a new service with ->set('symfonycasts.object_translator.twig_extension'). Now, because this is an internal service we don't want users to see, we can mark it as hidden by prefixing the service ID with .. I'll show you later how this affects things.

The second argument is the class name: ObjectTranslatorExtension::class:

// ... lines 1 - 7
return static function (ContainerConfigurator $container): void {
$container->services()
// ... lines 10 - 20
->set('.symfonycasts.object_translator.twig_extension', ObjectTranslatorExtension::class)
// ... line 22
;
};

Check this out, PhpStorm formats the class name with a strikethrough. This is because of that @internal flag. It's detecting we're trying to use it outside our bundle's src directory. In this case, it's a false positive - we really need to use it here. But in a user's app, this strikethrough would hopefully discourage them from using it directly.

Finally, tag it as a twig.extension with ->tag('twig.extension'):

// ... lines 1 - 7
return static function (ContainerConfigurator $container): void {
$container->services()
// ... lines 10 - 20
->set('.symfonycasts.object_translator.twig_extension', ObjectTranslatorExtension::class)
->tag('twig.extension')
;
};

Extension Code

Back to ObjectTranslatorExtension. This Twig filter isn't doing anything yet. The second argument to TwigFilter is a callable that does the work. We could inject our ObjectTranslator into a constructor and call its translate() method here but... this isn't the best idea. Every Twig extension is loaded whenever Twig is loaded. Even if we're not using this filter, it would still instantiate this object and our ObjectTranslator as well. We want to avoid that, and make it lazy. Twig Runtimes to the rescue!

Twig Runtime

For the second argument, after translate_object, create an array: [ObjectTranslator::class, 'translate']:

// ... lines 1 - 11
final class ObjectTranslatorExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('translate_object', [ObjectTranslator::class, 'translate']),
];
}
}

This isn't a true callable since translate() isn't static. But Twig understands ObjectTranslator might be a runtime and will lazily load this class and call translate() on it when the filter is used.

We just need to mark ObjectTranslator as a Twig runtime. Over in services.php, under the first service, our object translator, add a tag: ->tag('twig.runtime'):

// ... lines 1 - 7
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
// ... lines 11 - 16
->tag('twig.runtime')
// ... lines 18 - 22
;
};

Now, if you've written Twig runtimes before, you might have created a dedicated class for it. This isn't strictly required! You can make any service as a runtime with this tag. Then, in your extensions, you can reference it like we did here.

Using our Twig Filter

Let's give it a whirl! Our article show page is already translating object fine, so let's use the filter in the index - where we list all articles. Open templates/article/index.html.twig.

Inside the articles loop, at the very top, override the article with the translated version: {% set article = article|translate_object %}:

89 lines | templates/article/index.html.twig
// ... lines 1 - 2
{% block body %}
// ... line 4
<div class="">
// ... line 6
<div>
{% for article in articles %}
{% set article = article|translate_object %}
// ... lines 10 - 25
{% endfor %}
</div>
// ... lines 28 - 86
</div>
{% endblock %}

Jump over to the browser and refresh. We're on the English homepage, so switch to French...

Voila! The first article title and content are in French. Our Twig filter is working! Of course, the other articles are still in English since we haven't set up translations for them yet.

Understanding Hidden Services

Let's take a quick detour to understand how hidden services, the ones prefixed with . work. At your terminal, run:

symfony console debug:container symfonycasts

Ok, we see two services, symfonycasts.object_translator and ObjectTranslator as a class - this is our alias. We don't see our Twig extension service because it's hidden. Hidden doesn't mean "not there", or even "not usable", they're just excluded by default when listing services. Run the command again, but add --show-hidden:

symfony console debug:container symfonycasts --show-hidden

There we go! The first entry is our hidden Twig extension service.

Next, let's work on some performance optimizations to keep database queries under control.