Drupal 8 and Symfony 2

These are my notes from a talk at DrupalCon Denver 2012 by Fabien Potencier @fabpot. [My other notes from DrupalCon Denver]

Fabien is the lead developer and founder of Symfony2, as well as Twig, Swiftmailer, Silex, Pirum, Sismo, Pimple and Goutte.

Lukas Kahwe Smith @lsmith works for L//P in Switzerland. He was the PHP 5.3 release manager and Co-lead of PHPCR.

We are both fairly experienced PHP developers, but we would probably be totally lost if we looked into the core of Drupal. It has taken us a lot of time to learn more about the "Drupal Way." Whatever we learn along the way with Drupal will only help us with Drupal, as opposed to growing our programming experience.

Other people have adopted OO design patterns much more openly. I do know a little bit about Drupal's history, and I know that in Drupal 7 the amount of OO code has risen by a great amount. The language of PHP is evolving towards OO and lots of performance improvements are being made.

You're not doing it wrong; obviously Drupal is wildly successful. I think this talk at Drupalcon is actually the biggest Symfony talk ever... and we even have our own conference! [applause]

What Could be Gained with Symfony2?

Drupal is redefining itself. In Drupal 8, there's a goal to refactoring towards a "framework" core, and Symfony2 can help.

More importantly, Drupal is a CMS, and this is where your resources should be focused, not on the lower-level items that people have done before.

Also, Drupal will be more easily integrated with other applications if these standard paradigms are adopted.

Announcement

Obviously the blog post by Dries has a certain significance; it sounds like he's saying that it's time to use Symfony2.

Symfony2 is really keen on this collaboration. Symfony2 is MIT licensed which is GPL compatible. Symfony2 development is very transparently managed on github. We are the most-watched PHP project, with 10 daily pull requests. We want to make sure Symfony2 works wtih Drupal, phpBB, and other projects.

For example, Symfony2 provides a concept called "flash messages" to pass messages to the next request. Drupal_set_message() could be replaced by this if we make it a little more flexible. Another CMS which uses Symfony2 was able to give some feedback on this also.

What is Symfony?

Symfony version 1 is a totally different product. Nothing applies to Symfony2. When I talk about Symfony, I always mean version 2.

Symfony is a set of PHP components. Each component solves a common development problem, from abstracting HTTP messages to creating command line tools or converting XPATH selectors. Symfony tries to make these components flexible and extensible.

Symfony is an object-oriented set of classes that uses PHP 5.3 features. This set of classes provides tools to every PHP developers. As of today, we have 21 PHP components, some more important than others. I'll talk about some of them today.

Symfony is also a full-stack web framework, which is probably the most popular part of the Symfony project. I won't talk about this today, because Drupal is only adopting a small subset of the Symfony components.

  • Official website: http://symfony.com/components
  • Github repository: https://github.com/symfony
  • Mailing list: https://groups.google.com/group/symfony2

Symfony tries very hard to help communities with their low-level architecture. We already have quite a few projects using them:

  • Behat
  • Doctrine
  • Propel
  • PHPUnit
  • Jackalope
  • easybook
  • Midgard CMS
  • Zikula
  • phpBB

What about Drupal 8?

Drupal will use quite a few Symfony components. I want to focus on the main ones. As of today, we know they will use:

  • ClassLoader
  • HttpFoundation
  • HttpKernel
  • Routing
  • EventDispatcher
  • DependencyInjection

I will talk about all of these except DependencyInjection, because it will take too long.

The first step is to clone the git repository to download the Symfony components. You can also download an archive from github, use pear, or use composer.

The good news is that as a Drupal developer, by default, the Symfony components are included directly into Drupal 8.

ClassLoader

This gives you a way to upload all your PHP classes so that the files where the classes are defined are only loaded on demand when needed. ClassLoader can load any class as long as you follow the naming conventions. This convention is described at http://symfony.com/PSR0. Drupal is adopting these conventions.

HttpFoundation

This is probably the most important component in Symfony, because of HTTP. Symfony leverages open standards as much as possible. As web developers, we are using HTTP all the time. Please read the HTTP/1.1 RFC 2616. HTTP defines:

  1. The user asks for a resource in a browser
  2. The brwoser sends a request to the server
  3. The server sends back a resource to the browser
  4. The browser displays the resource to the user

How do you represent the HTTP request in PHP? Currently, you may use global variables. That is horrible. Using $_GET, using $_SESSION. For example, using $_SERVER['REMOTE_ADDR'] you can get the IP address of the client... unless the client is using a proxy, in which case you have to check the HTTP_X_FORWARDED_FOR header. But this is unsecure! You need to check for a reverse proxy that you trust. Otherwise, it is not secure. So we see that it's a lot of work to just get the client IP.

Plain PHP with global variables is not object oriented, and "Singleton"-like. Overriding global variables is not easy to do. It's a low-level abstraction only. If you want to be secure, you have a lot of work to do.

In Symfony, we have a Response object (as well as a StreamedResponse object) which allows you to build your response to the client. This is also important:

$response->prepare($request)

Of course, we also have session management. Everything works out of the box. We are already compatible with the new PHP 5.4 session handler interface, with forward-compatibility with PHP 5.3. It's our job to take care of all that.

To sum up, the Symfony HttpFoundation container replaces PHP native global variables and functions to allow more sustainable and testable code.

Routing

Drupal has too many routes, but it wants to benefit from the interfaces defined in this component. Using these interfaces means that Drupal will be compatible with all the other projects that use the Routing component.

In code, we define a Route and define a RouteCollection to store the routes. Each route has a unique name. Then, using a UrlMatcher, we are able to match a path for the request and return an array of attributes. For example, "/node/12" will match the "node" route, and call the appropriate controller. This is only about matching incoming requests to be able to know which controller handles the request.

The Symfony routing component also takes care of the other direction. When we want to link to a page, instead of embedding the URL in the page, you give Symfony the unique route name, and Symfony will generate the URL for you. It means you never need to hard-code a URL in your code, and then you can change your routes whenever you want, without updating your code. So there is a clear separation between defining your routes, and using them.

EventDispatcher

This is similar to the Drupal hook system, the biggest difference being that EventDispatcher is object-oriented. I think Drupal 8 will not use the EventDispatcher yet, but will stick with the existing hook system.

Your code dispatches events, and the dispatcher notifies all listeners for the event, and each listener can act on the event. Each event has a name.

HttpKernel

This is the most important Symfony component. Why? HttpFoundation is about the representation of the HTTP messages as defined in the spec. HttpKernel implements the dynamic part of the HTTP specification. It gives a developer a way to handle the conversion of a request to a response.

By default, the HttpKernel defines a workflow by dispatching events during the handling of a request. By default, when the request comes in, the request event is notified. For example, inject some arguments and alter some headers. If a listener returns a response, then we are done, and we go to the last step (the response event) where you can tweak the response the way you want, and the response is sent back to the browser.

If the request event does not return a response, we need to find the controller that is supposed to handle this request. The ControllerResolverInterface is used here; Drupal will use the default implementation. Once the controller is identified, we resolve the arguments and call a controller. If the response is then generated, the response event is called and the response is sent back to the browser.

If not, the view is called, and the view tries to convert the controller's output into a response. During this handling, if there is an exception, in this case, Symfony caches the exception and notifies an exception event. At this point, listeners have the chance to convert the exception to a response.

Finally, once the response is sent to the client, you can listen to the terminate event to do things that do not influence the response, for example, sending emails.

This is the default workflow in the HttpKernel object.

Listeners

Symfony comes with many listeners you can use out of the box. It's very simple to create an ExceptionHandler and subscribe to an event using an ExceptionListener.

Http Caching

If you know how HTTP caching works, you already know how to configure the Symfony cache system. The HTTP spec defines two different caching models. The first one, Expiration, is really simple. You define how long a response is valid by setting the Cache-Control and Expires headers. A new request will not be made until past expiration.

Unfortunately there are many situations where it does not make sense, for example with a heavily dynamic site. In this case, the validation model is better, using the ETag or If-None-Match headers.

You configure cache headers in Symfony like:

$response->setTtl(10);
$response-setClientTtl(10);

Edge Side Includes (ESI)

This was defined by Akamai. Let's take YouTube as an example. One area of the page changes if you are logged in; we can use HTTP Validation. Another part is only refreshed every hour; we can use HTTP Expiration.

Instead of including the content directly into the main page, you use these ESI include tags. The source is actually the URL for embed content. This is why you can have several requests in the workflow... you can say, for this content, you can create a 'sub-response' which makes a new request.

2 Comments

Did you enjoy this post? Please spread the word.