Step 30: Discovering Symfony Internals

Discovering Symfony Internals

I like to understand how things work behind the scenes. And I have always been fascinated by tools that help me understand how things work. The first time I used a step by step debugger or the first time I discovered are magical memories.

Would you like to better understand how Symfony works? Time to dig into how Symfony makes your application tick. Instead of describing how Symfony handles an HTTP request from a theoretical perspective, which would be quite boring, we are going to use Blackfire to get some visual representations and use it to discover some more advanced topics.

You already know that all HTTP requests are served by a single entry point: the public/index.php file. But what happens next? How controllers are called?

Let’s profile the English homepage in production with Blackfire via the Blackfire browser extension:

Or directly via the command line:

  1. $ blackfire curl `symfony env:urls --first`en/

Go to the “Timeline” view of the profile, you should see something similar to the following:

From the timeline, hover on the colored bars to have more information about each call; you will learn a lot about how Symfony works:

  • The Kernel::handle() method handles the request;
  • It calls the HttpKernel that dispatches some events;
  • The first event is RequestEvent;
  • The ControllerResolver::getController() method is called to determine which controller should be called for the incoming URL;
  • The ControllerResolver::getArguments() method is called to determine which arguments to pass to the controller (the param converter is called);
  • The ConferenceController::index() method is called and most of our code is executed by this call;
  • The ConferenceRepository::findAll() method gets all conferences from the database (notice the connection to the database via PDO::__construct());
  • The method renders the template;
  • The ResponseEvent and the FinishRequestEvent are dispatched, but it looks like no listeners are actually registered as they seem to be really fast to execute.

The timeline is a great way to understand how some code works; which is very useful when you get a project developed by someone else.

Now, profile the same page from the local machine in the development environment:

    Open the profile. You should be redirected to the call graph view as the request was really quick and the timeline would be quite empty:

    Step 30: Discovering Symfony Internals - 图2

    Do you understand what’s going on? The HTTP cache is enabled and as such, we are profiling the Symfony HTTP cache layer. As the page is in the cache, HttpCache\Store::restoreResponse() is getting the HTTP response from its cache and the controller is never called.

    Disable the cache layer in public/index.php as we did in the previous step and try again. You can immediately see that the profile looks very different:

    The main differences are the following:

    • The TerminateEvent, which was not visible in production, takes a large percentage of the execution time; looking closer, you can see that this is the event responsible for storing the Symfony profiler data gathered during the request;
    • Under the ConferenceController::index() call, notice the SubRequestHandler::handle() method that renders the ESI (that’s why we have two calls to Profiler::saveProfile(), one for the main request and one for the ESI).

    Explore the timeline to learn more; switch to the call graph view to have a different representation of the same data.

    As we have just discovered, the code executed in development and production is quite different. The development environment is slower as the Symfony profiler tries to gather many data to ease debugging problems. This is why you should always profile with the production environment, even locally.

    Some interesting experiments: profile an error page, profile the / page (which is a redirect), or an API resource. Each profile will tell you a bit more about how Symfony works, which class/methods are called, what is expensive to run and what is cheap.

    By default, Blackfire removes all method calls that are not significant enough to avoid having big payloads and big graphs. When using Blackfire as a debugging tool, it is better to keep all calls. This is provided by the debug addon.

    From the command line, use the --debug flag:

    In production, you would see for instance the loading of a file named :

    Where does it come from? SymfonyCloud does some optimizations when deploying a Symfony application like optimizing the Composer autoloader (--optimize-autoloader --apcu-autoloader --classmap-authoritative). It also optimizes environment variables defined in the .env file (to avoid parsing the file for every request) by generating the .env.local.php file:

    1. $ symfony run composer dump-env prod

    Blackfire is a very powerful tool that helps understand how code is executed by PHP. Improving performance is just one way to use a profiler.

    Blackfire timelines and call graphs allow developers to visualize which files/functions/methods are executed by the PHP engine to better understand the project’s code base.

    Another way to follow code execution is to use a step debugger like . A step debugger allows developers to interactively walk through a PHP project code to debug control flow and examine data structures. It is very useful to debug unexpected behaviors and it replaces the common “var_dump()/exit()” debugging technique.

    First, install the xdebug PHP extension. Check that it is installed by running the following command:

    1. $ symfony php -v

    You should see Xdebug in the output:

    Step 30: Discovering Symfony Internals - 图5

    Now, enable the debug mode of Xdebug:

    php.ini

    1. [xdebug]
    2. xdebug.mode=debug
    3. xdebug.start_with_request=yes

    By default, Xdebug sends data to port 9003 of the local host.

    Triggering Xdebug can be done in many ways, but the easiest is to use Xdebug from your IDE. In this chapter, we will use Visual Studio Code to demonstrate how it works. Install the PHP Debug extension by launching the “Quick Open” feature (Ctrl+P), paste the following command, and press enter:

    Create the following configuration file:

    .vscode/launch.json

    From Visual Studio Code and while being in your project directory, go to the debugger and click on the green play button labelled “Listen for Xdebug”:

    If you go to the browser and refresh, the IDE should automatically take the focus, meaning that the debugging session is ready. By default, everything is a breakpoint, so execution stops at the first instruction. It’s then up to you to inspect the current variables, step over the code, step into the code, …

    When debugging, you can uncheck the “Everything” breakpoint and explicitely set breakpoints in your code.

    If you are new to step debuggers, read the , which explains everything visually.

    Going Further


    This work, including the code samples, is licensed under a license.