Journey to the Center of Symfony: The Dependency Injection Container

Buy Access

Let's not just use Symfony, let's conquer it! In this series, we're going to rip open the code that builds and boots Symfony's Container to see how it really works. Yes, this is geeky, and yes it's sweet!

Besides the occassional dinosaur, expect to do the following on our adventure:

  • Build a container from scratch
  • Master the ContainerBuilder and Definition objects: the backbones to the container
  • See how Definitions are built with YAML
  • Dump the container to PHP code and use it
  • See how Symfony builds the container (woh)
  • Learn how DependencyInjection Extension classes work (i.e. the guys who process all that config.yml stuff)
  • Discover existing compiler passes and create your own

Your Guides

Ryan Weaver Leanna Pelham

Questions? Conversation?

  • 2016-12-15 weaverryan

    Hey Eddy!

    Yea, these are all good! Some of them are "softer" question - i.e. not just "how" to use something, but the *when* and when not to, etc. These are a bit harder to answer, well, other than trying to make screencasts that show as many examples a possible. But, these are really interesting, so let me address a few right here:

    1) About Symfony being service-oriented, you're absolutely right. And actually, this is entirely on purpose, and $service->driveToCustomer($car) is generally considered to be superior to $car->driveToCustomer(). I'm not sure what you meant by more procedural, however - both lines end up being a method call, just a different method call. The reason this is "better" is really two things. First, it's just a by-product of no longer using global variables. If "driving" requires us to need a database call, then we can't do that inside of Car, unless we inject the database connection. So, instead of saying "let's inject services into *all* objects if they need them", we instead organize classes into two types: simple classes that only hold data and do (almost) no work, and classes that hold no data (maybe just some tiny configuration) and do work. That little philosophy just ends up cleaning a lot of code. Second, if you have $transportationManager->driveToCustomer($car, $customer), it's quite possibly a bit more flexible. For example, I could very easily add another method $transportationManager->walkToCustomer($person, $customer). Probably, a lot of the logic between these two methods/actions would be shared. But, if we had buried it into Car, then we'd need to do some more work to also suppose this in Person. Anyways, I don't know if this really gets at your question, but hopefully it does! :)

    2) About Bundles: yes! In a perfect world, if I wanted to share some code, I would do two things: (A) I would put all my PHP classes that I want to share into a directory and release just that as a library. Then, anyone - Symfony or not - could use this. Then (B) I would create another directory and include just the Symfony-specific configuration needed to integrate those classes into Symfony. This bundle would have the library has a dependency. And actually, this *does* happen out in the real-world - e.g. https://github.com/knplabs/knp... and https://github.com/knplabs/knp.... It's an oversimplification, but for the most part, the only purpose of a bundle is to allow for an automatic way for those PHP classes to be registered as services into the container.

    3) About Doctrine and switching from MySQL to something else. Really, only switching from MySQL to some other relational database (e.g Postgresql) is supported. However, in theory, Doctrine's ODM (which talks to document databases) and the Doctrine ORM share many interfaces, like the "ObjectManager" - https://github.com/doctrine/co.... So, *technically* - though it's not as simple as this, these DB types are quite different - if you only relied on the methods in the ObjectManager and other shared interfaces, you could switch. Heck, there's even an OXM, which in fact does use XML as a backend: https://github.com/doctrine/ox...

    4) Events! Ah, this is a GREAT question, and so subjective. I rarely use them in my projects - they make your code a bit harder to read, and since it is YOUR code, you could just write the code you need where you need it, instead of dispatching an event and attaching multiple listeners. However, I know some companies that use events a lot in their code. The slam dunk for event usage is in re-usable code: it allows you to add hook points, because you of course have no idea what the user might need to do in that spot. We DO use events in a few places however. Sometimes, after something happens in your code, you need to do many, seemingly unrelated things afterwards. I use events here. Let me give you an example: on KnpU, after a user completes a challenge or video, we dispatch an event (e.g. user.challenge_completed or user.video_completed). Then, we have several listeners that check for several different, seemingly unrelated things - e.g. checking to see if a course is now completed, or if a badge should be awarded. Without events, I would need to check for both of these things... in both places (i.e. after a challenge is completed and after a video is completed).

    Phew! I hope this helps - it's fun for me to think about these things - great question!

  • 2016-12-11 Eddy de Boer

    I was thinking about the following --but maybe there are already tutorials about the topics, pls correct me--:
    - Doctrine: what does really happen behind the scenes?
    - OO: Symfony is service oriented, making entities, our domain layer, empty bags, hence forcing a more procedural style of programming (e.g.: $service->driveToCustomer($car) instead of $car->driveToCustomer()), I would love to hear the thoughts on a Symfony guru on that one
    - Bundles are all about re-use, but they can mostly be re-used in Symfony systems. Is there a way to make them more generic?
    - Doctrine: how to switch from mysql to, let's say something wild, Document Database, or XML?
    - Events: when to use events? And: when not to?

    Thanks so far!

  • 2016-12-10 weaverryan

    Hey Eddy!

    Ah, so glad you like them! These were my favorite to write - I *love* this stuff. We don't have any other ones planned... but mostly because I'm not sure what other parts of Symfony would lend themselves to going "deep" like this. The other most complex components, forms and security, are covered really well in our main Symfony series. Anything you had in mind? I might be totally overlooking something really interesting.

    Cheers!

  • 2016-12-10 Eddy de Boer

    Thanks for this tutorial, I find the 'Go deep' ones very interesting...so far there are only 2. Any plans to have more in the near future?

  • 2016-09-30 weaverryan

    Hey Jim!

    I just saw this message from you :). In general, I think the approach makes sense - it would certainly work, and as you said, the only issues are security (which is a big issue, but at least, an obvious one). But, I don't think Symfony helps you here in any special way - you're still just writing some code that reads some things from the database, executes them, and loads up some status assets that live in the filesystem. That could be done in flat php or any other language. Symfony is just some tools on top of PHP, so it's not giving you any special help here (it has all its normal benefits of giving you organization and tools to make your life easier, but nothing special to your specific situation). So yes, it would come down to trying to figure out security... if you can at all :). If you let people write actual code, then you are going to have security risks. We actually allow this on our site - some of our lessons have coding challenges where you can write PHP and we execute it. This is safe only because we isolate you in a docker instance, and kill that "machine" right after you run your code. It can be done, but it's a tough problem for sure!

    Cheers!

  • 2016-09-30 weaverryan

    Hi Jim!

    I don't think you're missing anything, but perhaps you're expecting too much :). I would equate Symfony to a set of power tools. It says nothing about what you could build with those power tools or how you should build them (my suggestions so far about a database were just me trying to understand your problem-space and suggest some possible solutions). Actually, technically, Symfony doesn't talk to the database at all - it uses Doctrine (and external tool to do that). So, you can build anything in Symfony, or PHP, or any other language. But, if you think that Symfony might offer you some partially-built solution for your problem space, it definitely won't. Yes, Symfony does have a lot of reusable open source bundles, but your framework sounds quite advanced and quite complex: so that would be complex code. Drupal, on the other hand, is like those same "power tools" plus some high-powered machines that are good at producing certain type of applications more easily. Will something in Drupal be able to give you a head-start, I'm not sure.

    Anyways, I'm happy your enjoying your lessons, and yes, Symfony *can* help you build this, but it won't give you any great step forward for free, but I also doubt that nay other language will either.

    I hope that helps!

  • 2016-09-27 Jim Fuqua

    Ryan do you think the following approach would work to minimize the use of files?

    1. Create a bundle in Symfony. Name the bundle something like “DummyBundle”.
    2. Put in the controller for DummyBundle a PHP file that would use some of the PHP commands described in http://php.net/manual/en/ref.e... to call shell scripts to erase all files in the DummyBundle and recreate the files with contents from the database.
    3. The database would have a lesson ID linking a tables containing javascript files; containing twig files; containing links to SVG files with the SVG files themselves in a separate table; CSS files and a table containing links to png files stored as files on a separate server.

    If this approach could eliminate all lesson bundles stored as files it might be scalable. Security issues would exist, but lessons are not like money. If the DummyBundle could be in some sort of sandbox or even the entire Symfony instance in some sort of security sandbox protecting the server it would not make much difference if a person screwed up a lesson. It would be important that they not hijack the server.

    Would it have a chance? I may be too old to learn functional programming with Clojure.

    Jim

  • 2016-09-27 Jim Fuqua

    I don't see how Symfony is putting programs in a "db program table" if they are part of a bundle in javascript files in a js directory which is either in web/ or in a bundle's public/js directory. It appears that they are in files and directories and not in a database.

    It appears that mixing runnable code into and out of a database in PHP might be unusual and difficult. It might be possible with exec(), but would not be the normal course of things.

    Perhaps Clojure or Scheme would be a better fit than PHP. Clojure and Scheme were designed to mix code and data from the start. I don't know how good Clojure and Scheme are in serving web pages.

    I don't have large numbers of lessons now, but have been writing what I hoped would be an open-source framework that would allow many people to add lessons easily with great flexibility like blog entries can be easily added to Drupal. Lessons that teach a single concept can usually be designed, constructed and tested in a few hours, but to be useful they must be storeable, retrievable, organizable and administerable as discrete separate parts with only interaction with a student.

    The problem seems to be that any lesson of any quality will have code and require the user to fill in a blank or drag and drop or click on something with the code reacting to the student input. A blog entry is internally immutable and does not contain reactive code. A blog entry can be edited by external action but that is not like taking input and automatically grading the input and storing the result of the grading in the database and then automatically going to the next lesson without human involvement.

    I started looking at Drupal and Symfony because as content management systems I thought they could keep everything in a database. Bundles in directories would probably work for a few hundred pages and perhaps a thousand pages but ultimately is is more unwieldy than database tables.

    A company in Norway, https://h5p.org, is building some great tools to make lessons in Drupal 7, but there are not a lot of open-source lessons.

    Unless there is a Symfony bundle or a Drupal module that will store other functioning bundles or functioning modules in a database instead of directories, I may be pursuing a lost cause trying to create large scale multi-subject teaching tools with Drupal and Symfony.

    What am I missing? I like Symfony and like your lessons. Symfony is just fine for static data. I just don't see how directories and files can store the quantity of dynamic lessons that would be needed for flexibility.

    Jim

  • 2016-09-25 weaverryan

    Hey Jim!

    Yea, this can be a lot of stuff to learn at once :). It will pay off, but it's not easy! I'm guessing you have already, but just in case you haven't, also check out our OO track: http://knpuniversity.com/track.... And as I always tell people, when you're learning Symfony, you're not really learning Symfony - you're tackling standard skills that'll follow you no matter where find yourself code. I really mean that :).

    Ok, so you mentioned that you ported 2 procedural lessons into Symfony and then mentioned two Twig template files. So, I may not understand your current lessons very well - how does your procedural PHP app work? Do you have the lessons stored in the database somehow? Or are all the 10,000 lessons just individual files or functions somewhere? It sounds *weird* to me so far... so that's why I think I don't understand!

    But, from the little that I know - regardless of what framework or language you use - it seems like you would have a "program" table and a "lesson" table. The "lesson" table would have a program_id foreign key back to the "program" table. Then, every program can have many lessons. You would do something similar with assignments and tests. In Doctrine, these would be a Program entity and a Lesson entity. The Lesson entity would have a ManyToOne relationship back to Program. If a lesson also has related assets, usually you store the filename of the asset (e.g. digram1.png) on a field(s) in the table and then store the asset wherever you want to, which yes, could be as simple as in the web/ directory, but you could also put the Assets up on Amazon's S3.

    Let me know if that helps!

    Cheers!

  • 2016-09-24 Jim Fuqua

    Thanks Ryan. I have been enjoying your lessons on Symfony, Twig, Drupal and Doctrine. My background is straight procedural PHP. Learning this much simultaneously is quite a task.

    You said “the number of lessons/pages would be something that's stored in the database: as the number of records grow in your database, the size of the container won't increase.”.

    I don’t understand. I have tried to port some of my procedural PHP lessons into symfony. I put one in /app/resources/views/left_right/show_left_right.html.twig. I copied your genus_aqua structure.

    For the other I made a bundle: /src/ClockwiseCounterclockwiseBundle/Resources/views/Default/index.ClockwiseCounterclockwise.html.twig. These appear to be Directories and files outside any database. This approach won’t scale. The program would not be manageable with 10,000 lessons that way much less 100,000 all in separate directories and files.

    How do I get them into a database? I would guess that the ideal way would be for each lesson or group of related lessons to be in a stand-alone bundle with that bundle in a database so that it could be loaded and administered to a student as needed. The program would be like a machine gun with the lessons like the bullets except that each bullet would be a unique single-concept lesson or small group of lessons related to a single concept.

    Assignments and tests could then be highly flexible database entries themselves and not closely coupled to other lessons or the administering program. Storing assets for all lessons in web/ seems to be the epitome of content coupling that is partially but not fully cured with Assetic.

    Thank you for sharing your deep knowledge of Symfony. It would be unfortunate to spend a lot of time learning something that is unsuited to the task. Hopefully Symfony is flexible enough to make lessons decoupled data at times and integral parts of the program only when needed.

    Jim

  • 2016-09-23 weaverryan

    Hi Jim!

    Fortunately, the container and the number of lessons/pages end up being two different ideas :). The container will simply hold all of your useful objects in one convenient place. To give a rough, but basically accurate analogy, your container will grow as the amount of actual PHP code in your application grows. And, for the most part (and by design) the container can grow very large without having any performance impact. On the other hand, the number of lessons/pages would be something that's stored in the database: as the number of records grow in your database, the size of the container won't increase. So, the limitations or performance impacts from storing *many* lessons are just those of your database and how you write the code that interacts with it (e.g. obviously if you tried to load 10k lessons at once and render them on one page, that'll be slow).

    It sounds like you're coming from a Drupal background? If so, the Entity framework that's used to load data has improved, but hasn't changed *all* that much. If you are (or aren't) having problems with these many records in Drupal 7, then you equally will (or will not) have problems in Drupal 8.

    Let me know if that helps!

    Cheers!

  • 2016-09-23 Jim Fuqua

    What about the capacity of such containers? For an educational program where the contents of the container are finite single-concept single-page lessons could Symfony or Drupal handle 10,000 or even 100,000 lessons/pages? How do they scale? Multiple lessons in one container or multiple lessons per container and multiple containers? Practical limits? All of K-12 public school lessons/concepts could approach or exceed 1,000,000 in 13 years of school.

  • 2016-01-26 weaverryan

    It is a good practice :). Well, specifically, you should unit test everything you can... but sometimes it's just good to do a quick functional/integration test that actually boots the kernel to make sure everything is wired together correctly. Here's an example from one of my bundles: https://github.com/knpuniversi..., which uses this kernel: https://github.com/knpuniversi....

    Cheers!

  • 2016-01-21 damdoudidam

    Thanks for your comment. I finally mock it before your response. This is the good way for test my extension. But I'm keeping by my side this trick of KernelTestCase. I did this way (with a kernel) for create an empty application in my bundle to allow people to test
    it (manually or automatically) outside of an actual application. But I don't know if it's a good practice...

  • 2016-01-20 weaverryan

    Hey, thanks!

    Your setup for creating the "test" kernel is correct :). Actually, I often use Symfony's KernelTestCase as a base class for my tests. Then you can simply say:


    self::bootKernel();
    $container = self::$kernel->getContainer();

    But really, it's the same thing. About your questions, why and where are you calling $extension->load()? Are you trying to test your extension class? If so, you do *not* need to create the kernel like my code above (or your code). That container is a fully-built, finished Container. If you want to test your extension class, simply create a new ContainerBuilder (or mock it) and pass that in:

    $builder = new ContainerBuilder();
    $extension->load($configs, $builder);

    Cheers!

  • 2016-01-20 damdoudidam

    Hi, great tutorial !

    I have a question : I want to add a standalone kernel for unit testing in a bundle. When in setUp function I do :

    $kernel = new AppKernel('test', true);

    $kernel->boot();

    $container = $kernel->getContainer();

    I have a appTestDebugProjectContainer instance for $container and now I understand why. But for example, Symfony wants ContainerBuilder instance when we call $extension->load(array $configs, ContainerBuilder $container). So Symfony kick me out. How can I fix this ?

  • 2015-09-23 Jules Truong

    Awesome tutorial as the first one was. Congrats here !

  • 2015-06-12 Shairyar Baig

    I am starting to get my head around the Service and The Dependency Injection after watching this tutorial but still a long way to go, may be when you guys get time try to create a tutorial that includes real life examples.