Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Creating a Reusable (& Amazing) Symfony Bundle

2:21:06

What you'll be learning

This tutorial is built using Symfony 4, but most of the concepts apply fine to Symfony 5!
// 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
    }
}

Want to share some code between projects, or maybe with the whole world? Let's do it! By creating a Symfony bundle! In this tutorial, we'll learn about bundles, their super-powers, how to add services & routes and the best-practices to create the best bundle possible:

  • Anatomy of a Bundle
  • Bundles vs Libraries
  • Building a bundle inside your app
  • DependencyExtensions to add Services
  • Complex Service configuration: Definitions & compiler passes
  • Allowing config via a Configuration class
  • Private services
  • Routing & other Configuration
  • Creating a Recipe
  • Registering your bundle with Packagist
  • Handling Releases
  • README!

Not only will you be able to create your own bundle, but we'll learn a lot along the way about how all bundles in the Symfony world work!


Your Guides

Ryan Weaver

Buy Access

Join the Conversation?

44
Login or Register to join the conversation

Hi !
I see that this course is currently in progress, and I wonder, will it be implemented into the SF3 or 4 Track ? I know that there are no more bundle into SF4, so, does it concern SF3 exclusively ?
Also, I must say that I can't find this course into your searching tool. I only found it while going through the "Courses updates" through my profile.

1 Reply

Hi Virginie,

Really? I could find this course by "Symfony Bundle" keyword, see https://knpuniversity.com/s... . I wonder what keywords you searched for. Well, yes, Symfony 4 is bundle-less, but it means you should avoid creating bundles in the src/ folder of your project, but third-party bundles are still valid. And this screencast is not about creating project bundles but third-party bundles. In shorts, you create a bundle only when you need to share some code between projects. And this course probably will be in the Symfony 4 track because, well, we're using Symfony components v4.0 in this course. But most of the information we show in this course is valid for Symfony 3 as well, of course, if we're talking about sharable third-party bundles.

Cheers!

Reply

Hi Victor,
Yeah, I worked a lot with SF since this comment, and I must say, it was kinda stupid.
Thank you for answering me at that dark time. I now understand what this course is about and its usefulness. :)

Reply

Hey Virginie,

No problem! Thank you for your feedback! Glad it was useful for you ;)

Cheers!

Reply
Default user avatar

Hey, Is it going to cover how to create and publish sf flex recipe for this custom bundle? it would be nice!

1 Reply

Hey Rafael!

Bah! Unfortunately, that's the one thing that I did *not* cover in this tutorial.... in part because it's super tricky to record properly. But if you have any questions, let us know. My advice is this: your bundle may NOT need a recipe at all - many do not. You only need a recipe if your bundle requires some configuration or other files (e.g. the Twig recipe creates a templates/ directory). A recipe is basically "automating" the README. So if your README says "Do X, then create file Y", these are great things for the recipe :). And mostly, recipes are quite simple: you include files that you would like copied into the user's project.

Cheers!

1 Reply

Hey, there thank you so mush for this tutorial,
I have a small question, Imagine that we need to create a reusable bundle that should work for both symfony ^3.0||^4.0||^5.0 how we should manage this via github. How mush tags or branchs we need for this usecase. I suppose that we need to create 3 tags (not sure the right and the recomanded way) And then, Imagnie that we get introduced a new feature that should be merge on both tag3 (for symfony 3) and tag4 (for symfony 4) then, what gonna be the solution ? Cretate new 2 tagas 3.1 and 4.1 for example ?
Cheers

Reply

Hey Jeremy!

This is a tough question :) First of all, it depends on the code... if no BC breaks between Symfony ^3.0||^4.0||^5.0 and your bundle may work in any version of it - you don't need different tags, you can do a single release but in your composer.json file allow all those symfony versions, i.e: ^3.0||^4.0||^5.0. And that's it, you bundle now can be installed on ^3.0||^4.0||^5.0. But, if you started your bundle when the latest Symfony version was 3.0, it makes sense that you already have a release for ^3.0 already, and so when the new version of Symfony come out, i.e. 4.0 - you will need to tweak your composer.json file to ^3.0||^4.0 and do a second release for this. And so on.

But if your code has some specific code that should be written in different ways for symfony 3.0 and 4.0 for example - then you still don't need different releases (tags) for this. You can do a trick, depends on your case, e.g. check if the specific Symfony class exist that was added in the newer Symfony 4.0 version and does not exist in 3.0. If that class exist - do some specific code... else - do another specific code that will be legacy for Symfony 3.0, something like that. So, basically, you have a few implementation in the same code base, and you may have only 1 release for this, but depends on the symfony version of users who use your bundle a slightly different implementation will be called.

And usually, all I explained above is enough for bundles, bundles are not as big as Symfony components, so usually maintainers do not have separate active branches for different versions they still maintain. Usually, bundles are just pushed forward, and all the bug fixes applies at the latest version. But if you have a *very* complex bundle, and want simultaneously support a few active branches and each branch is responsible for each major Symfony version for example - then you need different branches, and so, different tags. Basically, just look at the symfony/symfony repo structure, it has branches like 3.x, 4.x, 5.x. And if you want to do some changes that should affect only symfony 4, you need to do that in 4.x branch... and so release it as 4.x.x release for example.

But this way you may have troubles with versioning, as you preserve your major release versions for symfony major version that means you may introduce some BC breaks in minor releases that is not supposed to be done. Well, you can create a new release that brings some BC breaks in to 4.x branch as e.g. 6.x.x or 44.x.x, i.e. changing the major version of release to a new version... but most probably you will just mislead your users.

But as I said, usually it's enough to follow first two ways, the 3rd way is difficult and suppose that you have a crazy complex bundle development process of which you should care as Symfony components do, something like this. Probably it's valid only for a huge projects.

I hope this helps!

Cheers!

1 Reply

Thank you for your answer, really appreciate.
1) Sorry I don't get the difference between a release and a tag :( kind of noob question, I'm sorry.

2) Imagine that the 3.x bundle use Symfony\Component\EventDispatcher\Event and extends that class and the 5.x also should extends that class, but it does not exist because they renamed the class to Symfony\Contracts\EventDispatcher\Event, then do you think I can have a legacy code for each branch ? It's possible does there an example because I think If the class does not exist that should raise an exception?

3) So if I have a bug that should be fixed on 3.x and 4.x then the hotfix should be merged twice ? and taged twice?

Reply

Hey Jeremy,

> 1) Sorry I don't get the difference between a release and a tag :( kind of noob question, I'm sorry.

No problem! :) Basically, it's the same thing, at least I mentioned them as the same thing in my previous comment, so you can literally read "tag" instead of "release" there. But if you're curious about the difference - you can find it here: https://stackoverflow.com/q...

> 2) ... do you think I can have a legacy code for each branch ? It's possible does there an example because I think If the class does not exist that should raise an exception?

I think yes, moreover, I think you can have legacy code for the exact same branch, you don't need different branches for this, but you would probably need to create different files: first will extend the old class, and trigger a deprecation notice so your users know that they should stop using it in their projects and extend a different class instead. Well, unless I'm correct and PHP will throw a fatal error because that class does not exist, I don't remember fairly speaking. But if so - yep, probably you would need different branches, and in different branches you would need to set correct constraints to your composer.json and allow install only those Symfony package versions that works well with the code in that exact branch. Though there might be some workaround still like, if the class does not exist - register that class definition manually to suppress the runtime fatal error. But you would still want to throw an exception if user will try to use that legacy code. Sorry, don't have any examples that come to my mind currently

> 3) So if I have a bug that should be fixed on 3.x and 4.x then the hotfix should be merged twice ?

if you decided to manage your bundle with different maintained branches - then I suppose yes :) Well, it depends as always, you may have follow the Symfony strategy, when they merge bug fixes to the lowest maintained branch... and then they merge that branch upstream. I.e. if you have a bugfix for 3.x and 4.x - that bug fix should be merged to 3.x, and then the updated 3.x branch that has the bugfix already should be merged upstream into the 4.x, i.e. you as a maintainer bring that bugfix to 4.x branch as well. And if nothing should be tweaked - perfect! both branches will have the same bugfix, and you only need 1 PR for this. But if 4.x would require a slightly different or compeltely different code - you would need 2 PRs: first for 3.x branch and on for 4.x.

> and taged twice?

Definitely! You need 2 releases because you literally have 2 different codes bases: one is used by one group of people, and another one is used by another group :)

P.S. Well, I have to mentioned that it's a really complex topic with a lot of edge cases :) The best would be to look at similar big projects and learn how they maintain the code.

I hope this helps!

Cheers!

Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | posted 2 years ago | edited

Hello there , The term "Bundle" will disappear from Symfony? (In the new book I found nothing that talks about Bundle).

I'm asking because normally I structure the whole application with the new structure, but I have a case like a CMS that I really need to make the Bundle structure as it seems to be done in the old times, it's the way I found to be able to make a replicable code, I don't know if there is another way?

Reply

Hey CharlES,

Not really! Bundles still remain in Symfony, but it's not recommend anymore to orginize *your* own code with bundles anymore, just use directory structure in your src/ folder and that's it. Bundles remain only for third-party code as a way to provide possibility to reuse third-party code with autoconfiguration, i.e. you require them via composer as a dependency and they bring you some features.

If you want to *share* some code with other developers - yes, create a bundle. You still may find a lot of bundles that exist on the GItHub which provide some features out of the box, e.g. knplabs/knp-menu-bundle to build menu lists, etc. So, if your goal is to share some code between a few projects - yes, bundles are what you need. Think of bundles like a standalone code that provides some features to applications that require it.

I hope this helps!

Cheers!

1 Reply
Jose carlos C. Avatar

Sylius has also done so https://github.com/Sylius/S..., What do you recommend me to do to have a replicable code?

Reply

Hey CharlES,

Look my comment above. Silyus is a bit different, it looks like it follows Symfony strategy, you can see something similar in symfony/symfony repo. In Sylius repo you may see some bundles in the src/ directory... but that's because you're looking at their repo... if you would like to use those bundles - you will need to require them via Composer, and so they will be put into vendor/ directory, not in your src/, and so you won't be able to make any changes to them directly as it's a third-party code. You may only override some code using Symfony features. If you want to put some code into *src/* directory of user projects - you would need a *code generator*, not a bundle. See how Symfony Maker works... it's a bundle, but it generates some code via different commands and put it into your src/ dir directly, so you can further change it without any overridding as it's *your* custom code, not a third-party code anymore.

I hope it sheds a light for you!

Cheers!

1 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | victor | posted 2 years ago

So I am doing it this way: https://prnt.sc/udwmbi
Inside src/ put the CMS directory, where everything goes global and then go putting what goes different in each web outside the CMS directory, something like in the following image, where I have taken out ServiceLocation outside the CMS directory and that way make it more reusable. Do you see it well or do you recommend something better?

Reply

Hey CharlES,

Looks good to me... things related to each other would be good to keep together. The question is how you are going to share that src/CMS/ code between a few projects? By simple copy/paste? Probably OK as long as you know what you're doing. Also, the question in maintainability, as what if you copy/paste your code from source repo to a few projects and do some modifications in it, and then, later, you will made some changes in the source code? It might be tricky to apply those changes in all your projects where you copy/pasted it. Composer and versioning could help with this, but this would mean your third-party code will live in vendor/ and you won't be able to change it, only override using framework features.

I hope this helps!

Cheers!

1 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | victor | posted 2 years ago

Yes, that's my big problem, I want to try to do things right. Currently I work alone, I am Freelancer. I also want to accustom myself as very well you recommend me to use Composer and the versions, so much to have a control of code as also to get used to them in case some day I work in a company with other programmers.
That's why I'm asking, I'm looking for a comfortable and replicable way.

But the questions you expose are the doubts I have.

At first, yes, the idea would be copying between projects, but that as you say is not maintainable :(

So I have several doubts and I hope you can help me, also if you have a tutorial that explains this, it's good for me too.

I guess if I want to work with github.com and versioning branches, the idea would is the following:

1- Create a directory outside my project with all the packages (Example "packages" directory), include "cms" as one of the directories.
2- Add composer.json and include it to be compatible with PSR-4
3- Attach directory cms from packages to PHPStorm project to work more quickly similar to working on the example in this course.
4- Push to github
5- In projects where I use my CMS package add it to the composer.json with the URL of github and the branch.

Then from here if what I've said is true, when I'm working locally, I'll have to continually push up every change I make to github.com and then update with composer to see if the changes are correct?

Or is there another way to do it?

I have seen that there is "satis", but I forget about this for the moment.

Or when I work locally on the composer file instead of pointing to github.com pointing to the directory and then when I'm done I push the changes to github.com and change the URL(package) to do the deploy?

I'm very sorry for my rookie doubts.

Thank you and great answer.

Reply

Hey CharlES,

> Then from here if what I've said is true, when I'm working locally, I'll have to continually push up every change I make to github.com and > then update with composer to see if the changes are correct?
> Or is there another way to do it?

Please, complete this tutorial first :) Or if you already did - probably you missed some information here. In this tutorial we're talking how to work on bundle in a real project to avoid constant push/pull. IIRC it should be covered here: https://symfonycasts.com/sc...

> Or when I work locally on the composer file instead of pointing to github.com pointing to the directory and then when I'm done I push the changes to github.com and change the URL(package) to do the deploy?

Basically, yes, that's exactly what I mean above. You may require all your bundles in one project linking them from localhost and when you're ready - commit changes and push to GitHub, releasing a new version so the others will be able to pull updated.

Yes, that's a bit of a complex setup, so I'd recommend you to go slowly step by step, start from a one or few packages and see how it goes, get used to it. I think things will be more clear when you will work on them on practice. And yes, that's not an easy thing to do to maintain many packages, but that's doable, and if that's what you need - just go for it.

To start easier, you can probably start with a single "monolith" repo, or a few big repos holding things tight to each other. And then step by step split your project to smaller pieces, extracting them to individual packages with individual versionining. I think it will be easier to start this way then trying to split all your project on little pieces at once, especially if you don't have too much practice on it.

I'm sorry, I can't tell more here, that's a complex topic. Probably try to google on this question, I bet there already should be some good articles on the internet about it explaining it better. We only have bandwidth to answer our tutorial-related questions, and your questions are out of scope unfortunately. I'd recommend you to look at how Symfony or Sylius manage their packages, they probably are the best example of strategy that would fit you. Unfortunately, we only have 1 tutorial about this topic, the current one about create Symfony bundles.

I hope this helps!

Cheers!

1 Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | victor | posted 2 years ago

Thank you very much, that's all I need.

Reply

Hey CharlES,

I'm really happy that it was useful for you. Good luck with your projects!

Cheers!

Reply
Jose carlos C. Avatar
Jose carlos C. Avatar Jose carlos C. | victor | posted 2 years ago

Another thing I can think of is to create a CMS directory and put all the general content there, that is to say the namespace would be App\CMS, and then all the others added as Bundles, is that I look for the best way to replicate my own code.

All this comes as I said before because I have created a website with a FULL CMS with the new Symfony structure. Now I am going to create another website where I will use "almost" all of the previous ones, but I see very cumbersome having to go between all the directories (Controllers, Entity, Events, Helpers, Traits), which is the one I am going to use and my next project that uses a CMS. So what I think is to use Bundle(s). But I'm not quite sure.

Reply

Hey CharlES,

See my another reply above: https://symfonycasts.com/sc...

Btw, I'd recommend you to look at https://github.com/symfony-... - it's a CMF based on Symfony, you can find docs about it here: https://symfony.com/doc/cur... - probably it will give you some good hints how to organize your code.

I hope this helps!

Cheers!

1 Reply

Hi!
How can I add a symfony console command via a bundle?
Is there a good tutorial somewhere?

Reply

Hey M_Holstein

We don't cover that topic but it should be easy to do so. You just need to create the command, give it a name, and then in your services configuration file, you have to enable the autoconfigure and autowiring functionality for you command

Cheers!

Reply

Ok.
But why I do need this line in my service.xml additional to my service config?

<tag name="console.command" command="setup:couchbaseBucket"/>

Without this line my command does not appear in my command list.
I only did find this by try and error. Nothing documented. :(

Reply

That line tells to the Symfony Container to register your command class as a command service with the name setup:couchbaseBucket. Usually, you don't have to do that if you have enabled "autoconfigure" for your project.

1 Reply

I'm not sure, but I think, my project is autoconfigured.
I have this not disabled.
Or must I set this in my service.xml?

Where can I read all this things?

Reply

Hey M_Holstein

Sorry for my late reply. The autoconfigure and autowiring functionality have to be enabled per "services configuration" file, in other words, if you have more than one services.xml in your app, then you will have to add the configuration code that enables those features.

You can find this info in the Symfony docs, you only have to find the topic you want to learn about. Another good place is at the Symfony Slack channel

BTW, when you want to check all the config options of a bundle, you can use the bin/console debug:config command

Cheers!

Reply
Rafael G. Avatar
Rafael G. Avatar Rafael G. | posted 2 years ago

Hi Guys, great work with these videos, they are very helpful. I'm just seem to be stuck on logging. How do I make my reusable bundle to log to the main application log stream? Do I have to autowire monolog to my services?

Reply

Hey Rafael G.

You have to inject the log service by ID but you should make it optional in case no logger is installed - https://github.com/symfony/...

Sometimes people also like to log to a specific "channel" - it gives users more flexibility to handle your logs in a different way. That's done with a tag https://github.com/symfony/...

Cheers!

Reply
Rafael G. Avatar

That worked! Thank you so much.

1 Reply
Rafael G. Avatar
Rafael G. Avatar Rafael G. | MolloKhan | posted 2 years ago | edited

MolloKhan so now I have a similar issue with unit testing, I have an integration test similar to the one described in chapter 11 and one of my services has ValidatorInterface as DI, it is installed but it's null in the constructor for the tests, I think I have to register the bundle so it becomes available, but I don't think it's related to any bundle. Do you have any idea how I can make it available on my test with DI?

Reply

Hey Rafael,

Let me clarify, somehow in your test the validator object that's passed as a dependency in your service you're testing is equal to null instead of a real object? Am I correct? But how do you instantiate it? Are you trying to get it from the DI container? And why don't you want to create it manually?

Cheers!

Reply
Rafael G. Avatar

Hi Victor, thank you so much for getting back to me. Yes you are correct, my bundle has a service that has the validator passed as DI.
I'm booting up the kernel, loading and registering my bundle and monolog, when I call one of my services that has monolog with DI it works because I'm registering monolog's bundle along with mine for this test, but now for the service that uses the validator I get null instead of ValidatorInterface.

My function looks like this


public function testServiceWiring()
{
$kernel = new MyBundleKTestingKernel();
$kernel->boot();
$container = $kernel->getContainer();

$myservice = $container->get('myserviceid');
}

I'm guessing I have to load the validator service somehow, but not sure where and how so it works when my service gets loaded.

Reply

Hey Rafael,

Ah, ok! So, yes, you can mock the validator in case you want to write a unit tests for some piece of your code. But in case you want to add an integration or functional test - you would need to register it. I suppose it should be done the same way as you did for monolog service? :) How did you register it? Just do the same steps for validator. But there's no validator bundle, the integration of Validator component into Symfony app is made by FrameworkBundle, see "validation" section: https://symfony.com/doc/cur... . So, it looks like you need to configure FrameworkBundle (in particular "validation" option of it) the same way you did it for MonologBundle.

I hope this helps!

Cheers!

Reply
Rafael G. Avatar

Hi Victor,

thank you so much for your help, I actually got it working and just for reference these are the values I used:


public function registerBundles()
{
return [
new MyBundle(),
new MonologBundle(),
new FrameworkBundle()
];
}

And then, for FrameworkBundle, the only service that complains is the uri_signer for the secrets and I solved that like so:


public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(function (ContainerBuilder $container) {
$container->register('myservice', MyService::class);
$container->loadFromExtension('myservice', $this->myBundleParams);

$container->register('uri_signer', UriSigner::class);
$container->loadFromExtension('framework', ['secret' => 'myrandomsecret']);
});
}
1 Reply

Hey Rafael!

Glad you for it working! And thank you a lot for sharing your solution - this might be useful for other users!

FYI, I just wrapped your code inside "code" tags with a special "pre" tags that makes code look better :) So basically, you need to use both "pre" and "code" tags to format blocks of code :)

Cheers!

Reply
Default user avatar

This is great tutorial for reusable my code, thanks...

Reply

Hey Fire01

Thank you for your feedback! We are very happy that you like it! Stay on tune!

Cheers!

Reply
Steven J. Avatar
Steven J. Avatar Steven J. | posted 3 years ago

What method should be used to load global twig variables from a 4.2 symfony bundle?

I would like to add some twig globals to my bundle using src/Resources/config/twig.yaml.

Here is what my twig.yml file looks like:

twig:
globals:
my_name: 'Jane Doe'

In my DependencyInjection Extension file I include:

$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yaml');
$loader->load('twig.yaml');

I get the following error:

There is no extension able to load the configuration for "twig" (in src/DependencyInjection/../Resources/config/twig.yaml).
Looked for namespace "twig", found none

I try from my services.yaml file

imports:
- { resource: twig.yaml }

services:...

I get the same error.

Thanks!

Reply

hey @Steve Johnson

I think you should look at this link https://symfony.com/doc/cur... it will be better to use this method to modify twig configuration from your custom bundle!

Cheers!

Reply
Steven J. Avatar

Thanks! That worked perfect.

Reply
Vinoth K. Avatar
Vinoth K. Avatar Vinoth K. | posted 4 years ago

Hi! This tutorial is awesome to quickly setting up a custom bundle. It will be more helpful if you explained something about twig file rendering from the custom bundle.

Reply

Hey Vinoth K.!

Ah, an excellent topic! And one that has a very simple answer:

1) Put you template files in a Resources/views directory in your bundle - e.g. Resources/views/admin/index.html.twig
2) When rendering, refer to them with a special "@BundleName" syntax... where BundleName is the name of your bundle, but without the last word Bundle. For example, if you want to render the above template from your SymfonyCastsHelloWorldBundle, you would use:


return $this->render('@SymfonyCastsHelloWorld/admin/index.html.twig');

That will work, and you will be able to override any templates in your project by following the normal "override mechanism" - https://symfony.com/doc/cur...

I hope that helps!

Cheers!

1 Reply
Cat in space

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