Understanding Symfony Bundle Configuration and Service Container

Share this article

In this post we’ll cover different ways on how to configure Bundles in Symfony2 and how the dependency injection container works with the configuration. The Bundle configuration and Symfony dependency injection container (also known as service container) can be difficult concepts to grasp when first starting development with Symfony2, especially if dependency injection is not a familiar concept beforehand. Bundle configuration can also be a little bit confusing, since there are multiple ways to do it and the best approach depends on the situation. All of the configuration examples in this post are in YAML. Symfony also supports other configuration formats (XML and PHP array) and they are valid options. I am used to working with YAML because I think it’s more readable than XML, but you do get the benefit of schema validation when using XML. The choice of the configuration format is up to you (or your project team) and there is no right or wrong option here. Just use the one you feel most comfortable with.

Bundle creation

A Bundle is a directory containing a set of files (PHP files, stylesheets, JavaScripts, images, …) that implement a single feature (a blog, a forum, etc). In Symfony2, (almost) everything lives inside a bundle.
or, in other words, from the docs
A bundle is similar to a plugin in other software, but even better. The key difference is that everything is a bundle in Symfony2, including both the core framework functionality and the code written for your application. Bundles are first-class citizens in Symfony2. This gives you the flexibility to use pre-built features packaged in third-party bundles or to distribute your own bundles. It makes it easy to pick and choose which features to enable in your application and to optimize them the way you want.
When you create a new bundle, either by auto-generating it (php app/console generate:bundle --namespace=Acme/TestBundle) or manually, you need the BundleNameBundle.php file at the root directory of the bundle. The class in this file, while mostly empty, extends the Symfony core Symfony\Component\HttpKernel\Bundle\Bundle class and it is what you need to register in the AppKernel registerBundles method. When the kernel boots, it creates instances of each bundle and loads the container extension of each bundle (using methods in the parent Bundle class). The container extension is the class in BundleNameExtension.php file inside the DependencyInjection folder of the bundle. The container extension class loads and manages the bundle configuration. The whole extension class is optional and the bundle will work without it, but it’s good to know how things work to better understand the configuration system.

Loading the bundle configuration, the easy way

As I stated above, the extension class is optional and there are other ways to configure a bundle. The simplest form of bundle configuration is to configure the parameters and services inside the application’s main configuration file (app/config/config.yml). This is a totally valid option, although you need to understand the implications of this to better understand when this really is appropriate. Having bundle configuration in the main config file makes your bundle tightly coupled to the current application, and therefore not very portable. This might be okay, if the bundle doesn’t have many services or parameters and you know it will be used only in the current application. This said, I really don’t recommend using this approach even then, because things tend to change and more configuration might be needed later on and other developers will probably look for the configuration inside the bundle. Another simple method is to have a separate configuration file inside the bundle (Resources/config/services.yml for example) and import it in the main configuration. At the beginning of config.yml there are usually some imports already, so all you need to do is add one more import to the list that points to your bundle configuration file. Note, the path is relative to the main config file. Here is a small example:
imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: ../../src/Cvuorinen/ExampleBundle/Resources/config/services.yml }

Loading bundle configuration, the semantic way

As noted above, the extension class handles loading the bundle configuration. It creates an instance of a Configuration class, from the Configuration.php file also located in the DependencyInjection folder. The Configuration class is used to validate and process the main configuration (from config files in app/config/) related to the bundle. If there is no configuration related to your bundle in the main config, the Configuration class can be left as is (more on this after the next paragraph). After processing the main configuration, the extension class loads the bundle specific configuration from Resources/config folder of the bundle using a loader appropriate for the config file type (YamlFileLoader
when using YAML). Any services defined in the bundles configuration file can use the parameters from the applications main config file (and global parameters) and will be added to the service container. After this the services are available for your controllers, CLI commands or any other “container aware” part of the application. You just need to call the get method of the container with the service name specified in the service configuration. The real power of the semantic configuration comes from the way it can be used to process, merge and validate configuration options from the application’s main configuration and pass those on to the bundles service configuration. This is especially useful when creating bundles that are intended for wider audiences, like open source bundles, but might also be good for in-house distribution inside companies. This way you can provide a few simple and well documented configuration options (with default values where appropriate), that users of the bundle can set in the main configuration file and then the bundles extension class can process and validate them so that the user of the bundle does not need to touch any configuration inside the bundle.

Loading bundle configuration, the right way?

Of course, there is no one right way to do it. But most of the time, the easy way is not the way to go since the whole idea of bundles is to make them reusable across different applications. And it’s also quite easy to auto-generate all the required files for the semantic configuration using the Symfony CLI command:
$ app/console generate:bundle
When first starting development with Symfony, I would say go with the easy way or the (auto-generated) semantic way without any configuration processing. When you get more familiar with the bundle system and start creating bundles that you plan on distributing to other people, then learn more about the validation and merging of the main configuration.

Configuration file structure

Ok, now that we know how to load a configuration file, let’s figure out what to put in there. The configuration consists mainly of two types of information, parameters and services. Parameters are any static values that your bundle requires. Things like login credentials, API keys, host names or URLs of external services, you probably get the idea. Parameters can be any scalar values, (e.g. strings, numbers, booleans) and arrays and they are specified under the parameters key in the configuration. It is also good practice to specify service class names as parameters, as this allows extending the bundle and, for example, overriding certain services from another bundle. You can retrieve the parameters from the container by calling the getParameter method with the parameter name as an argument, but most of the time you will be passing the parameters as arguments to services in the configuration. Services are the classes that hold the business logic of your bundle. By defining them in the configuration file, you can harness the power of the dependency injection container, i.e. inject parameters and other services into them automatically, and later retrieve them from the container fully operational with a single line of code. If you are not very familiar with dependency injection, I suggest you read about it a little bit first, there are many good explanations about it elsewhere so I will not cover it in detail here. By convention, the parameters and services are namespaced with the bundle name (in snake case), but this is not enforced in any way so you are free to name them as you like. Although I would recommend using the bundle name namespacing to avoid any collisions with other bundles. Here is a small example of a services.yml file from an example bundle:
parameters:
    cvuorinen_example.greeter.class: Cvuorinen\ExampleBundle\Service\Greeter
    cvuorinen_example.greeter.greeting: "Hello"

services:
    cvuorinen_example.greeter:
        class: %cvuorinen_example.greeter.class%
        arguments: [%cvuorinen_example.greeter.greeting%]
Here we define one service (called cvuorinen_example.greeter). We have two parameters, the first one holds the class name of the Greeter service and is set as the class parameter of the service, and the other one is a string value that will be passed to the service as a constructor argument using the arguments parameter array of the service. As you can see, the defined parameters can be referenced in the configuration by wrapping them with %-characters. You can also reference parameters from the main configuration, and also from other bundles (although be aware that this again makes your bundle coupled to the other bundle). You can read more about parameters in the Symfony documentation. You can test and debug your configuration with the CLI command:
$ app/console container:debug
This will construct and merge the bundle configurations and print out all the registered services, or an error message in case there is something wrong with the configuration. There is also a --parameters option to print out all the parameters.

Putting it all together

Now that we have a working service configuration, let’s see how this can be used in a controller. First, here is our very simple Greeter service:
namespace Cvuorinen\ExampleBundle\Service;

class Greeter
{
    public function __construct($greeting)
    {
        $this->greeting = $greeting;
    }

    public function greet($name)
    {
        return $this->greeting . ' ' . $name;
    }
}
In this post I will focus on the service configuration, so I will not cover router configuration in detail. You can read more about it in the official documentation if you are not familiar with it. But suppose we have a route with pattern /hello/{name}, that points to a DefaultController inside our example bundle. Then our controller might look something like this:
namespace Cvuorinen\ExampleBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    public function indexAction($name)
    {
        $greeter = $this->get('cvuorinen_example.greeter');

        return new Response(
            $greeter->greet($name)
        );
    }
}
As you can see, there is no logic in the controller, it only retrieves a service from the container and calls a method on that service and passes along a parameter from the request. The controller does not need to know how to construct the Greeter service, the dependency injection container does all the work for it. This allows us to keep the controllers “skinny” and all the business logic and dependency management elsewhere. Now, suppose we wanted to change the greeting, all we need to do is change the parameter inside the configuration file. This might not seem like a big deal with this example, but in a real world application you will probably reuse the same services in many places of the application. With this perspective, the benefits are quite obvious.

What about the dependencies!?

Ok, if you have been paying attention, you might have noticed that our example didn’t really inject any dependencies, only a simple parameter. This is true, I wanted to keep the first example as simple as possible. Now, what if we need to make a multilingual application? We will have to use some kind of translation system and the Greeter service needs it to translate the greeting. First, let’s change the service class like this:
namespace Cvuorinen\ExampleBundle\Service;

use Symfony\Component\Translation\TranslatorInterface;

class Greeter
{
    public function __construct($greeting, TranslatorInterface $translator)
    {
        $this->greeting = $greeting;
        $this->translator = $translator;
    }

    public function greet($name)
    {
        return $this->translator->trans($this->greeting) . ' ' . $name;
    }
}
Then let’s change the configuration to inject a translator service like this:
parameters:
    cvuorinen_example.greeter.class: Cvuorinen\ExampleBundle\Service\Greeter
    cvuorinen_example.greeter.greeting: "Hello"

services:
    cvuorinen_example.greeter:
        class: %cvuorinen_example.greeter.class%
        arguments:
            - %cvuorinen_example.greeter.greeting%
            - @translator
Otherwise this is exactly the same as the previous example, the only difference is in the arguments section of the greeter service configuration. Here we have added another constructor argument, this time it is another service. Services can be referenced in the configuration by prefixing them with the @-character. We are using the default Symfony Translator service, which has the name “translator”. Some core services do not have a namespace in their name, but you could just as well inject a custom translator service with @cvuorinen_example.translator for example.

Injecting all the things

So far I have only covered the “basic” injection method, constructor injection. The Symfony service container can also be used with setter injection and property injection. Setter injection means that the service class has a separate setter method for a dependency (because it might be optional, for example). This can be configured by adding a calls parameter to the service configuration that is an array of method calls that will be executed after the service has been constructed. Property injection means that the class has public properties and the dependency can be set into it from outside. This can be achieved with properties parameter in the service configuration. More information about the different injection types can be found in the documentation. The service configuration can also use another service as a factory to create a service. The factory can be another service inside your own bundle, but you can also use this feature with some existing services. One example is that if your service needs a certain Doctrine repository, instead of injecting the entity manager service, you can use the entity manager service as a factory to define the repository class as a service. This way, your service class only needs to add a dependency to the repository class and not the entire entity manager. This makes the service more portable and more easily testable, since you will only need to mock the repository class. Services can also be declared private. This means they will not be available from the container using the get method, they can only be used as arguments injected to other services. This can be done by setting public: false in the service configuration. Here is a final example that shows how we can define an entity repository as a service using the Doctrine entity manager as a factory and injecting it into another service using setter injection. The entity repository service is also declared private, so it will not be available through the container.
parameters:
    cvuorinen_example.item_repository.class: Doctrine\ORM\EntityRepository
    cvuorinen_example.item_repository.entity: "CvuorinenExampleBundle:Item"
    cvuorinen_example.items.class: Cvuorinen\ExampleBundle\Service\Items

services:
    cvuorinen_example.item_repository:
        class: %cvuorinen_example.item_repository.class%
        public: false
        factory_service: doctrine.orm.entity_manager
        factory_method: getRepository
        arguments: [%cvuorinen_example.item_repository.entity%]
    cvuorinen_example.items:
        class: %cvuorinen_example.items.class%
        calls:
            - [setRepository, [@cvuorinen_example.item_repository]]

Conclusion

This turned out to be quite a long post and there is still so much more to the Symfony bundle configuration and dependency injection container that I have not covered. Hopefully this will help you understand the Symfony bundle system a little bit better and encourage you to use dependency injection more. I hope to cover more advanced topics in a future post, such as overriding bundle configuration (bundle inheritance), sharing parameters between bundles and defining controllers as services. Mean while, you can learn more about the service container and dependency injection from the Symfony documentation. One last thing. Even though the service container itself is a service that can be referenced in the configuration and injected into other services, don’t do that. That is really the anti-pattern of dependency injection, as the services will then be responsible of their own dependencies again and also the services then become tightly coupled to the container itself.

Frequently Asked Questions (FAQs) about Symfony Bundle Configuration and Service Container

What is the Symfony Service Container and how does it work?

The Symfony Service Container, also known as the Dependency Injection Container, is a powerful tool that manages the services in a Symfony application. It’s a central registry of the application’s services, controlling the instantiation of service objects. When a service is defined, the container is responsible for creating the service object, managing its lifecycle, and injecting it with any dependencies it needs. This allows for a clean, modular architecture where components are loosely coupled and easier to test.

How do I configure a Symfony Bundle?

Configuring a Symfony Bundle involves defining services and parameters in the service container. This is typically done in a configuration file, such as services.yaml or config.yaml. The configuration file uses a specific syntax to define services, their arguments, and any tags or calls they should have. You can also use the configuration to set parameters that can be used throughout the application.

What is the purpose of a Bundle in Symfony?

A Bundle in Symfony is a structured set of files (PHP files, stylesheets, JavaScripts, images, etc.) that implement a single feature, such as a blog, a forum, etc. In Symfony applications, bundles are used to add functionality in a reusable, decoupled manner. They can be reused across multiple projects, and can also be shared with the Symfony community.

How can I override a Bundle’s configuration in Symfony?

Overriding a Bundle’s configuration in Symfony can be done by creating a new configuration file in your application’s config directory. This file should have the same name as the bundle’s configuration file, and any settings you define in this file will override the bundle’s default configuration.

What are some common issues I might encounter with the Symfony Service Container?

Some common issues with the Symfony Service Container include services not being available because they haven’t been defined in the container, circular reference errors where two services depend on each other, and issues with service visibility where a private service is being accessed from outside the container.

How can I debug issues with my Symfony Bundle configuration?

Symfony provides several tools for debugging bundle configuration issues. The debug:container console command can be used to list all services in the container, and the debug:config command can be used to display the current configuration for a bundle.

How can I use parameters in my Symfony Bundle configuration?

Parameters can be defined in your Symfony Bundle configuration file and can be used to configure services. They are defined using the parameters key, and can be referenced in your service definitions using the %parameter_name% syntax.

What is the role of the Extension class in Symfony Bundle configuration?

The Extension class in Symfony Bundle configuration is responsible for loading and managing the bundle’s configuration. It’s where you define services, parameters, and any other configuration that your bundle needs.

How can I create a custom service in Symfony?

Creating a custom service in Symfony involves defining the service in your bundle’s configuration file, and then creating a class for the service. The service class should contain the logic for the service, and any dependencies should be injected through the constructor or setter methods.

How can I share a service across multiple Symfony Bundles?

Services in Symfony are shared across bundles by default. Once a service is defined in the service container, it can be accessed from any bundle in the application. This allows for a high degree of reusability and modularity in Symfony applications.

Carl VuorinenCarl Vuorinen
View Author

Carl is a developer and team lead at W3 Group Finland with over ten years of experience in web development and PHP. He likes to take on challenging projects and loves to solve problems effectively and deliver quality code. He lives in Järvenpää, Finland with his wife and two little girls. Carl is also an active member of the PHP User Group Finland.

bundlesconfigurationframeworkPHPsymfonysymfony bundlesymfony2
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form