Oct 23 2015
Oct 23

One of the big trends during the Drupal 8 creation has been the replacement of info hooks by two main mechanisms: annotations, and YAML files. In that light, hook_drush_command(), as the CLI equivalent of the late hook_menu, replaced by various YAML files, looks just like a perfect candidate for replacement by a commands section in some mymodule.drush.yml configuration file. Turns out it is incredibly easy to achieve. Let's see how to kill some hundred lines of code real fast !

The goal

Just like its hook_menu() web counterpart, hook_drush_command() defines controllers, the main difference being that it maps them to CLI commands instead of web paths. Its structure is that of a very basic info hook: a nested array of strings and numbers, which maps neatly to a YAML structure. Consider the canonical sandwich example from Drush docs.

hook_drush_command mymodule.drush.yml <?php
function sandwich_drush_command() {
 
$items = array(); // The 'make-me-a-sandwich' command.
 
$items['make-me-a-sandwich'] = array(
   
'description' => "Makes a delicious sandwich.",
   
'arguments' => array(
     
'filling' => 'The type of the sandwich (turkey, cheese, etc.). Defaults to ascii.',
    ),
   
'options' => array(
     
'spreads' => array(
       
'description' => 'Comma delimited list of spreads.',
       
'example-value' => 'mayonnaise,mustard',
      ),
    ),
   
'examples' => array(
     
'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
    ),
   
'aliases' => array('mmas'),
   
// No bootstrap at all.
   
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  );
   
?>
commands:
  # The 'make-me-a-sandwich' command.
  make-me-a-sandwich:
    description' => "Makes a delicious sandwich."
    arguments:
      filling: 'The type of the sandwich (turkey, cheese, etc.). Defaults to ascii.'
    options:
      spreads:
        description: 'Comma delimited list of spreads.'
        example-value: 'mayonnaise,mustard'
    examples:
      'drush mmas turkey --spreads=ketchup,mustard': 'Make a terrible-tasting sandwich that is lacking in pickles.'
    aliases: ['mmas']
    # No bootstrap at all.
    bootstrap: -1
    

The trick

There is no trick ! Well, almost... assuming that simple format for the mymodule.drush.yml file, all it takes is loading it. This can even be entirely generic, like this : <?php
use Symfony\Component\Yaml\Yaml;/**
* Implements hook_drush_command().
*/
function beanstalkd_drush_command() {
 
$file = preg_replace('/(inc|php)$/', 'yml', __FILE__);
 
$config = Yaml::parse(file_get_contents($file));
 
$items = $config['commands'];
  return
$items;
}
?>

Since your drush plugin for module mymodule is named mymodule.drush.inc (or mymodule.drush.php if you write them like me), the name of the Yaml file can be deduced from the plugin name. And then, the Symfony Yaml component parses it to a plain array matching the expected hook_drush_command() structure.

This is the mechanism used by the Drupal 8 version of the Beanstalkd module.

The limitations

You may have noticed a small limitation : hook_drush_command() implementations may need to use constants like DRUSH_BOOTSTRAP_NONE. With such a limited implementation, you will need to convert the constants to their values from drush/includes/bootstrap.inc. Should the mechanism come to be included in Drush at some point, a more evolved mechanism will likely provide translation for these constants, too.

Oct 20 2014
Oct 20

How to Build a Drupal 8 Module

Please be aware that due to the development process Drupal 8 has been undergoing at the time of writing, some parts of the code might be outdated. Take a look at this repository in which I try to update the example code and make it work with the latest Drupal 8 release.

With the incorporation of many Symfony components into Drupal in its 8th version, we are seeing a shift away from many Drupalisms towards more modern PHP architectural decisions. For example, the both loved and hated hook system is getting slowly replaced. Plugins and annotations are taking away much of the need for info hooks and the Symfony Event Dispatcher component is replacing some of the invoked hooks. Although they remain strong in Drupal 8, it’s very possible that with Drupal 9 (or maybe 10) hooks will be completely removed.

In this article we are going to primarily look at how the Symfony Event Dispatcher component works in Drupal. Additionally, we will see also how to invoke and then implement a hook in Drupal 8 to achieve similar goals as with the former.

To follow along or to get quickly started, you can find all the code we work with here in this repository. You can just install the module and you are good to go. The version of Drupal 8 used is the first BETA release so it’s preferable to use that one to ensure compatibility. Alpha 15 should also work just fine. Let’s dive in.

What is the Event Dispatcher component?

A very good definition of the Event Dispatcher component can be found on the Symfony website:

The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them.

I recommend reading up on that documentation to better understand the principles behind the event dispatcher. You will get a good introduction to how it works in Symfony so we will not cover that here. Rather, we will see an example of how you can use it in Drupal 8.

Drupal 8 and the Event Dispatcher

For the better part of this article, we will focus on demonstrating the use of the Event Dispatcher in Drupal 8. To this end, we will create a simple demo module (event_dispatcher_demo) that has a configuration form which saves two values as configuration. Upon saving this form, we will dispatch an event that contains the config object and which will allow other parts of the application to intercept and modify it before being saved. Finally, we will do just that by demonstrating how to subscribe (or listen) to these events.

In Drupal 7, this type of modularity is only achieved with hooks. Hooks are being invoked and modules have the option to implement them and contribute with their own data. At the end of this article, we will see how to do that as well in Drupal 8. But first, let’s get on with our demo module.

If you don’t know the basics of Drupal 8 module development, I recommend checking out my previous articles in this series.

The form

The first thing we need is a simple config form with two fields. In a file called DemoForm.php located in the src/Form folder, we have the following:

<?php

/**
 * @file
 * Contains Drupal\event_dispatcher_demo\Form\DemoForm.
 */

namespace Drupal\event_dispatcher_demo\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

class DemoForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormID() {
    return 'demo_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('event_dispatcher_demo.demo_form_config');
    $form['my_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('My name'),
      '#default_value' => $config->get('my_name'),
    ];
    $form['my_website'] = [
      '#type' => 'textfield',
      '#title' => $this->t('My website'),
      '#default_value' => $config->get('my_website'),
    ];
    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {

    parent::submitForm($form, $form_state);

    $config = $this->config('event_dispatcher_demo.demo_form_config');

    $config->set('my_name', $form_state->getValue('my_name'))
      ->set('my_website', $form_state->getValue('my_website'));
      
    $config->save();
  }

}

Let’s also create a route for it (in the event_dispatcher_demo.routing.yml file) so we can access the form in the browser:

event_dispatcher_demo.demo_form:
  path: 'demo-form'
  defaults:
    _form: '\Drupal\event_dispatcher_demo\Form\DemoForm'
    _title: 'Demo form'
  requirements:
    _permission: 'access administration pages'

So now if you point your browser to example.com/demo-form, you should see the form. Submitting it will create and persist a configuration object called event_dispatcher_demo.demo_form_config that contains two fields: my_name and my_website .

The event dispatcher

Now it’s time to work on the form submit handler (the formSubmit() method) and dispatch an event when the form is saved. This is what the new method will look like:

public function submitForm(array &$form, FormStateInterface $form_state) {

  parent::submitForm($form, $form_state);

  $config = $this->config('event_dispatcher_demo.demo_form_config');

  $config->set('my_name', $form_state->getValue('my_name'))
    ->set('my_website', $form_state->getValue('my_website'));

  $dispatcher = \Drupal::service('event_dispatcher');

  $e = new DemoEvent($config);

  $event = $dispatcher->dispatch('demo_form.save', $e);

  $newData = $event->getConfig()->get();

  $config->merge($newData);

  $config->save();
}

So what happens here? After we take the submitted values and add them to the config object like before, we retrieve the event dispatcher object from the service container:

$dispatcher = \Drupal::service('event_dispatcher');

Please keep in mind that it’s recommended you inject this service into your class, but for brevity, we will retrieve it statically. You can read this article about dependency injection and the service container for more information.

Then we create a new DemoEvent object and pass it the $config through its constructor (we have not yet created the DemoEvent class, we will do that in a minute). Next, we use the dispatcher to dispatch an event of our type and assign this action the identifier demo_form.save. This will be used when subscribing to events (we’ll see this later). The dispatch() method returns the event object with modifications made to it so we can retrieve the config values that may or may not have been altered elsewhere and merge them into our original configuration. Finally, we save this object like we did initially.

Before moving onto the event subscription part of our application, let’s create the DemoEvent class we just instantiated above. In a file called DemoEvent.php located in the src/ folder of our module, we have the following:

<?php

/**
 * @file
 * Contains Drupal\event_dispatcher_demo\DemoEvent.
 */

namespace Drupal\event_dispatcher_demo;

use Symfony\Component\EventDispatcher\Event;
use Drupal\Core\Config\Config;

class DemoEvent extends Event {

  protected $config;

  /**
   * Constructor.
   *
   * @param Config $config
   */
  public function __construct(Config $config) {
    $this->config = $config;
  }

  /**
   * Getter for the config object.
   *
   * @return Config
   */
  public function getConfig() {
    return $this->config;
  }

  /**
   * Setter for the config object.
   *
   * @param $config
   */
  public function setConfig($config) {
    $this->config = $config;
  }

} 

As you can see, this is a simple class that extends the default Event class and which defines setters and getters for the config object we will be passing around using this event. And since we created it, let’s also make sure we use it in the file where we defined the form:

use Drupal\event_dispatcher_demo\DemoEvent;

The event subscriber

Now that our form is functioning normally and an event is being dispatched when the form is saved, we should take advantage of that and subscribe to it. Let’s start with the event subscriber class that implements the EventSubscriberInterface. Inside a file called ConfigSubscriber.php (name of your choice) located in the src/EventSubscriber/ folder, we have the following:

<?php

/**
 * @file
 * Contains Drupal\event_dispatcher_demo\EventSubscriber\ConfigSubscriber.
 */

namespace Drupal\event_dispatcher_demo\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ConfigSubscriber implements EventSubscriberInterface {

  static function getSubscribedEvents() {
    $events['demo_form.save'][] = array('onConfigSave', 0);
    return $events;
  }

  public function onConfigSave($event) {

    $config = $event->getConfig();

    $name_website = $config->get('my_name') . " / " . $config->get('my_website');
    $config->set('my_name_website', $name_website);
  }

}

So what happens here? The EventSubscriberInterface has only one required method called getSubscribedEvents(). This method is used to register events and callbacks to these events. So above we registered the callable onConfigSave() (found in the same class below) to the event dispatched with the identifier of demo_form.save. And in the callback method we simply add another value to the config object (based on a concatenation of the existing two values). The latter part is just for our demo purposes: here you can do what you want.

When we subscribed our onConfigSave() method to listen to the demo_form.save event, we passed a weight of 0. If you register multiple callbacks to the same event, this weight becomes important (the higher the number, the earlier it gets called). And if a callback alters the same values as one triggered before, they will get overridden. It’s good to keep this in mind.

Now in order for this event subscriber to work, we need to define it as a service and give it the event_subscriber tag. So in a file called event_dispatcher_demo.services.yml found in the root folder of our module, we will have this:

services:
  event_dispatcher_demo.config_subscriber:
    class: Drupal\event_dispatcher_demo\EventSubscriber\ConfigSubscriber
    tags:
      - { name: event_subscriber }

This is a simple service definition with the right tag that will make the container automatically instantiate an object of this class whenever the dispatcher is in play. And that is pretty much it. Clear the cache and if you now save the form again, the configuration object that gets saved will always contain a new value that is based on the first two.

Hooks

In the final part of this article we will demonstrate the use of hooks to achieve a similar goal.

First, let’s change the form submit handler and instead of dispatching events, we will invoke a hook and pass the config values to it. This is what the new submitForm() method will look like:

public function submitForm(array &$form, FormStateInterface $form_state) {

        parent::submitForm($form, $form_state);
        
        $config = $this->config('event_dispatcher_demo.demo_form_config');
        
        $config->set('my_name', $form_state->getValue('my_name'))
          ->set('my_website', $form_state->getValue('my_website'));
        
        $configData = $config->get();
        $newData = \Drupal::service('module_handler')->invokeAll('demo_config_save', array($configData));
        
        $config->merge($newData);
        
        $config->save();
}

We are not using any event objects nor the dispatcher service. Instead, we retrieve the Module Handler service that contains the invokeAll() method used to invoke hook implementations from all modules. This is essentially replacing the Drupal 7 module_invoke_all() helper. And again, it is recommended to inject this service, but for brevity, we’ll retrieve it statically.

The hook implementation invoked in our case is hook_demo_config_save and it gets one parameter, an array of values pulled from our config object. Inside $newData we will have an array of values merged from all the implementations of this hook. We then merge that into our config object and finally save it.

Let’s quickly see an example hook implementation. As with Drupal 7, these can only be in .module files:

/**
 * Implements hook_demo_config_save().
 */
function event_dispatcher_demo_demo_config_save($configValues) {

  $configValues['my_name_website'] = $configValues['my_name'] . " / " . $configValues['my_website'];

  return $configValues;

}

As you can see, we are adding a new value to the config array that will later be merged into the object getting persisted. And we have essentially the same thing as we did with the event dispatcher.

Conclusion

In this article we have taken a look at how the Symfony Event Dispatcher component works in Drupal 8. We’ve learned how flexible it makes our application when it comes to allowing others to extend functionality. Additionally, we’ve seen how the invoked hooks work in the new version of Drupal. Not much has changed since Drupal 7 in this respect apart from the frequency with which they are used. Many hooks have been replaced by plugins and annotations and the Event Dispatcher component has also taken on a big chunk of what was in D7 a hook responsibility.

Although the Event Dispatcher approach is more verbose, it is the recommended way to go forward. Where possible, we no longer use the old procedural approach characteristic to hooks but rather object oriented, decoupled and testable solutions. And Symfony helps greatly with that.

May 06 2013
May 06

You can either get on the Drupal 8 bus now, or get run over by it later.

It's true. Drupal 8 is coming, and it will be big. Not just lines of code (that too), but big in the sense that Drupal 8 changes more of Drupal than any major release in the last 10 years. The adoption of so many 3rd party components (from Symfony and otherwise) is only part of that picture. That offers challenges for many, but also enormous opportunity. Drupal 8 will allow Drupal to expand into new types of application and new markets, which is a great thing for those who make their living off of Drupal. But where do you get started with learning about Drupal 8?

At DrupalCon Portland, that's where!

There are many sessions slated for Portland at both DrupalCon and at Symfony Live that deal with Drupal 8, either directly or indirectly. Below is my recommended hitlist for Portland for those wanting to get the lowdown on Drupal 8.

What, you're not already signed up? There's still time! Go register for either DrupalCon or Symfony Live, and be sure to get a Combo Ticket so that you are able to attend both conferences as well as Web Visions! (The combo ticket is the same price either way.)

The WSCCI Track

As the Lead for the Web Services and Context Core Initiative (WSCCI), I am of course biased. If you want to know what's going on specifically with the core system, web services, and "other things that Larry broke", then here's the schedule for you:

The Driesnote (DrupalCon) OK, this isn't about WSCCI specifically but you can't quite get away from it. Everyone's favorite Pointy Haired Project Lead will be giving his usual keynote, this time focusing on Drupal 8. You'll be there, right? Dependency Injection in Drupal 8 (DrupalCon) "Dependency Injection" doesn't have to involve a trip to the doctor. If you didn't get that joke, you definitely want to go to this session by Kat Bailey. It's one of the fundamental principles of good Object Oriented design, and of Drupal 8's architecture. You'll want to know how it works, how it works in Drupal 8, and why it will make your life oh so much easier. REST for Web Developers (DrupalCon) Drupal 8 is all about "REST", but... what does that have to do with napping? Nothing, actually. Author, presenter, and general purpose expert Lorna Jane Mitchell will fill in the gaps of what REST is (and isn't), and what it means for how to use the modern Web. Building web service clients with Guzzle (Symfony Live) Web services aren't just about serving requests, but also making them. If you want to connect to other web services from your site, you need an HTTP client. At Symfony Live, Michael Dowling will be speaking about Guzzle, the HTTP client that is widely used in the Symfony world and now baked into Drupal 8. Want to know how to build solid systems that talk to other systems? Start here. Composer: There's a Library for That (DrupalCon) The PHP world has begun embracing a new universal package manager called Composer. Drupal 8 is using it to pull in our many 3rd party libraries. This joint session by Rob Loach, Sam Boyer, and yours truly will delve into what Composer means for PHP in general and Drupal in particular, and how you can start using it now for both Drupal 7 and Drupal 8 to spend less time writing code and more time using and building reusable components. REST and Serialization in Drupal 8 (DrupalCon) Remember all that theory about REST from 2 sessions ago? Now let's apply it to Drupal 8. Klaus Purer and Lin Clark will guide you through the new REST and Serialization modules in Drupal 8 and how to best leverage them. Hint: You will probably never need the Services module again. Modernizing Drupal 8 with Symfony2 (Symfony Live) For a big-picture "wait, so how does this damned thing work?", travel with me across the hall to Symfony Live for a part retrospective, part architectural overview of Drupal 8. How is Drupal using Symfony, and where is it not? Seek your answers here, young Jedi. Dries & Company Q&A (DrupalCon) Now that your brain is full, turn some of that new knowledge into questions! For the last session of the conference, join us for an open Q&A session with Dries and the Initiative Leads. (That sounds like an 80s band, doesn't it?) We'll try to answer your questions as well as we can about Drupal 8 and the future of Drupal, and try to be brief so we can get in as many questions as possible. Hey, I said try...

The rest of Drupal 8

Have any time left in your schedule after all of that? If so, try out these sessions. They're not related to Web Services, but do relate to major changes in Drupal 8.

The Future of Views In case you hadn't heard, Views is in core for Drupal 8. (And there was much rejoicing.) Tim Plunkett will be presenting an overview of where Views stands today, what it will look like tomorrow in Drupal 8, and how you can ready yourself for it now. Asset Management in Drupal 8 One of the biggest changes in Drupal 8 is that global state needs to go away. To do that, we are working to remove drupal_add_css() and drupal_add_js() in favor of a much more powerful system. Come see what it is... WYSIWYG in Drupal 8 Know what else Drupal 8 finally has? A WYSIWYG editor! And not just the dinky little admin form version you're used to, but a fully integrated inline editor. Nate Haug will be presenting on what that means for you as a site builder, and how to leverage it most effectively (or not at all). Drupal 8: The Good, The Bad, and The Ugly Join several senior members of the Palantir.Net team to talk about Drupal 8 in this open Q&A session from the perspective of people who make a living building Drupal sites for large clients. The Palantir team includes two Core Initiative Leads — myself and Mobile Initiative Lead John Albin Wilkins — and we'll be answering the question "now that you've built it, what are we going to do with it?" The Old and New Field API Let's face it, as powerful as Field API is in Drupal 7 the API leaves much to be desired. Fortunately the Field and Entity APIs have been heavily overhauled in Drupal 8 to be OO Plugins. Join Wolfgang Zigler, Yves Chedemois, and Kristof De Jaeger on a tour of the new data system in Drupal 8. Upgrading your Modules to Drupal 8 Oh dear oh dear. This is going to be scary, right? Not if you attend this session with Alex Bronstein, one of the few developers who has touched just about every new part of Drupal 8. Using Twig: The New Template Engine in Drupal 8 After years of PHPTemplate, we listened: Themers, behold Twig, the theme engine used by Symfony2 and now Drupal 8 as well. It has 30% less confusion, 50% fewer div tags, 80% fewer potential security holes, 95% less PHP in-your-face, and is 100% MortenDK approved. Join Jen Lampton, Fabian Franz, and John Albin Wilkins to see why Drupal 8 will make front-end developers sleep well at night. Making Drupal 8 Mobilicious Mmm... Mobilicious... Courtesy of my esteemed colleague and fellow Initiative Lead John Albin Wilkins, this session will cover what it takes for a Drupal site, and Drupal, to survive in a mobile-centric world. See what's already been done (hint: Drupal 8 is completely responsive out of the box) and what you (yes, you, right there, I'm talking to you!) can do to help finish the job. Multilingual Drupal 8 The Multilingual Initiative has been working to improve Drupal's already first-rate linguistic abilities even further. Initiative lead Gábor Hojtsy provides a look at where we stand and what's left to do. Using the Drupal 8 Configuration System Gone is variable_get(). In Drupal 8, you'll probably never need your own SQL tables for configuration. Fighting with Features just to get a few config changes to production is a thing of the past. Why? Join Greg Dunlap of the Configuration Management Initiative for a tour of the new configuration system of your dreams. Drupal 8 Plugin System Deep Dive And last but certainly not least, Drupal 8 will also feature a new unified plugin system, replacing the scattered array of array-based mechanisms for making "things" swappable and configurable. SCOTCH Initiative Lead Kris Vanderwater joins an all-star cast of James Gilliland, Tim Plunkett, and Alex Bronstein to show how to Plug All the Things! in Drupal 8 and make your code easier for others to extend, to boot.

As always, all sessions will be recorded. It's a good thing, too, because there's way too many good sessions just in the list above to fit into one week, to say nothing of all of the other sessions available at DrupalCon, Symfony Live, and Web Visions. Oy!

Core Conversations

Oh yeah, and there's also an entire track dedicated to looking at Drupal's future, including Drupal 8 and even a few mentions of (gulp!) Drupal 9.

Trainings

What, that's not enough for you? OK, you asked for it. Both DrupalCon and Symfony Live are also offering full day paid training workshops.

If you're looking specifically at understanding the Drupal/Symfony relationship, check out Drupal 8 and Symfony: All you need to rock (an all day session on Drupal 8 development with emphasis on Symfony components and Twig) on Monday. For a more Symfony-centric but still Drupal-relevant appraoch, try SensioLabs' workshop on the Symfony Components on Sunday.

Put it to good use

Whew! After a conference (or conferences) like that, your brain will probably be bursting with new information. There's only one way to treat an over-stuffed brain: Apply your new knowledge to something, quickly before it falls out!

DrupalCon and Symfony Live will be hosting our usual Sprint Day on Friday 24 May, all day from 9 am to 6 pm (and if history is anything to go by, continuing in hotel lobbies for several more hours). There will be teams working on all parts of Drupal 8 core, documentation, even project application reviews. In particular, I'll be coordinating efforts to port "page callbacks" to "controllers". (Don't know what that means? See the meta issue and documentation, and then come to DrupalCon to find out more!) In fact, we'll also be participating in the extended sprints hosted by Acquia on Saturday and Sunday.

Whew! I'm exhausted and it's not even conference time yet. Drupal 8 will be here before you know it, and now's the time to get a jump on this amazing new platform before the plaster dries.

Share with

Jan 25 2013
Jan 25

There's been a lot of talk recently regarding the integration of the Symfony2 components, as a fundamental part of Drupal 8's core system. I won't rabble on repeating the many things that have already been said elsewhere; however, to quote the great Bogeyman himself, let me just say that "I think this is the beginning of a beautiful friendship".

On a project I'm currently working on, I decided to try out something of a related flavour. I built a stand-alone app in Silex (a sort of Symfony2 distribution); but, per the project's requirements, I also managed to heavily integrate the app with an existing Drupal 7 site. The app does almost everything on its own, except that: it passes its output to drupal_render_page() before returning the request; and it checks that a Drupal user is currently logged-in and has a certain Drupal user role, for pages where authorisation is required.

The result is: an app that has its own custom database, its own routes, its own forms, its own business logic, and its own templates; but that gets rendered via the Drupal theming system, and that relies on Drupal data for authentication and authorisation. What's more, the implementation is quite clean (minimal hackery involved) – only a small amount of code is needed for the integration, and then (for the most part) Drupal and Silex leave each other alone to get on with their respective jobs. Now, let me show you how it's done.

Drupal setup

To start with, set up a new bare-bones Drupal 7 site. I won't go into the details of Drupal installation here. If you need help with setting up a local Apache VirtualHost, editing your /etc/hosts file, setting up a MySQL database / user, launching the Drupal installer, etc, please refer to the Drupal installation guide. For this guide, I'll be using a Drupal 7 instance that's been installed to the /www/d7silextest directory on my local machine, and that can be accessed via http://d7silextest.local.

D7 Silex test site after initial setup.

D7 Silex test site after initial setup.

Once you've got that (or something similar) up and running, and if you're keen to follow along, then keep up with me as I outline further Drupal config steps. Firstly, go to administration > people > permissions > roles, create a new role called 'administrator' (if it doesn't exist already). Then, assign the role to user 1.

Next, download the patches from Need DRUPAL_ROOT in include of template.php and Need DRUPAL_ROOT when rendering CSS include links, and apply them to your Drupal codebase. Note: these are some bugs in core, where certain PHP files are being included without properly appending the DRUPAL_ROOT prefix. As of writing, I've submitted these patches to drupal.org, but they haven't yet been committed. Please check the status of these issue threads – if they're now resolved, then you may not need to apply the patches (check exactly which version of Drupal you're using, as of Drupal 7.19 the patches are still needed).

If you're using additional Drupal contrib or custom modules, they may also have similar bugs. For example, I've also submitted Need DRUPAL_ROOT in require of include files for the Revisioning module (not yet committed as of writing), and Need DRUPAL_ROOT in require of og.field.inc for the Organic Groups module (now committed and applied in latest stable release of OG). If you find any more DRUPAL_ROOT bugs, that prevent an external script such as Symfony2 from utilising Drupal from within a subdirectory, then please patch these bugs yourself, and submit patches to drupal.org as I've done.

Enable the menu module (if it's not already enabled), and define a 'Page' content type (if not already defined). Create a new 'Page' node (in my config below, I assume that it's node 1), with a menu item (e.g. in 'main menu'). Your new test page should look something like this:

D7 Silex test site with test page.

D7 Silex test site with test page.

That's sufficient Drupal configuration for the purposes of our example. Now, let's move on to Silex.

Silex setup

To start setting up your example Silex site, create a new directory, which is outside of your Drupal site's directory tree. In this article, I'm assuming that the Silex directory is at /www/silexd7test. Within this directory, create a composer.json file with the following:

{
    "require": {
        "silex/silex": "1.0.*"
    },
    "minimum-stability": "dev"
}

Get Composer (if you don't have it), by executing this command:

curl -s http://getcomposer.org/installer | php

Once you've got Composer, installing Silex is very easy, just execute this command from your Silex directory:

php composer.phar install

Next, create a new directory called web in your silex root directory; and create a file called web/index.php, that looks like this:

<?php

/**
 * @file
 * The PHP page that serves all page requests on a Silex installation.
 */


require_once __DIR__ . '/../vendor/autoload.php';

$app = new Silex\Application();

$app['debug'] = TRUE;

$app->get('/', function() use($app) { 
  return '<p>You should see this outputting ' .
    'within your Drupal site!</p>'; 
});

$app->run();

That's a very basic Silex app ready to go. The app just defines one route (the 'home page' route), which outputs the text You should see this outputting within your Drupal site! on request. The Silex app that I actually built and integrated with Drupal did a whole more of this – but for the purposes of this article, a "Hello World" example is all we need.

To see this app in action, in your Drupal root directory create a symlink to the Silex web folder:

ln -s /www/silexd7test/web/ silexd7test

Now you can go to http://d7silextest.local/silexd7test/, and you should see something like this:

Silex serving requests stand-alone, under Drupal web path.

Silex serving requests stand-alone, under Drupal web path.

So far, the app is running under the Drupal web path, but it isn't integrated with the Drupal site at all. It's just running its own bootstrap code, and outputting the response for the requested route without any outside help. We'll be changing that shortly.

Integration

Open up the web/index.php file again, and change it to look like this:

<?php

/**
 * @file
 * The PHP page that serves all page requests on a Silex installation.
 */


require_once __DIR__ . '/../vendor/autoload.php';

$app = new Silex\Application();

$app['debug'] = TRUE;

$app['drupal_root'] = '/www/d7silextest';
$app['drupal_base_url'] = 'http://d7silextest.local';
$app['is_embedded_in_drupal'] = TRUE;
$app['drupal_menu_active_item'] = 'node/1';

/**
 * Bootstraps Drupal using DRUPAL_ROOT and $base_url values from
 * this app's config. Bootstraps to a sufficient level to allow
 * session / user data to be accessed, and for theme rendering to
 * be invoked..
 * 
 * @param $app
 *   Silex application object.
 * @param $level
 *   Level to bootstrap Drupal to. If not provided, defaults to
 *   DRUPAL_BOOTSTRAP_FULL.
 */
function silex_bootstrap_drupal($app, $level = NULL) {
  global $base_url;
  
  // Check that Drupal bootstrap config settings can be found.
  // If not, throw an exception.
  if (empty($app['drupal_root'])) {
    throw new \Exception("Missing setting 'drupal_root' in config");
  }
  elseif (empty($app['drupal_base_url'])) {
    throw new \Exception("Missing setting 'drupal_base_url' in config");
  }

  // Set values necessary for Drupal bootstrap from external script.
  // See:
  // http://www.csdesignco.com/content/using-drupal-data-functions-
  // and-session-variables-external-php-script
  define('DRUPAL_ROOT', $app['drupal_root']);
  $base_url = $app['drupal_base_url'];

  // Bootstrap Drupal.
  require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
  if (is_null($level)) {
    $level = DRUPAL_BOOTSTRAP_FULL;
  }
  drupal_bootstrap($level);
  
  if ($level == DRUPAL_BOOTSTRAP_FULL &&
  !empty($app['drupal_menu_active_item'])) {
    menu_set_active_item($app['drupal_menu_active_item']);
  }
}

/**
 * Checks that an authenticated and non-blocked Drupal user is tied to
 * the current session. If not, deny access for this request.
 * 
 * @param $app
 *   Silex application object.
 */
function silex_limit_access_to_authenticated_users($app) {
  global $user;
  
  if (empty($user->uid)) {
    $app->abort(403, 'You must be logged in to access this page.');
  }
  if (empty($user->status)) {
    $app->abort(403, 'You must have an active account in order to ' .
      'access this page.');
  }
  if (empty($user->name)) {
    $app->abort(403, 'Your session must be tied to a username to ' .
    'access this page.');
  }
}

/**
 * Checks that the current user is a Drupal admin (with 'administrator'
 * role). If not, deny access for this request.
 * 
 * @param $app
 *   Silex application object.
 */
function silex_limit_access_to_admin($app) {
  global $user;
  
  if (!in_array('administrator', $user->roles)) {
    $app->abort(403,
                'You must be an administrator to access this page.');
  }
}

$app->get('/', function() use($app) {
  silex_bootstrap_drupal($app);
  silex_limit_access_to_authenticated_users($app);
  silex_limit_access_to_admin($app);
  
  $ret = '<p>You should see this outputting within your ' .
         'Drupal site!</p>';
  
  return !empty($app['is_embedded_in_drupal']) ?
    drupal_render_page($ret) :
    $ret;
});

$app->run();

A number of things have been added to the code in this file, so let's examine them one-by-one. First of all, some Drupal-related settings have been added to the Silex $app object. The drupal_root and drupal_base_url settings, are the critical ones that are needed in order to bootstrap Drupal from within Silex. Because the Silex script is in a different filesystem path from the Drupal site, and because it's also being served from a different URL path, these need to be manually set and passed on to Drupal.

The is_embedded_in_drupal setting allows the rendering of the page via drupal_render_page() to be toggled on or off. The script could work fine without this, and with rendering via drupal_render_page() hard-coded to always occur; allowing it to be toggled is just a bit more elegant. The drupal_menu_active_item setting, when set, triggers the Drupal menu path to be set to the path specified (via menu_set_active_item()).

The route handler for our 'home page' path now calls three functions, before going on to render the page. The first one, silex_bootstrap_drupal(), is pretty self-explanatory. The second one, silex_limit_access_to_authenticated_users(), checks the Drupal global $user object to ensure that the current user is logged-in, and if not, it throws an exception. Similarly, silex_limit_access_to_admin() checks that the current user has the 'administrator' role (with failure resulting in an exception).

To test the authorisation checks that are now in place, log out of the Drupal site, and visit the Silex 'front page' at http://d7silextest.local/silexd7test/. You should see something like this:

Silex denying access to a page because Drupal user is logged out

Silex denying access to a page because Drupal user is logged out

The drupal_render_page() function is usually – in the case of a Drupal menu callback – passed a callback (a function name as a string), and rendering is then delegated to that callback. However, it also accepts an output string as its first argument; in this case, the passed-in string is outputted directly as the content of the 'main page content' Drupal block. Following that, all other block regions are assembled, and the full Drupal page is themed for output, business as usual.

To see the Silex 'front page' fully rendered, and without any 'access denied' message, log in to the Drupal site, and visit http://d7silextest.local/silexd7test/ again. You should now see something like this:

Silex serving output that's been passed through drupal_render_page().

Silex serving output that's been passed through drupal_render_page().

And that's it – a Silex callback, with Drupal theming and Drupal access control!

Final remarks

The example I've walked through in this article, is a simplified version of what I implemented for my recent real-life project. Some important things that I modified, for the purposes of keeping this article quick 'n' dirty:

  • Changed the route handler and Drupal bootstrap / access-control functions, from being methods in a Silex Controller class (implementing Silex\ControllerProviderInterface) in a separate file, to being functions in the main index.php file
  • Changed the config values, from being stored in a JSON file and loaded via Igorw\Silex\ConfigServiceProvider, to being hard-coded into the $app object in raw PHP
  • Took out logging for the app via Silex\Provider\MonologServiceProvider

My real-life project is also significantly more than just a single "Hello World" route handler. It defines its own custom database, which it accesses via Doctrine's DBAL and ORM components. It uses Twig templates for all output. It makes heavy use of Symfony2's Form component. And it includes a number of custom command-line scripts, which are implemented using Symfony2's Console component. However, most of that is standard Silex / Symfony2 stuff which is not so noteworthy; and it's also not necessary for the purposes of this article.

I should also note that although this article is focused on Symfony2 / Silex, the example I've walked through here could be applied to any other PHP script that you might want to integrate with Drupal 7 in a similar way (as long as the PHP framework / script in question doesn't conflict with Drupal's function or variable names). However, it does make particularly good sense to integrate Symfony2 / Silex with Drupal 7 in this way, because: (a) Symfony2 components are going to be the foundation of Drupal 8 anyway; and (b) Symfony2 components are the latest and greatest components available for PHP right now, so the more projects you're able to use them in, the better.

Dec 31 2012
Dec 31

And so 2012 draws to a close. The world didn't end, to the disappointment of many. In some ways it was an eventful year, in others rather ho-hum follow-ups to the excitement of 2011.

In the Drupal world, though, 2012 will go down as the year Drupal finally began replacing NIH with PIE. Compare Drupal's 8.x branch a year ago with it today. A year ago, we had one class of 3rd party PHP code, in an uninteresting corner of the update system. Today, it contains 3rd party code from no less than five different projects: Symfony, Doctrine, Guzzle, Assetic, and Twig. Those libraries are included via Composer, the new and ground-breaking PHP-library-management-system-that-actually-finally-works. Code from at least one more should be landing soon.

Drupal developers, working on Drupal, have contributed to a number of other projects, including Symfony and Symfony CMF, and because of the degree of code sharing happening in the PHP world now have indirectly contributed to a half-dozen other major projects, too. Drupal 8, aside from the technological advances it will offer over Drupal 7, also represents perhaps the largest cultural shift in Drupal or PHP history.

Are you ready for 2013, Drupal? Really?

Like many PHP projects, Drupal has been its own island for years. A large, active, vibrant island, but an island. But in 2012, the PHP archipelago began to form into continents. With the bridges built by Composer and by the "open islands" made possible by the PHP Framework Interoperability Group and PSR-0, collaboration and sharing between PHP projects has never been higher.

That represents both a threat to Drupal's traditional island-based culture, but also an incredible opportunity. We have an incredible opportunity to, as Greg Dunlap has put it before, "get off the island" and learn from others outside of our community. No matter how big the Drupal community, the PHP community is larger.

So, I put this challenge to the Drupal community: Make your New Year's Resolution to get off the island in 2013. By that I mean get involved in the wider PHP community, both to learn from it and to share with it. Let's be honest, Drupal has done some pretty amazing things, both technically and as a community, that we can and should share with the rest of the web development world. At the same time, we need enough humility to accept that there are way better ideas floating around out there than exist in Drupal, and we should be open minded enough to learn from them or adopt them wholesale.

In 2013, make it your goal to attend at least two non-Drupal web development conferences. Large, small, doesn't matter. If you can, present at least one of them; maybe about Drupal, maybe not. Connect with people outside of Drupal. Meet fresh faces; expose yourself to new ideas; share your awesome ideas with a wider world than just the followers of Druplicon.

Then come back to Drupal Island wiser, more worldly, and more able to continue to drive Drupal to be the leading CMS on the market.

For my part, I will be attending at least Sunshine PHP in February; I'm still looking for other conferences to speak at this year as well. (Suggestions and invites welcome, of course.) And all Drupalers have a unique and awesome opportunity this May to attend Symfony Live Portland, directly next door to DrupalCon Portland. Kick off your island hopping in Portland with the trifecta of conferences: DrupalCon, Symfony Live, and WebVisions. Attend at least 2 of them, or just do the combo pass. Help build bridges between PHP communities.

Perhaps the world did end in 2012: The world that consisted of only Drupal. That world is indeed gone. The new world of 2013 involves collaboration and sharing across dozens of projects. Let's make Drupal a leader in this brave new world.

Share with

Jul 18 2012
Jul 18

That moment when you realize just how much awesome you have coming up that you're not sure you're going to survive it? Yeah, that.

It's nearly time for the second half of the year conference season, and it looks like my schedule is starting to fill up. I've three conferences and 5 speaking engagements in the coming weeks, so for those interested in Stalking Crell here's where you'll be able to find me:

Midwest Developers Summit: 26-28 July

A new event kicking off this year, MWDS is targeted specifically at Drupal developers, especially core developers. While there are a few sessions scheduled, the main focus is sprint time. For those not familiar with it, a "sprint" in Drupal-speak is "lock a bunch of people in a room for a few days and get stuff done!" I'll be presenting on the state of the Web Services and Context Symfony Core Initiative (WSCCI), but the main focus will be developing. I'm hoping that by the end of the weekend we can get a few patches in, especially this one that will allow modules to register services and events with the system. We're looking for more people to help with that and other issues, so if you want to help make Drupal 8 kick ass come on up to Madison. It's free, and there's free food!

And oh yeah, if we get enough people there I will finally get a real Twitter avatar.

Saturday includes DrupalCamp Wisconsin. That's a more normal every-level camp, but many of the summit folks (myself included) will just keep on sprinting for a third day.

DrupalCon Munich: 20-24 August

August sees me back to Europe for the second time this year, this time in Germany. There's nothing unusual about that, though. Over 2000 Drupalers will be in Germany in late August. Why? It's DrupalCon Munich, duh!

For those who don't know What DrupalCon is, DrupalCon is the Drupal community's semi-annual (correction, now tri-annual) mega-conference. We're expecting around 2000 people in Munich, from PHP developers to designers to front-end developers to project managers to just curious business people. (Please don't scare them off.) It's not just a Drupal conference, either, but a general web conference. I'm especially proud of the Coder track, where we have brought in such experts as Pierre Joye, Henri Bergus, and David Zuelke.

The conference organizers somehow managed to get me on stage once a day. Tuesday I'll be trying to convince people that Functional Programming in PHP is not only possible, but desirable. On Wednesday I'll be taking to the Core Conversations track to discuss the home-stretch plans for WSCCI. On Thursday I step out of the code track for a change and over to the Site Building track, where I'll be discussing the various and sundry ways to host multiple Drupal sites together.

If stalking me in meatspace isn't enough of an incentive for you, perhaps the keynote speakers will be of more interest to you. Drupal project lead Dries Buytaert, Symfony project lead Fabien Potencier, and Open Government / Open Data guru Anke Domscheit-Berg will all be headlining the show.

And of course there will, as always, be another sprint day on Friday to kick off the fall Drupal Development Season.

Symfony Live: 26-29 September

And of course with Fabien speaking at DrupalCon again, it's only polite to return the favor. Late September will see me giving an encore presentation at Symfony Live in San Francisco, on the combination of Symfony and Drupal and what it means for both projects. It will be similar to my talk from Symfony Live Paris, but updated of course. Registration is open now.

Given how significant Symfony2 is for Drupal 8, I'm hoping that we can have a sizeable Drupal turnout. Not only is it polite to return the favor, but it's a great opportunity to make connections with the people who wrote the system that Drupal 8 will be based on. That is, you can get a jump-start on understanding and using Drupal 8, or on helping to drive Drupal 8 to the finish line.

And, you guessed it, there's a hack day there, too. :-)

So that's three opportunities to see me speak and contribute to Drupal, in three different timezones, on two continents. Surely you can make it to one of them, right?

I'll see you there.

Share with

Jun 02 2012
Jun 02

On Friday, Dries merged in the first major work from the Web Services and Context Core Initiative (WSCCI). In short, it means we are now making use of all of the Symfony2 Components that we've pulled into core in the past few months.

It is also step one in the biggest change in Drupal's design since Drupal 4.7.

The "kernel branch" lays the ground work for the remaining efforts of the WSCCI and SCOTCH initiatives. menu_execute_active_handler(), delivery callbacks, and the PHP superglobals have now been replaced by Symfony2's HttpKernel, Request and Response objects, and Event listeners.

What does that buy us? Immediately, not a great deal. This is foundational work that enables all of the follow-up work of both initiatives. It establishes the basis of a new flow for Drupal's bootstrap and page handling. Really, as of today we are not doing page handling anymore; we are doing request handling. That is a very subtle change, but from a web services perspective is critically important. Pages are just one type of request.

The next major line of work is a new routing system, built off of Symfony's Routing component. The new router will allow modules to map incoming requests based not only on the path, but also on whether the request is a GET or POST, whether it's HTTP or HTTPS, and even the mime-type, allowing us to serve HTML, JSON, PDFs, and more from a single URL. Work on that has already begun and will be released as soon as possible for public completion.

Meanwhile, as I write this I am on a plane to Paris for a 3-day sprint hosted by Commerce Guys and partially funded by Acquia's Office of the CTO. The plan is work on the format that will be used for exporting Drupal entities as web services, to disk, or anywhere else. This process is the meet-point of the Web Services, Content Staging, and Entity API efforts within Drupal, and I'm hopeful that we'll be able to make significant progress toward the goal of making it dead-simple to import and export content from Drupal sites to mobile devices, other Drupal sites, client-side applications, and other uses that haven't even been developed yet.

I will also be speaking about these ongoing efforts in less than a week at Symfony Live Paris, the Symfony community's main developer conference. If you haven't gotten a ticket yet, I believe there are still a few available. Come help make this partnership even stronger.

It's not been an easy process to get here, and the hard work is far from over. I want to take a moment to give a shout-out to everyone that's helped out on the kernel work with code, reviews, architecture input, and bug squishing (alphabetically):

And of course a huge thank-you to Fabien Potencier, Lukas Smith, and Christophe Coevoet from Symfony for their help and patience with our Symfony-newbie questions. I'm sure we'll have more soon.

(And if there's anyone I missed in the above list, sorry! It's been a big group. Let me know and I'll correct it.)

There is still plenty of work to do! If you want to help right now, there's a good-sized list of Kernel follow-up issues that need attention. They run the gammut from Novice-tagged to "I don't know how this will work yet but we need to figure it out". Feel free to stop by #drupal-wscci in IRC if you have questions and a member of the team will try to help you out and turn you into a member of the team. (One of us...)

There is also on-going work to replace Drupal's session handling with the newly-improved Symfony2 session handling (improved specifically so that we could leverage it), rewrite the Drupal installer with its own small kernel implementation to make it no longer the red-headed stepchild of Drupal, ongoing efforts to clean up Drupal's classes to use PSR-0 so that we can simplify bootstrap even further, and of course the Blocks and Layout Initiative (SCOTCH).

There's no shortage of work that needs to be done yet to make Drupal 8 kick ass, and the time to get involved in the ass-kicking is now. I'll see you in the issue queues.

Share with

Nov 01 2011
Nov 01

(At BADCamp, several people were asking me to explain what the heck the Web Services core initiative was trying to do, so I got to practice my elevator pitch. This is essentially that pitch in written form.)

Drupal today is very page-oriented. Every request that comes in is responded to with a full HTML page. It is possible to return something else, and finally in Drupal 7 there is, sort of, native support to do so with delivery callbacks, but by and large any non-page response is an after thought at best and a hack at worst. The entire system is built around the assumption that we're returning an HTML page. Why else would we still load the theme system and form system for an auto-complete callback?

In the past, that hasn't been a major issue. The web was a series of pages, in practice, and Drupal is one of if not the most flexible page-generating machine on the web today. Drupal 7 is, arguably, the pinnacle of this page-oriented world.

Just in time for that world to be fading fast.

Tomorrow's web

In case you've been living under a rock, the web is in the midst of a massive upheval. HTML and HTTP, tools built for linking static pages together, are transmogrifying (somewhat painfully and with lots of torn shirts along the way) into a layer of the Internet atop the Internet. Everything is not a page, destined for a known browser. Everything is a response to a request. That is all that can be assumed.

Responsive design and mobile-friendly sites may get all the press, but that's the least of our worries. That's "just" theming (he says as the themers get pitchforks). The web is changing in even more fundamental ways.

Consider a site using Varnish. That can do more than just serve a full page from a RAM cache. Edge-Side Includes (ESI) let it cache most of the page but call on the server to render just certain portions of the page (say, those that are customized to a given user) quickly and stitch them together on the fly. That's next year's high-volume web sites.

Consider FaceBook. They switched to parallel rendering of the page through sub-requests over a year ago. They call it Big Pipe. The general idea, though, is breaking the page up to generate and deliver in segments, in parallel. This is last year's high-volume web site.

Consider My.FCC.Gov. Watch the video. That is a Drupal site. (Can you believe it?) The page is actually being built in the browser, by the client, in Javascript. The individual chunks of the page are being retrieved by multiple parallel Ajax requests. That required a fair bit of custom code and Services work, which means lots of full bootstraps.

Consider Backbone.js, which moves nearly all logic into the browser and treats the server as simply a dumb data store. Panels in the client, anyone?

Consider Web Sockets. Create a persistent bidirectional connection between the client and server and push raw data back out to the browser, not on request but immediately as it becomes available, to be presented to the user in whatever way Javascript dictates.

Now, try to combine those. No pages, just HTML fragments, JSON strings, raw persistent data sockets, etc. In practice, the server may almost never generate an entire page from html tag to html tag.

That's the future. That's also not something Drupal today can handle.

Show some initiative

Supporting that sort of next-generation workflow requires changing the way we approach page handling. It requires treating an incoming request as just that, an abstract request, which may be returning a page, or a fragment, or JSON, or establishing a persistent connection, or any number of things we can't even imagine yet. It requires leveraging all of the HTTP protocol, not just the tiny subset that browser HTML lets us access.

Fortunately, work is already under way. We also can get outside help. One of the reasons I am so excited that we've begun adopting Symfony2 components in core is that the Symfony2 framework is built on exactly those principles. By leveraging the work of others, in good open source fashion, we can save time both in coding and in architecting basic components. I expect we will pull in more Symfony2 components as time goes on, as reinventing the wheel in these critical areas would accomplish nothing but ensure that we don't get to the real work.

Today, Drupal loads nearly all of its code base for every request. We need to allow most of the system to lazy-load on demand instead. Fortunately, we can now do that by converting code to PSR-0 compatible classes in core.

Today, Drupal figures out what function will handle a page based purely on the path alone. We need to enable it to look at the full HTTP request and then some to do the complex routing we need. Or perhaps this is another area we could punt to Symfony2 components to save time and debugging.

Today, Drupal figures out what sort of response it is going to return after it's already done the work. We need to make determining what the response will be before we've build a single render array element. That depends on not just the request itself, but on the Drupal-derived information from it. That is, the Context of the request.

Today, we lay out a page as a blob of content with smaller blobs of content arrayed around it. We need to move to a model where one block on a page is no more special than any other, and we approach the page outside in, not inside out. Fortunately we have the model of Panels to follow.

Today, when laying out a page in Panels we have to deal with the Panels UI, which is widely recognized to be not up to the task. Fortunately some smart people are already trying to think through how that could be re-imagined to make it more approachable for site builders and content editors alike. It will take a lot more directed thought and planning,though, to re-invision the UI.

Web sockets may not be straightforward to implement in PHP, but we're already trying to work out how we could enable them from Drupal. (If you've done any development with Web Sockets, please share your experience!)

See something you could help with? We'll see you in the issue queues.

Bookmark/Search this post with

Oct 25 2011
Oct 25

Earlier today, Dries committed a patch that adds two Symfony2 Components to Drupal: ClassLoader and HttpFoundation.

On its face it's a fairly simple patch; the new code in it is maybe a dozen lines. But it's an important part of a larger shift within Drupal to better embrace the modern web, on the server as well as the client.

The new face of PHP

The PHP development community has long been highly fractured. Everyone had their own CMS, their own framework, and their own applications. Even when developers started rallying around a few market leaders (Drupal included), there was no real interoperability. Attempts at a common framework either suffered from the standards problem, morphed into all-or-northing frameworks, or ran into naming collisions with everyone else, making them almost useless.

That has changed in recent years thanks to three key developments.

  1. First, Object-Oriented architecture has firmly taken hold in the PHP world. While not the solution to every problem, OOP does support library mix-and-match design much more easily than procedural code.
  2. Second, PHP 5.3 introduced code-level namespaces, allowing different libraries to co-exist without clobbering each other on a class or function name.
  3. Third, some of the leading PHP projects collectively agreed on how to leverage namespaces. That convention, dubbed PSR-0 (for PHP Standard Reference #1, because computer people always start counting at 0), specifies a common pattern for naming and organizing namespaced classes. The result is that it's now possible to leverage PHP's autoload capabilities across a range of projects in a consistent and predictable manner.

And all of those changes passed Drupal by. Drupal 7 shipped as a mostly procedural, non-namespaced framework with a somewhat fragile custom autoload system, the Registry. And, as a result, very little ability to benefit from the changes happening in the wider PHP community.

That's actually rather sad for me personally, given that Drupal was a leading project in the last great PHP collaborative effort, GoPHP5. Sadly we never kept that momentum going.

Pushing Drupal forward

The Web Services and Context Core Initiative for Drupal 8 is working on major changes to Drupal, refactoring it from a page-oriented CMS to a request/response-oriented REST server. That's no small task, and one that could benefit from a leg up from the wider open source community.

For that reason, several people began recommending that Drupal start to embrace the changes happening in the wider development community, first with a PHP 5.3 base and now with PSR-0 classes. By adopting the existing standard for classes, making our autoloading simpler and less error prone, and open the door to considerable performance improvements. By converting our core code over to easily-lazy-loaded classes, we can greatly reduce Drupal's memory footprint by not bootstrapping code we don't need. That makes response time faster, which is critical for any form of web service.

Much to my surprise, there was very little resistance.

That also means that we could now leverage existing code like HttpFoundation that we don't need to write ourselves, saving time and effort. That too met with little resistance.

Drupal is back in the PHP game.

Why Symfony?

The PHP library world has been changing rapidly in recent years. Once upon a time, there was only one really popular component library, PEAR, which had quasi-official status but was still a pain to install on many shared hosts. More recently, a number of popular frameworks have grown up, including Zend Framework, CodeIgnighter, and others, specifically casting off the PHP 4 legacy of earlier years. The Symfony framework, a Rails-y pseudo-MVC application framework, decided to go that route as well and completely reinvented itself for version 2. That included spinning off many low-level systems as free-standing components.

Zend and Symfony were both parties to the original PSR-0 standard, making it possible for (properly-written) code to be mixed and matched between the two.

When Drupal was looking for a 3rd party HTTP library to adopt rather than writing our own, we looked at two that seemed to have the best market share behind them: Zend 2 and Symfony2. We knew that whichever one we adopted we stood a very good chance of adopting more components from, so it was perhaps a more important decision than those involved realized at the time. After a comparison, we settled on Symfony for a number of reasons.

  1. Symfony's component libraries, or at least HttpFoundation, appeared to be richer and more self-contained, allowing us to pick and choose what we use.
  2. Zend Framework has a Contributor License Agreement, which Symfony does not. While I fully understand the reasons why Zend has a CLA, and I've even toyed with the idea of one for Drupal in the past, it raises the barrier to entry for Drupal developers to contribute back upstream. We wanted to ensure that we didn't just grab-and-go, but formed a partnership with whatever project we decided to leverage.
  3. Speaking of partnerships, the quick feedback and cooperation from Symfony's project lead, Fabien Potencier, and Lukas Smith carried a great deal of weight.

Even since then, Fabien and Lukas have made themselves available for help, insight, and other advise. Drupal folks have even started to submit code back to Symfony, too, for minor cleanup as well as feature fixes. That's exactly what I was hoping to see happen.

Next steps

Now that we have the ClassLoader and HttpFoundation in core, there's a lot of work yet to do.

  • The HTTP handler for the Context API is not quite finished, but hopefully will be soon. That lets us leverage the HttpFoundation in a consistent, clean, and powerful way to help control how Drupal responds to page request.
  • HttpFoundation includes a session-handling system, but leveraging it requires better decoupling our user and session systems. Which is something we should be doing anyway.
  • There's an open discussion of where else we could leverage Symfony2 components. There are actually a number of viable possibilities.
  • Now that we have adopted PSR-0 for core, we need to start refactoring the /includes directory to leverage it. That means cleaning up and converting core subsystems to object-oriented designs that we can then lazy-load without effort. That should help performance and code clarity.
  • So far, we're just using PSR-0 for core classes. What about modules? That's actually a much harder question, but it's one we will need to figure out sooner rather than later.
  • And oh yeah, we should update our coding standards documentation. That's on my list.

We have a lot of work ahead of us, but this is the beginning of Drupal no longer being an isolated island in the PHP world. If you want to help Drupal remain the dominant PHP CMS, let's roll up our sleeves and get to work.

Bookmark/Search this post with

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web