The drop is always movingYou know that saying about standing on the shoulders of giants? Drupal is standing on a huge pile of midgetsAll content management systems suck, Drupal just happens to suck less.Popular open source software is more secure than unpopular open source software, because insecure software becomes unpopular fast. [That doesn't happen for proprietary software.]Drupal makes sandwiches happen.There is a module for that

Learning Symfony / Drupal 8

Submitted by nk on Sun, 2012-07-29 13:49

This is not documentation. This is literally the 30000ft high (I have written it on a Calgary-London flight) overview about Drupal 8 page flow as it stands on July 28, 2012. This doesn't mean it will be fully valid even a week later. Also, I am not writing out all the namespaces for classes, it's not necessary.

index.php now does:

$kernel = drupal_container()->get('httpkernel');
$response = $kernel->handle($request)->prepare($request)->send();
$kernel->terminate($request, $response);

About the drupal_container(). It is a

* Dependency injection container

This is a fancy array of objects. The fanciness is mostly that the objects are only created when retrieved. When adding an object to the DIC we don't add an object but we specify how it will be created: either by specifying a classname and let the DIC call new $classname with it, perhaps with arguments. A fun feature that arguments can be other members of the DIC. Or we can specify a factory class and method. Or, instead of a factory class, we can use an object in the DIC as the factory. Very fancy. Examples from D8 core:

$this->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');

So this registers an object we will be able to retrieve with $this->get('resolver'). When the get() runs, it will create a new object of class Symfony\Component\HttpKernel\Controller\ControllerResolver.

$this->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
->addArgument(new Reference('service_container'))
->setFactoryClass('Drupal\Core\DependencyInjection\ContainerBuilder')
->setFactoryMethod('getKernelEventDispatcher');

So this registers an object we will be able to retrieve with $this->get('dispatcher'). When the get() runs, it will call the method
Drupal\Core\DependencyInjection\ContainerBuilder::getKernelEventDispatcher because that is set as a factory. As we have an argument here, it will be passed to the getKernelEventDispatcher() method. As it's a reference, that means it's something out of the DIC, in this case, the DIC itself. This DIC is called a service container and the thingies we put into the DIC are called services by Symfony.

* About the kernel

The HTTP kernel uses the event dispatcher to call the following events during handle():

- KernelEvents::REQUEST
- KernelEvents::CONTROLLER
- KernelEvents::VIEW
- KernelEvents::RESPONSE

The controller and the view might be skipped if during the request event an event subscriber happens to set a response already. That's mostly for redirects, access denied and such.

* About events

The events are somewhat like hooks just way, way more complicated. We register classes with the dispatcher which calls the getSubscribedEvents of that class which describes the methods to be called on events. Here's an example from the ViewSubscriber class:

static function getSubscribedEvents() {
$events[KernelEvents::VIEW][] = array('onView');
return $events;
}

So when the KernelEvents::VIEW event is dispatched by the HttpKernel:

$this->dispatcher->dispatch(KernelEvents::VIEW, $event);

then the onView method is called with the $event argument. All events get a single $event object which doesn't do much just has setters and getters.

The DIC examples above are from the constructor of Drupal\Core\DependencyInjection\ContainerBuilder and as you can see it creates
a dispatcher by calling the getKernelEventDispatcher which method creates an EventDispatcher object and adds a lot of event listeners. I will just point out the most important ones:

$dispatcher->addSubscriber(new LegacyRequestSubscriber());

This finishes the bootstrap.

$dispatcher->addSubscriber(new RouterListener(new LegacyUrlMatcher()));

The LegacyUrlMatcher calls menu_get_item() for the current path (which was resolved by a listener I skipped).

$dispatcher->addSubscriber(new LegacyControllerSubscriber());

This will take the Drupal router item retrieved in the previous step and call the page callback / page arguments. More precisely, it sets the controller to a closure doing that. After the event, handle() calls the controller, this controller call itself is not an event. We have seen such things all the time in Drupal, a hook before doing something, very familiar.

$dispatcher->addSubscriber(new ViewSubscriber($negotiation));

This is the delivery callback. For example, for HTML requests it calls drupal_render_page().

So now we see what the Drupal 7 page workflow mutated into. And while this might change, knowing the kernel, the DIC, EventDispatcher will stay useful.

Commenting on this Story is closed.

Submitted by Anonymous on Sun, 2012-07-29 15:52.

Great writeup, chx! And yes, conceptually most of that is not going to change drastically, since it's just "this is how Symfony does things".

The main thing that will change soon is the subscriber registration, as I *just* RTBCed the issue to use subscriber services for that. That means the addSubscriber() call will change, but the subscriber classes themselves will not. Also some of the subscribers will be changing around as we continue to refactor. (ViewSubscriber will likely change, and the Legacy* subscribers should go away eventually, etc.)

Nice work!

Submitted by Anonymous on Sun, 2012-07-29 16:50.

Very nice! Does this mean, however, that the traditional hook-based workflow will get replace by an event-driven one? Or will this change be more of a end-user transparent change in core and for modules defining hooks?

Submitted by Anonymous on Mon, 2012-07-30 02:23.

ouch... i guess my, already limited, days of trying to contribute to core are over. That seriously sounded like a foreign language to me :-(

Submitted by Anonymous on Mon, 2012-07-30 06:14.

Do what I do: learn more. Don't let a challenge intimidate you to the point of closing yourself off completely. Learn to adapt (even something that may sound like a foreign language) and you will be a better developer because of it!

Submitted by nk on Mon, 2012-07-30 06:41.

But digging around you realize it's still just PHP.

Submitted by Anonymous on Mon, 2012-07-30 07:44.

Compare to

    protected function doDispatch($listeners, $eventName, Event $event)
    {
        foreach ($listeners as $listener) {
            call_user_func($listener, $event);
            if ($event->isPropagationStopped()) {
                break;
            }
        }
    }

function module_invoke_all() {
  $args = func_get_args();
  $hook = $args[0];
  unset($args[0]);
  $return = array();
  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    $result = call_user_func_array($function, $args);

Submitted by Anonymous on Mon, 2012-07-30 13:23.

The Symfony flow is more abstract, so I can definately relate to folks who feel frustrated and abandoned by it. I also urge you to persist! The best way for me to learn is to step through a request in the debugger. If you never setup XDebug, you really ought to invest the effort. You can use PHPStorm or Netbeans or Komodo or Eclipse on the client side. It is a mind blowing experience to see a request wind its way to the browser. You can study it for hours and days and still stumble across interesting new twists.

Also, it seems that we are finally closing in on Drupal Pipes.