Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Auto-Adding Services

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

At this point... we have a directory with a PHP class inside. And, honestly, we could just move this into its own repository, put it on Packagist and be done! But in that case, it wouldn't be a bundle, it would simply be a library, which is more or less defined as: a directory full of PHP classes.

So what is the difference between a library and a bundle? What does a bundle give is that a library does not? The "mostly-accurate" answer is simple: services. If we only created a library, people could use our classes, but it would be up to them to add configuration to register them as services in Symfony's container. But if we make a bundle, we can automatically add services to the container as soon as our bundle is installed. Sure, bundles can also do a few other things - like provide translations and other config - but providing services is their main super power.

So, we're going to create a bundle. Actually, the perfect solution would be to create a library with only the KnpUIpsum class, and then also a bundle that requires that library and adds the Symfony service configuration. A good example of this is KnpMenu and KnpMenuBundle.

Creating the Bundle Class

To make this a bundle, create a new class called KnpULoremIpsumBundle. This could be called anything... but usually it's the vendor namespace plus the directory name.

Make this extend Bundle and... that's it! You almost never need to have any logic in here.

... lines 1 - 2
namespace KnpU\LoremIpsumBundle;
... lines 4 - 6
class KnpULoremIpsumBundle extends Bundle
... lines 8 - 11

To enable this in our app, open bundles.php and configure it for all environments. I'll remove the use statement for consistency. Normally, this happens automatically when we install a bundle... but since we just added the bundle manually, we gotta do it by hand.

... lines 1 - 2
return [
... lines 4 - 14
KnpU\LoremIpsumBundle\KnpULoremIpsumBundle::class => ['all' => true],
];

And, congratulations! We now have a bundle!

Creating the Extension Class

So.... what the heck does that give us? Remember: the super-power of a bundle is that it can automatically add services to the container, without the user needing to configure anything. How does that work? Let me show you.

Next to the bundle class, create a new directory called DependencyInjection. Then, add a new class inside with the same name of the bundle, except ending in Extension. So, KnpULoremIpsumExtension. Make this extend Extension from HttpKernel. This forces us to implement one method. I'll go to the Code -> Generate menu, or Cmd+N on a Mac, choose "Implement Methods" and select the one we need. Inside, just var_dump that we're alive and... die!

... lines 1 - 2
namespace KnpU\LoremIpsumBundle\DependencyInjection;
... lines 4 - 7
class KnpULoremIpsumExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
var_dump('We\'re alive!');die;
}
}

Now move over and refresh. Yes! It hits our new code!

This is really important. Whenever Symfony builds the container, it loops over all the bundles and, inside of each, looks for a DependencyInjection directory and then inside of that, a class with the same name of the bundle, but ending in Extension. Woh. If that class exists, it instantiates it and calls load(). This is our big chance to add any services we want! We can go crazy!

See this $container variable? It's not really a container, it's a container builder: something we can add services to.

Adding services.xml

Right now, our service is defined in the config/services.yaml file of the application. Delete that! We're going to put a service configuration file inside the bundle instead. Create a Resources/ directory and another config/ directory inside: this is the best-practice location for service config. Then, add services.xml. Yep, I said XML. Wait, don't run away!

You can use YAML to configure your services, but XML is the best-practice for re-usable bundles... though it doesn't matter much. Using XML does have one tiny advantage: it doesn't require the symfony/yaml component, which, at least in theory, makes your bundle feel a bit lighter.

To fill this in... um, I cheat. Google for "Symfony Services", open the documentation, search for XML, and stop when you find a code block that defines a service. Click the XML tab and steal this! Paste it into our code. The only thing we need to do is configure a single service whose id is the class of the service. So, use KnpU\LoremIpsumBundle\KnpUIpsum. We're not passing any arguments, so we can use the short XML syntax for now.

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="KnpU\LoremIpsumBundle\KnpUIpsum" />
</services>
</container>

But this file isn't processed automatically. Go to the extension class and remove the var_dump(). The code to load the config file looks a little funny: $loader = new XmlFileLoader() from the DependencyInjection component. Pass this a new FileLocator - the one from the Config component - with the path to that directory: ../Resources/config. Below that, add $loader->load('services.xml').

... lines 1 - 9
class KnpULoremIpsumExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
}
}

Voilà! Refresh the page. It works! When the container builds, the load() method is called and our bundle adds its service.

Next, let's talk about service id best-practices, how to support autowiring and public versus private services.

Leave a comment!

35
Login or Register to join the conversation
Default user avatar
Default user avatar David Schaller | posted 1 year ago | edited

Hi everyone,

has something changed in symfony 6 with the depenency injection?
The var_dump part doesn't work for me. Seems like the file is not loaded at all (I can type gibberish in this file, doesn't do anything).


namespace Xxx\SomeBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class XxxSomeExtension extends Extension
{
  public function load(array $configs, ContainerBuilder $container)
  {
    var_dump('lol');die;
  }
}

Thanks for your help.
David

1 Reply

Hey David Schaller!

No, nothing has changed as far as I know - and this functionality is pretty core and unchanging. There are 2 possibilities:

A) Cache: you might need to manually rebuild your cache, then it'll hit your code. That shouldn't happen (the cache should be rebuilding itself when it detects that you modify this file), but it's possible something weird is happening.

B) For some reason your extension isn't being seen. The registration of the extension is actually handled by your bundle class. Well, not directly - but the base Bundle class that it extends. I'd open up that core class and add some debugging code in this method - https://github.com/symfony/... - to see what's going on.

Let me know what you find out :).

Cheers!

1 Reply

I found that I added XxxSomeBundleExtention instead of XxxSomeBundleExtention. Once I took out Bundle in the name it worked just fine.

Reply
Sami-jarmoud Avatar

Hey David,

when you delete the service in the config/services.yaml file of the application, then work the `var_dump'.

services:

KnpU\LoremIpsumBundle\KnpUIpsum: ~
Reply
triemli Avatar
triemli Avatar triemli | posted 2 years ago | edited

Hi. As I right understand, I specify triemli\LoremIpsumBundle\TriemliLoremIpsumBundle::class => ['all' => true], in bundles.php

Then I have extension file https://pastebin.com/qPUGaqZM

So it should be already try to be loaded right? I just see normal web page.

Reply

Hey WebAdequate,

Yep, but it's compiled, so you have to clear the cache first. Did you try to clear the cache? I'd recommend you manually remove var/cache/dev/ directory to clear the cache for dev (or for other env you use). Because bin/console cache:clear won't work as it will fail due to your die(); statement. Btw, if you run cache:clear command - you will find your die(); statement there, i.e. it should print "234" in your console :) Do you see this?

Cheers!

Reply
triemli Avatar

Ok I found an error: Instead of TriemliLoremIpsumBundleExtension must be TriemliLoremIpsumExtension
I just hear "bundle name + extension". I would specify "bundle name minus 'Bundle' postfix"

Reply

Hey WebAdequate,

Ah, a misprint! Good catch, I totally miss that part :) Thanks for sharing your solution with others!

Cheers!

Reply
triemli Avatar
triemli Avatar triemli | Victor | posted 2 years ago | edited

Hi again! Actually i'm always clear cache with manual remove.

So I have triemli\LoremIpsumBundle\TriemliLoremIpsumBundle::class => ['all' => true], in bundles.php
I have also class TriemliLoremIpsumBundle extends \Symfony\Component\HttpKernel\Bundle\Bundle
And this file https://pastebin.com/qPUGaqZM which is in DependencyInjection folder
refresh the page... it's successful loaded. The method load() wasn't called. ;[

Reply
Bertin Avatar
Bertin Avatar Bertin | posted 4 years ago | edited

Hi there,

For our company we're trying to manage multiple bundles and have some questions
or need some best pratices or tips (hope you could help us).

#1 Bundles
Do you (or someone else) have some tips how we could manage/maintain/development these bundles. We're thinking of an development project where all bundles are loaded but with opportunities to maintain individuel bundles (does someone has experience with this kind of setup.

#2 Tools
Is there some tool where we could manage all projects like Symfony version, Bundle versions, code health, code errors, etc
Something like ManageWP for Wordpress.

Reply

Hey Bertinm!

Sorry for the delay!

1) Not sure I completely understand your question. Could you explain a bit more what you're trying to achieve here? Are you talking about open-source bundles? Or private bundles? Actually, Composer can work with private packages as well, it just need to have access (READ access at least) to your GitHub account where those bundles are hosted and it can install them without problems.

2) Unfortunately, I don't know a similar service personally. You probably may google for it, but can't give you any recommendations about them. I'd recommend you to look at SymfonyCloud: https://symfony.com/cloud/ - it's the best for Symfony apps. About code health - take a look at Symfony solution: https://insight.symfony.com/ . Or you can also check another one: Scrutinizer. What about monitoring - we love NewRelic. All this is a good stack, you can read more about them on their promo pages.

Cheers!

Reply
Bertin Avatar
Bertin Avatar Bertin | Victor | posted 4 years ago | edited

victor
Our bundles are stored into a private repo (Bitbucket). Now we are maintaining all these bundles individual (each bundle is an separated folder). We are searching for a way to maintain these bundles something like git subtree and with automatich versioning, CI, etc.

Hope someone has experience with this kind of setup

Reply

Hey Bertin,

Oh, I see... It sounds exactly like Symfony team manages their repositories: they have subtree for each bundle/component, but it's readonly. All the work is going to happen in symfony/symfony repository. I think you can even spy their travis CI configuration as it's an OSS. What about automatic versioning? I know they use internal tool for merging, and probably for versioning as well, or maybe they do it manually. I think you can ask it in Symfony slack channel and get more context about it, here's the link: https://symfony.com/support

I hope this helps.

Cheers!

Reply
Weli Avatar

I get an error when i run the project's code as follows: "Attempted to call an undefined method named, hasBeenStarted" of class "Symfony\Component\HttpFoundation\Session\Session".

Reply
sadikoff Avatar sadikoff | SFCASTS | Weli | posted 4 years ago | edited

Hi Weli

We investigated your problem and already fixed course code. There are 2 options for you

1) Re-download course code and start from the beginning
2) run "composer require symfony/http-kernel:4.0.12" (this will fix package versions used in course code)

I would prefer first option, because you are only on second chapter, but you are free to choose!

Cheers!

Reply

Hey Weli,

Yeah, looks like this method was removed because it had been marked as internal. Probably the best solution is to try to upgrade your dependencies, see https://github.com/symfony/... for more information. Btw, you say that it happens in the code of this course you downloaded when follow instructions in README? Could you explain a bit more about when you see this error and what was your actions? Are you in start/ or finish/ directories?

Cheers!

Reply
Vishal P. Avatar
Vishal P. Avatar Vishal P. | posted 4 years ago

how to create Bundle using command in symfony 4?

Reply

Hey @Vishal!

Great question! There is currently no way to generate a bundle using a command in Symfony 4. There is some discussion about this https://github.com/symfony/... - but it is not currently supported. And this is part, on purpose. In Symfony 4, you should never need to create a bundle in your application. The only purpose to a bundle is if you're creating re-usable code. And, in that case, it's probably better just to build it manually and add only the pieces that you need.

I hope that helps!

Cheers!

Reply
Dmitry V. Avatar
Dmitry V. Avatar Dmitry V. | posted 4 years ago

All great, but we should use try/catch blocks while XmlFileLoader tries to load a file...

Reply
Shing S. Avatar
Shing S. Avatar Shing S. | posted 4 years ago

Hi,

This is not really a question per se, I was trying out having config in php instead of yaml or xml.
After trying and figuring out for a long while,

php configs in symfony is just a php file with no classes, and readable with just
loadFromExtension()

Also a yaml or xml config file will override the php config file.
Is this correct or am i missing something?

Thank u.

Reply

Hey Shing,

I have never used PHP configs :p but probably it's correct to say that different config formats will be merged into one big config that is cached later. But of course, if you set the same config key in config.php and config.yaml or config.xml - the value will be overwritten. In theory - yes, the value you set in PHP config file will be overwritten with one from Yaml/XML. Why? Probably because in the english alphabet "Y" and "X" letters go after "P", that why the file order for reading will be the next:
- config.Php
- config.Xml
- config.Yaml

But I'd not recommend you mixing different config formats. Choose one and use it. Well, at least do not use the same file names for different formats to do not confuse system, other devs and yourself. However, having config.php and bundle.yaml files are OK I think. But I'd stick to a one format in whole the system.

And yes, it's just a PHP file, but you should at least have access to $loader variable from it like showed here: https://symfony.com/doc/cur...

Cheers!

Reply
Default user avatar

haha..Didnt consider the letters of file extension part of the order.

Yea, agree, mixing the config is not the easiest way to go. Just though some config might benefit from php for more flexibility. But let go of the idea, because its too messy to organise and test.

👍

Reply
tomchkkk Avatar
tomchkkk Avatar tomchkkk | posted 5 years ago

I was scouring the Internet for a tutorial like this only a couple of days ago. It seems to be the only one of its kind! Thanks

Reply

Hey Tom,

We have gotten a lot of request about this topic, so finally was able to find a time to write one ;)

Cheers!

Reply
Bertin Avatar
Bertin Avatar Bertin | posted 5 years ago | edited

I also having the issue when refreshing the page, the page loads normaly.
I'am in dev mode, the bundle is registered in bundles.php, also added the lib/bundle/src to composer.json


#lib/MenuBundle/src
/**
 * BhamMenuBundle
 */

namespace Bham\MenuBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
 * Class BhamMenuBundle
 * @package Bham\MenuBundle
 */
class BhamMenuBundle extends Bundle {

}

#lib/MenuBundle/src/DependencyInjection
/**
 * BhamMenuExtension
 */

namespace Bham\MenuBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class BhamMenuExtension extends Extension {

	/**
	 * Loads a specific configuration.
	 * @param array $configs
	 * @param ContainerBuilder $container
	 */
	public function load(array $configs, ContainerBuilder $container) {
		var_dump('ALIVE!');die;
	}
}

Does i miss something? I've cleared my cache and run the command composer dumpautoload

Reply

Hey Bertin,

I think you used incorrect namespace in your Extension class, it should be "Symfony\Component\DependencyInjection\Extension\Extension" instead of "Symfony\Component\HttpKernel\DependencyInjection\Extension". That's important and I think that's why your extension does not loaded, i.e. you just extends invalid class.

Cheers!

1 Reply
Dmitry V. Avatar

Hi Victor!

Are you right with your comment? As this tutorial shows it must be used "Symfony\Component\HttpKernel\DependencyInjection\Extension"... And I see there's no difference which one is used, they both are called while Symfony loads its bundles.

Reply

Hey Dmitry,

Oh, wait... you're right, it should be "Symfony\Component\HttpKernel\DependencyInjection\Extension" as we show in this screencast. And in turn, this class extends "Symfony\Component\DependencyInjection\Extension\Extension". So the problem was in the namespace, but not exactly as I thought.

Thanks for noticing it!

Cheers!

Reply
Bertin Avatar

@Victor Bocarsky, it was the wrong namespace. Thx

Reply

Hey Bertin,

Great! And thanks for approving the fix, I think it might be helpful for others.

Cheers!

1 Reply

Hi,

I don't understand why when I refresh my page, it loads normally. I don't have the var_dump

Cheers,
Thx.

Greg

Reply

Hey Gregory

"var_dump()" is native function from PHP, so you should have it :)
What I believe is that you are hitting the prod environment and that's why you can't see your changes without clearing the cache, or maybe you forgot to register the bundle?

Cheers!

Reply

I known that my mistake I dont see the var_dump when I refresh the page ;)

Reply

hehe, no worries

Can you double check that you can actually see the "profiler" bar at the bottom of the page, and that you registered the bundle in "config/bundles.php"
Also, just in case, try clearing the cache

Reply

Ok I found it
I just forgot to remove the service in the service.yaml ;)

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial is built using Symfony 4, but most of the concepts apply fine to Symfony 5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "doctrine/annotations": "^1.8", // v1.8.0
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knpuniversity/lorem-ipsum-bundle": "*@dev", // dev-master
        "nexylan/slack-bundle": "^2.0,<2.2", // v2.0.1
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.6
        "symfony/asset": "^4.0", // v4.0.6
        "symfony/console": "^4.0", // v4.0.6
        "symfony/flex": "^1.0", // v1.18.7
        "symfony/framework-bundle": "^4.0", // v4.0.6
        "symfony/lts": "^4@dev", // dev-master
        "symfony/twig-bundle": "^4.0", // v4.0.6
        "symfony/web-server-bundle": "^4.0", // v4.0.6
        "symfony/yaml": "^4.0", // v4.0.6
        "weaverryan_test/lorem-ipsum-bundle": "^1.0" // v1.0.0
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "sensiolabs/security-checker": "^4.1", // v4.1.8
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.6
        "symfony/dotenv": "^4.0", // v4.0.6
        "symfony/maker-bundle": "^1.0", // v1.1.1
        "symfony/monolog-bundle": "^3.0", // v3.2.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.3
        "symfony/stopwatch": "^3.3|^4.0", // v4.0.6
        "symfony/var-dumper": "^3.3|^4.0", // v4.0.6
        "symfony/web-profiler-bundle": "^3.3|^4.0" // v4.0.6
    }
}
userVoice