Journey to the Center of Symfony: The Dependency Injection Container

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-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.