Login to bookmark this video
Buy Access to Course
25.

Warmup Command Configuration

|

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 warmup command class created, now we need to configure it as a service. In your apps, this would be done automatically because of the AsCommand attribute, but in bundles, like any other service, we need to configure it manually.

Open our bundle's services.php file. Add it as a new service with ->set('.symfonycasts.object_translator.warmup_command'). Class: ObjectTranslationWarmupCommand.

Add some args(): First is the object translator service, so write service() and copy-paste that service ID. Next is the mapping manager service(). Copy-paste that service ID as well. The next service() is the locale switcher, it's the same service we used above, so copy-paste that.

The last argument is the enabled locales, write param('kernel.enabled_locales').

Finally, mark this service as a console command with ->tag('console.command').

Let's test this command out in the terminal. Run:

symfony console object-translation:warmup -v

The -v will give us a stack trace of any errors we encounter.

And... we do have an error. Well a warning actually: "Undefined array key 0". Look at the first line of the stack trace, it's pointing us to line 23 of TranslatableMappingManager.

Undefined Array Key Fix

Check out that class and find the line.

It's subtle but, we're trying to use the null safe operator on an array. It doesn't protect us from undefined keys. To fix this, wrap this in brackets, and add ?? null.

Try the command again:

symfony console object-translation:warmup -v

Cool! That worked!

But there's actually an even more subtle bug that can pop up here...

Clear the cache with no warm-up:

symfony console cache:clear --no-warmup

Now run the warmup command again:

symfony console object-translation:warmup -v

Hmm... "Class Proxies__CG__\App\Entity\Category is not translatable." What's the deal with this class name?

Accounting for Doctrine Proxies

What's happening is that Doctrine generates a proxy class that extends your entity class behind the scenes. In this case, App\Entity\Category. This proxy class is what enables Doctrine to do it's magic.

In TranslatableMappingManager, the passed object is sometimes one of these proxies. And that proxy class doesn't have the Translatable attribute, that's the problem.

Remember, the proxy class extends our entity class. We can use reflection to get the parent class if we detect that it's a proxy.

After creating the ReflectionClass, write if ($class->implementsInterface(Proxy::class)). Import the class from Doctrine\Persistence. All generated proxies have this interface.

Inside, write $class = $class->getParentClass() to get the true entity class.

This should fix the issue but confirm by running the cache clear with no warm-up again:

symfony console cache:clear --no-warmup

Then run the warm-up command again:

symfony console object-translation:warmup -v

Nice! No errors this time, and our progress bar shows 12 entities being processed. This is some stylish output!

Forcing Cache Recalculation

Now, there's just one last thing we need to do. Currently, when we're warming up the cache, if a translation is already cached, it won't be recalculated. Currently, this command is only really useful to warm an empty cache.

We need to force the recalculation of translations, even if they're already cached.

Symfony Cache Contracts has our back!

In ObjectTranslator::translationsFor(), dig into the cache get() method. The one on CacheInterface. See this float $beta parameter? Check out it's docblock. "A float, that as it grows, controls the likeliness of triggering early expiration. 0 disables it, INF forces immediate expiration."

This feature helps with cache stampedes, if a bunch of items expire at the same time, there's a probability that some will expire early to help spread out the load.

For our purpose, we can pass infinity to force immediate expiration.

Back in ObjectTranslator::translationsFor(), add a new parameter: bool $forceRefresh = false. Now, for the third argument of cache->get(), pass $forceRefresh ? \INF : null. If true, use infinity as the $beta to force expiration, otherwise, pass null to use the default behavior.

Up in translate(), PhpStorm is complaining that we need to pass this new parameter. Add a third argument to the method: array $options = []. We could have used a dedicated parameter for this, but using an options array will make it easier to add more options in the future.

Expand the translationsFor() call and for the third argument: $options['force_refresh'] ?? false.

Finally, back in our warm-up command, in the innermost loop where we're calling translate(), add a third argument: ['force_refresh' => true].

Over in the terminal, run the warm-up command again:

symfony console object-translation:warmup -v

No errors, that's a good sign!

If you want to test that the refresh is actually happening, you can change some translations in the database, then run the command. You should see these changes reflected when refreshing the page - and no new database queries.

Up next, we'll add two more commands we need for a 1.0 release!