This course is still being released! Check back later for more chapters.

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
Login to bookmark this video
Buy Access to Course
05.

"Wiring Up" our Bundle Service

|

Share this awesome video!

|

Keep on Learning!

We created the class we want to use as a service in our bundle, but when we try to inject it, we get this "cannot autowire argument..." error.

In your end apps, you might be used to this just working. That's because service autowiring is enabled by default for all classes in your app's src directory.

Bundles, however, are a different story. If you've been using Symfony as long as I have, you might remember that in the old days, autowiring wasn't a thing! You had to manually define every... single... service in a YAML or XML file. It was rough...

Luckily, for apps, this is no longer the case (for the most part). But you still have to do it for bundles. This is because bundles are meant to be reusable, and you want to give the end user the most flexibility possible.

There are still some improvements in this department though.

Creating a Services File

Remember we created that empty config directory in our bundle? This is where we define our bundle's services. In it, create a regular PHP file (not a class): services.php.

If you've written bundles in the past, you might have used XML for defining services. This was the previous best practice. But nowadays, using PHP is preferred. In your end-apps, if you do need to define services manually, YAML is likely what you use. There's nothing stopping you from using YAML here, but this would require your bundle to depend on the YAML component. So, PHP it is!

First, add namespace Symfony\Component\DependencyInjection\Loader\Configurator:

// ... lines 1 - 2
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
// ... lines 4 - 8

This'll let us use the service definition helper classes and functions we require without the need to import each one.

Next, return static function (ContainerConfigurator $container):

// ... lines 1 - 4
return static function (ContainerConfigurator $container): void {
};

Inside, write $container->services(). We'll chain our service definitions off this:

// ... lines 1 - 4
return static function (ContainerConfigurator $container): void {
};

Defining the Bundle's Services

Now comes the fun part! Add ->set() - the first argument is the service ID. In your apps, this is usually just the class name, but in bundles, we use a plain string - again, for maximum flexibility. This ID needs to be unique, so prefix it with a namespace that makes sense for your bundle. Here, we'll use symfonycasts.. Now the name of our service: object_translator. The second argument is the fully qualified class name: ObjectTranslator::class (make sure you import it):

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
;
};

Letting Symfony Know About the Service

We now have this... sort of... random services.php file in our bundle, so we need to tell Symfony about it!

In ObjectTranslationBundle, override the loadExtension() method from AbstractBundle. This method is called when the bundle is loaded by Symfony.

The parent method is empty, so we can remove this call. Import our services.php file using $container->import(). The path is relative to our current file, so write ../config/services.php:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
;
};

Is this all we need? Let's see. Jump back to the browser and refresh the error page. Hmm, the same error. But now we have more details: "You should maybe alias this class to the existing symfonycasts.object_translator service."

This is still progress - since Symfony is suggesting the service ID, that means it knows about it!

Alias our Service

In ArticleController::show(), where we're trying to inject ObjectTranslator, we could add the #[Autowire] attribute with the service ID... This would work... but we can do better! I want this service to be autowire-able. We do this by setting the class name as a service alias.

In services.php, below set(), write ->alias(). The first argument is the alias we want to create: ObjectTranslator::class. The second argument is the service ID we defined earlier: symfonycasts.object_translator:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
// ... lines 9 - 10
->alias(ObjectTranslator::class, 'symfonycasts.object_translator')
;
};

Back in the browser, refresh. And... still an error - but a different one. "Too few arguments passed to ObjectTranslator".

Defining Service Arguments

Remember, in ObjectTranslator, we have two required dependencies: LocaleAwareInterface, a service, and $defaultLocale, a container parameter. We need to tell Symfony how to supply these.

I know we can autowire LocaleAwareInterface, but in bundles, we need to manually configure it, so we need it's service ID. To find this, at your terminal, run:

symfony console debug:autowiring LocaleAware

We don't need the full name, just the first part should be enough. Perfect! Here it is: translation.locale_switcher. Copy that.

Back in services.php, right below set(), indent to keep this organized, and write ->args() with an array. These elements match the order of the constructor arguments, so the first argument is the service. Use the service() function and paste the service ID we just found:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
->args([
service('translation.locale_switcher'),
// ... line 12
])
// ... lines 14 - 15
;
};

Now for the second argument, $defaultLocale. This is a container parameter. We can list all parameters in the terminal by running:

symfony console debug:container --parameters

Big list... filter it by running the same command but with | grep locale at the end:

symfony console debug:container --parameters | grep locale

kernel.default_locale is what we're looking for! Copy that.

Back in services.php, for the second args array element, use the param() function and... paste:

// ... lines 1 - 6
return static function (ContainerConfigurator $container): void {
$container->services()
->set('symfonycasts.object_translator', ObjectTranslator::class)
->args([
// ... line 11
param('kernel.default_locale'),
])
// ... lines 14 - 15
;
};

Go back to our browser... refresh... and success! No error means the service is correctly defined and injected!

Next, we'll look at how our bundle will store translations.