The Ultimate Developer Guide to Symfony - Bundle 09/03/2016 symfony ultimate symfony series reference

Reference: This article is intended to be as complete as possible and is kept up to date. TL;DR: Configure services from a third party library in a Bundle.

In this guide we've explored the main standalone libraries (also known as "Components") provided by Symfony to help us build applications:

In this article, we're going to have a closer look at how HttpKernel enables reusable code.

Then in the next article we'll see the different ways to organize our application tree directory.

Finally we'll finish by putting all this knowledge in practice by creating a "fortune" project with:

HttpKernel vs Kernel

The HttpKernel component provides two implementations for HttpKernelInterface .

The first one, HttpKernel , relies on Event Dispatcher and Routing to execute the appropriate controller for the given Request.

And the second one, Kernel , relies on Dependency Injection and HttpKernel :

<?php namespace Symfony\Component\HttpKernel; use Symfony\Component\HttpFoundation\Request; class Kernel implements HttpKernelInterface { public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { if (false === $this->booted) { $this->boot(); } return $this->container->get('http_kernel')->handle($request, $type, $catch); } public function boot() { // Initializes the container } abstract public function registerBundles(); }

Note: For brevity's sake, Kernel has been heavily truncated.

Initialization of the container includes:

retrieving all "bundles" creating a ContainerBuilder for each bundles: registering its ExtensionInterface implementations in the container registering its CompilerPassInterface implementations in the container dumping the container in an optimized implementation

Once the container is initialized, Kernel expects it to contain a http_kernel service to which it will delegate the actual HTTP work.

Bundle

A bundle is a package that contains ExtensionInterface and CompilerPassInterface implementations, to configure a Dependency Injection container. It can be summed up by this interface:

<?php namespace Symfony\Component\HttpKernel\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; interface BundleInterface { // Adds CompilerPassInterface implementations to the container public function build(ContainerBuilder $container); // Returs an ExtensionInterface implementation, which will be registered in the container public function getContainerExtension(); }

Note: Once again, this interface has been truncated for brevity's sake.

Bundles are usually created for one of the following purposes:

define a third party library's classes as Dependency Injection services (e.g. TacticianBundle for Tactician which provides a CommandBus, MonologBundle for Monolog which provides a PSR-3 compliant logger, etc)

define an application's classes as Dependency Injection services (usually named AppBundle)

create a framework (e.g. user management with FOSUserBundle, admin generator with SonataAdminBundle, etc)

Bundles follow by convention the following directory tree:

. ├── Command ├── Controller ├── DependencyInjection │ └── CompilerPass ├── EventListener ├── Resources │ └── config │ └── services │ └── some_definitions.yml ├── Tests └── VendorProjectBundle.php

NanoFrameworkBundle example

Since HttpKernel component is a third party library, we're going to create a bundle to provide its classes as Dependency Injection services. This is also a good opportunity to have a look at how a Symfony application works behind the hood.

NanoFrameworkBundle's purpose is to provides a http_kernel service that can be used by Kernel . First let's create a directory:

mkdir nano-framework-bundle cd nano-framework-bundle

Then we can create an implementation of BundleInterface :

<?php // VendorNanoFrameworkBundle.php namespace Vendor\NanoFrameworkBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; class VendorNanoFrameworkBundle extends Bundle { }

Bundle extension

To be able to load Dependency Injection configuration, we'll create an implementation of ExtensionInterface :

<?php // DependencyInjection/VendorNanoFrameworkExtension.php namespace Vendor\NanoFrameworkBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class VendorNanoFrameworkExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { $fileLocator = new FileLocator(__DIR__.'/../Resources/config'); $loader = new DirectoryLoader($container, $fileLocator); $loader->setResolver(new LoaderResolver(array( new YamlFileLoader($container, $fileLocator), $loader, ))); $loader->load('services/'); } }

Once done, we can create the configuration:

# Resources/config/services/http_kernel.yml services: http_kernel: class: Symfony\Component\HttpKernel\HttpKernel arguments: - "@event_dispatcher" - "@controller_resolver" - "@request_stack" event_dispatcher: class: Symfony\Component\EventDispatcher\EventDispatcher controller_resolver: class: Symfony\Component\HttpKernel\Controller\ControllerResolver public: false request_stack: class: Symfony\Component\HttpFoundation\RequestStack

Bundle compiler pass

In order to register event listeners in EventDispatcher in a way that doesn't require us to edit Resources/config/services/http_kernel.yml , we're going to create an implementation of CompilerInterface :

<?php // DependencyInjection/CompilerPass/AddListenersPass.php namespace Vendor\NanoFrameworkBundle\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class AddListenersPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $eventDispatcher = $container->findDefinition('event_dispatcher'); $eventListeners = $container->findTaggedServiceIds('kernel.event_listener'); foreach ($eventListeners as $id => $events) { foreach ($events as $event) { $eventDispatcher->addMethodCall('addListener', array( $event['event'], array(new Reference($id), $event['method']), isset($event['priority']) ? $event['priority'] : 0; )); } } } }

With this, we only need to add a tag with:

a kernel.event_listener name

name an event to listen to (e.g. kernel.request )

) a method to call (e.g. onKernelRequest )

) optionally a priority (default to 0 , the greater the sooner it will be executed)

To complete the step, we need to register it in our bundle:

<?php // VendorNanoFrameworkBundle.php namespace Vendor\NanoFrameworkBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; use Vendor\NanoFrameworkBundle\DependencyInjection\CompilerPass\AddListenersPass; class VendorNanoFrameworkBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new AddListenersPass()); } }

Note: While CompilerPassInterface implementations need to be registered explicitly, there is no need to do anything for ExtensionInterface implementations as Bundle contains a method able to locate it, based on the following conventions: it needs to be in DependencyInjection directory

directory it needs to be named after the bundle name (replace Bundle suffix by Extension )

suffix by ) it needs to implement ExtensionInterface

More configuration

HttpKernel relies on event listeners for the routing, in order to enable it we need to add the following configuration:

# Resources/config/services/routing.yml services: router_listener: class: Symfony\Component\HttpKernel\EventListener\RouterListener arguments: - "@router" - "@request_stack" - "@router.request_context" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 32 } router: class: Symfony\Component\Routing\Router public: false arguments: - "@routing.loader" - "%kernel.root_dir%/config/routings" - "%router.options%" - "@router.request_context" calls: - [setConfigCacheFactory, ["@config_cache_factory"]] routing.loader: class: Symfony\Component\Config\Loader\DelegatingLoader public: false arguments: - "@routing.resolver" routing.resolver: class: Symfony\Component\Config\Loader\LoaderResolver public: false calls: - [addLoader, ["@routing.loader.yml"]] router.request_context: class: Symfony\Component\Routing\RequestContext public: false config_cache_factory: class: Symfony\Component\Config\ResourceCheckerConfigCacheFactory public: false routing.loader.yml: class: Symfony\Component\Routing\Loader\YamlFileLoader public: false arguments: - "@file_locator"

Usage

Since Kernel is an abstract class, we need to create an implementation (usually called AppKernel):

<?php // Tests/app/AppKernel.php use Symfony\Component\HttpKernel\Kernel; class AppKernel extends Kernel { public function registerBundles() { return array( new Vendor\NanoFrameworkBundle\VendorNanoFrameworkBundle(), ); } public function getRootDir() { return __DIR__; } public function getCacheDir() { return dirname(__DIR__).'/var/cache/'.$this->getEnvironment(); } public function getLogDir() { return dirname(__DIR__).'/var/logs'; } }

Finally we need to create a "Front Controller" (a fancy name for index.php ):

<?php // Tests/web/index.php <?php use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('prod', false); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);

Conclusion

Bundles enable us to define classes as Dependency Injection services, for our applications and third part libraries in a reusable way.

In the example above we've created a bundle that provides a http_kernel service, which can then be used to create Symfony applications. Here are some existing bundles that do it for us:

FrameworkBundle, the official one provided by Symfony. It comes with many services out of the box, mainly targeted at full stack applications (it follows a "solve 80% of use cases" philosohpy)

MicroFrameworkBundle, an unofficial one. It comes with the bare minimum (it follows a "add what you need" philosohpy)

There are many bundles available, you can find them by checking symfony-bundle in Packagist.