fromJune 2014
Feature:

At Your Request

Introducing the Symfony Request Object
0

PhotoIn the beginning there was the Common Gateway Interface, commonly known as CGI – a standard approach used to dynamically generate web pages. Originally devised in 1993 by the NCSA team and formally defined by RFC 3875 in 2004, CGI 1.1 took seven years to go from the original RFC to an endorsed standard.

In 1994, not long after the original CGI standard was documented by NCSA, Rasmus Lerdorf created Personal Home Page tools (PHP Tools), an implementation of the Common Gateway Interface written in C. After going through a number of iterations and name-changes this grew to be the PHP language we know and love.

One of PHP's strengths was the way in which it made many of the request and server specific variables, as defined by the CGI standard, easy to access – through the use of superglobals, namely $_POST, $_GET, and $_SERVER. Each of these is an associative array. In the case of $_POST, the request body is parsed for you and turned into an array of user-submitted values, keyed by field name, and conveniently supporting nested arrays. Similarly for $_GET, the query string is parsed by PHP and turned into a keyed array. In the case of $_SERVER, the gamut of server-specific variables are available for your script to interrogate.

Now, as Drupal developers, we rarely interact with $_POST and $_SERVER, and seldom interact with $_GET. Variables submitted through forms are abstracted behind Drupal's Form API; we ordinarily work with the $form_state variable instead of $_POST. In terms of the $_SERVER superglobal, Drupal provides handy wrappers for many of these values too, so instead we interact with those rather than with $_SERVER directly.

Please note that this article is accurate at the time of writing, however there is a proposal (see http://wdog.it/4/1/attributes and http://wdog.it/4/1/match) to improve the developer experience of working with important attributes stored on the Request object. The goal of these issues is to introduce methods on helper classes to make access to important elements of the request/response life-cycle more explicit instead of relying on using arbitrary string keys to fetch items from the Request object.

Under the proposal, items that relate to the routing system will be available from a RouteMatch class containing useful methods, for example RouteMatch::getRawArguments(). This would be used instead of $request->attributes->get('_raw_variables'). Under the proposal this will form a key developer facing API for working with upcast routing parameters so RouteMatch::getArgument('node') will replace $request->attributes->get('node'). This makes sense because the node object is not part of the request, it is a product of the routing of that request.

In addition the existing RequestHelper class will be expanded to add additional methods for interrogating concepts related to the Request but unrelated to routing. For example RequestHelper::isCleanUrl() can be used to determine if the incoming request utilizes clean urls, instead of $request->attributes->get('clean_urls').

Before using the example code in this article, be sure to first review the above issues.

If It Ain't Broke, Don't Fix It?

So what's wrong with using $_GET, $_POST, and $_SERVER in conjunction with some other Drupalisms™ like arg() and current_path()?
Well, does the code below look familiar?

<?php
  if (($nid = arg(1)) && is_numeric($nid) && !arg(2)) {
    // We have a node page and it's not the node edit page.
    // Do something.
  }

If you're nodding your head, then you're probably aware of one of the drivers behind the Web Services and Core Context initiative providing context to a page request or, better yet, providing context to a request – because, after all, the web has changed and Drupal has to be able to handle more than pages to remain relevant.

So, taking a step back, the Hypertext Transfer Protocol, commonly known as HTTP, is essentially made up of a Request and a Response. An incoming request is made, and then, based on the request contents and some application logic, a response is returned.

Now early in the Drupal 8 release cycle, it was decided that we would leverage the Symfony 2 components, and one such set of components is called HttpFoundation. Essentially these components are object-oriented replacements for PHP superglobals that represent the request and the functions used to generate the response.

In Drupal 8, we represent an incoming request as a Request object (Symfony\Component\HttpFoundation\Request) and a response as a code object (Symfony\Component\HttpFoundation\Response).

Throughout the Drupal 8 cycle, we've been working hard to remove all references to the PHP superglobals and replace them with references to the Request object.

“But why?” I hear you ask.

Well, there are a number of reasons.

First, because we're reacting to an incoming request, and this object is available throughout the whole response/routing process, it represents the ideal place to store context. That is, if you're responding to a request for node/{node}, then you would most likely need to know which Node you are dealing with.

Second, because we're dealing with a first class object, we can do away with a lot of code juggling that is normally required with arrays.

Consider:

<?pre
  $foo = FALSE;
  if (isset($_GET['foo'])) {
    $foo = $_GET['foo'];
  }

And third, if our whole response pipeline is wired to use the incoming request object, we open the possibility of performing functional web testing without the need for an actual HTTP request: we can mock the Request, collect the Response, and compare it against the expected outcome. Contrast this with our current web testing which relies on Curl to make full-fledged requests to a test-only site that is installed during test setup. We're not there yet, but we're getting close.

So that’s the what and the why of the Symfony Request object. Now lets get to the how.

Working with the Request Object

With the Request object, each of $_SERVER, $_GET, and $_POST can be accessed from the server, query, and request properties, respectively. These properties are ParameterBag objects (Symfony\Component\HttpFoundation\ParameterBag), which have useful methods like has(), get(), and set().

<?php
  // Drupal 7.
  $foo = FALSE;
  if (isset($_GET['foo'])) {
    $foo = $_GET['foo'];
  }
 
  // Drupal 8 - assuming Request is $request.
  $foo = $request->query->get('foo');

Getting Context From the Request

So how do you use the Request object to get the context of a page?
In Drupal 7, we had menu_get_object() to fetch an item from the current path. For example, when viewing node/1 the defaults were enough:

<?php
$node = menu_get_object();

For the taxonomy terms on taxonomy/term/1, you had to pass the arguments:

<?php
// We know that the menu callback is /taxonomy/term/%taxonomy_term
// with the term in position 2.
$term = menu_get_object('taxonomy_term', 2);

In Drupal 8, upcast parameters are simply available from the attributes property of the request. The attributes property is also a ParameterBag object, so you also have the convenient has(), set(), and get() methods.

<?php
// Assuming the Request is $request.
$node = $request->attributes->get('node');

How simple is that!

Getting the Route Name

In the earlier example, where we were trying to ascertain if we were on a 'node page', it involved some juggling using the arg() function. One of the great features of the new Routing system in Drupal 8 is that each route has a name. For example, consider the following routing.yml entry from Forum module:

forum.delete:
  path: '/admin/structure/forum/delete/forum/{taxonomy_term}'
  defaults:
    _form: '\Drupal\forum\Form\DeleteForm'
    _title: 'Delete forum'
  requirements:
    _permission: 'administer forums'

In this example the route name is 'forum.delete', and this route handles the path 'admin/structure/forum/delete/forum/{taxonomy_term}', where taxonomy_term is an integer corresponding with a term ID.

In your code, if you want to check the route name, you can access a special attribute on the Request object and, instead of using a random string to refer to the attribute name, use the ROUTE_NAME constant defined on the RouteObjectInterface (\Symfony\Cmf\Component\Routing\RouteObjectInterface).

By the time Drupal 8 comes out, we might have a request helper object to make this simpler, but here's an example of how to fetch the route name at the moment:

<?php
 
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
$route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
 
if ($route_name == 'mymodule.someroute') {
  // Do something.
}

Getting Request Headers

The Symfony Request object also includes a HeaderBag (Symfony\Component\HttpFoundation\HeaderBag), which gives easy access to any request headers you might need. The HeaderBag contains the same convenient methods as the ParameterBag

Deep Integration with the Routing System

Looking back at that same routing entry from forum.routing.yml, you'll note the 'defaults' section. Those values can also be found in the attributes parameter bag:

<?php
// Assuming the Request is $request.
if ($request->attributes->has('_title')) {
  $title = $request->attributes->get('_title');
}

I'll Swap You an Int for an Object – ParamConverters

Note that in the forum routing entry above, the path includes a {taxonomy_term} placeholder. In the URL this would be a term ID, so the URL might look like admin/structure/forum/delete/forum/1. But in your code, the taxonomy_term attribute will be a full-fledged object implementing TermInterface (\Drupal\taxonomy\TermInterface), meaning you have direct access to all the methods and properties a Term contains.

<?php
$term = $request->attributes->get('taxonomy_term');
if ($term->bundle() == 'forum') {
  $name = $term->label();
}

This works using the new routing system concept of parameter converters. These are objects implementing ParamConverterInterface (Drupal\Core\ParamConverter\ParamConverterInterface). This interface is not dissimilar to Drupal 7's %node style placeholders in hook_menu entries but is much more explicit, no longer relying on function naming conventions. By default, Drupal core ships with support for upcasting any entity-type-ID into an actual EntityInterface (\Drupal\Core\Entity\EntityInterface) object. So if you name your placeholder with an entity-type-id, it will be automatically upcast for you. If you're interested in gaining further insight into the upcasting system, take a look at the ParamConverterInterface.

Raw Values

What happens if you want to get access to the raw values before they were upcast?
In the previous example, suppose you wanted to get access to the raw term ID (1) instead of the actual term object, and let’s assume that for some reason just using $term->id() wasn't an option (just go with me on this). In this case, the raw values are stored in the request attributes, in another ParameterBag that you can fetch the values from like so:

<?php
if ($request->attributes->get('_raw_variables')->has('taxonomy_term')) {
  $term_id = $request->attributes->get('_raw_variables')->get('taxonomy_term');
}

There is an issue in the core queue to add a constant for _raw_variables to make this more robust, so using _raw_variables might be simplified by the time Drupal 8 is released.

Accessing the Request Object in Your Code

That covers working with the request, but how do you get access to it in your code? It depends on where you need it, as the approach differs between procedural code, services, and controllers.

Requesting the Request in Your Controller

If you're building a controller (the Drupal 8 equivalent of a page callback), and you need access to the request, the good news is: all you have to do is ask. And by ask I mean declare it in your method arguments. Some reflection wizardry in the Symfony routing components will ensure it gets handed to your method automagically. In this example, the MyModuleController::fooBar method type-hints its $requestparameter as a Symfony Request object and the ControllerResolver (\Symfony\Component\HttpKernel\Controller\ControllerResolver) ensures that the active Request is passed to the fooBar method when called.

<?php
/**
 * @file
 * Contains \Drupal\mymodule\Controller\MyModuleController.
 */
 
namespace Drupal\mymodule\Controller;
 
use Drupal\Core\Controller\ControllerBase;
 
/**
 * Controller methods for mymodule routes.
 */
class MyModuleController extends ControllerBase {
 
  /**
   * Provides content for the /mymodule/foobar path.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request
   */
  public function fooBar(Request $request) {
    $quantity = $request->query->get('quantity');
    return array(
      '#type' => 'markup',
      '#markup' => $this->t('You requested %quantity items.', array(
        '%quantity' => $quantity,
      )),
    );
  }
}

Accessing the request from procedural code

In procedural code, you don't have the opportunity to inject dependencies or magically receive the request, so you have to call out to the \Drupal singleton and use the static request method.

<?php
 
/**
 * Utility to check if the foo attributes exists on the Request.
 *
 * @return bool
 *   Returns TRUE if the request has a foo attribute.
 */
function mymodule_request_has_foo() {
  $request = \Drupal::request();
  return $request->attributes->has('foo');
}

Now while that might seem simpler than having the Request object passed in as an argument, you should resist the temptation to use \Drupal::request() in object-oriented code. Calling out to the global \Drupal singleton in your object-oriented code makes unit-testing your code more difficult, as you need to setup a container, add the request to it, and then call the setContainer method on \Drupal before you can do so.

Injecting the Request Into a Service

If you are creating a service and you need access to the request, you can nominate it as a dependency in your services.yml file thusly:

services:
  mymodule.foo:
    class: Drupal\mymodule\MyModuleManager
    arguments: ['@database', '@request']

But this depends on the life-cycle of your service, as the request service isn't always available. For information on how to create a service that might need to be instantiated before Drupal enters the Request scope, see the Getting Advanced section later in this article.

Bring Your Own – Creating a Request

If you're writing code, such as a custom application or a shell script that lives outside the normal Drupal bootstrap, you might find that you need to instantiate a Request object. Or you might be working on some tests and wish to create a pseudo Request. In these cases, you need to build your own Request object. There are two approaches to doing so, depending on your use case.

Transforming Globals Into a Request Object

If you're working in a script or custom outside the standard Drupal bootstrap, you can easily transform the superglobals into a Request object using the static createFromGlobals method:

<?php
 
use Symfony\Component\HttpFoundation\Request;
 
$request = Request::createFromGlobals();

Making Your Own Request

If you need to create a request for simulation purposes while testing or some other purpose such as a sub-request, you can manually create one using the static create method. This takes arguments for the URL, method, parameters, etc.

<?php
 
use Symfony\Component\HttpFoundation\Request;
 
$uri = 'http://foo.com/bar';
$method = 'get';
$params = array(
  'yes' => 'please'
);
$request = Request::create($uri, $method, $params);
 
// Will return 'please'.
$yes = $request->query->get('yes');

Getting More Advanced

Earlier, we talked about creating a service with a dependency on the Request and noted that this would only work if your service was only instantiated during the request scope. When the DrupalKernel (\Drupal\Core\DrupalKernel) handles an incoming request, it calls the handle method on the HttpKernel service (\Drupal\Core\HttpKernel This notifies the dependency injection container that the request scope has been entered, sets the request service in the container, and generates a response. Once the response has been built by the kernel, it notifies the container that the request scope has been left.
When the container is in the request scope, calls to fetch the 'request' service are successful. But if your service requires the Request object and can be initialized before or after the kernel is in the request scope, the request object will not be available. In these instances you'll raise a RuntimeException (Symfony\Component\DependencyInjection\Exception\RuntimeException) with the message You have requested a synthetic service ("request"). The DIC does not know how to construct this service because your service was initialized too early or too late.
With such a service, you need to use setter injection instead of constructor injection. This just means that instead of handing in the Request object as an argument to your service's constructor, you need to have a setter method to handle handing in the Request object at a later time.
One such service in core is the FormBuilder (Drupal\Core\Form\FormBuilder) which is the successor to Drupal 7's drupal_get_form(), drupal_build_form(), and other functions.

Consider its definition from core.services.yml:

  form_builder:
    class: Drupal\Core\Form\FormBuilder
    arguments: ['@module_handler', '@keyvalue.expirable', '@event_dispatcher', 
      '@url_generator', '@string_translation', '@?csrf_token', '@?http_kernel']
    calls:
      - [setRequest, ['@?request=']]

Here, the calls definition tells the dependency injection container to call setRequest on the FormBuilder object, only when the request object is available. If the request object is not available, the call is ignored. The “?” In the request service definition tells the container builder to ignore invalid references; for example, that the CSRF token and HTTP kernel dependencies are also declared with the “?” notation.
For the FormBuilder service, if it is used in circumstances where it might be built before the request scope is entered, either code calling to the form builder service will need to take care to call setRequest on its behalf, or an event listener will be needed.

Reacting to the Request - Event Listeners

When Drupal enters the request scope, a Symfony Event is fired. Symfony Events are the object-oriented equivalent of Drupal hooks. The event fired is KernelEvents::REQUEST (Symfony\Component\HttpKernel\KernelEvents). If you need to react to the Kernel entering the request scope, you can create an event listener and register KernelEvents::REQUEST as one of the subscribed events. Your listener method will receive a GetResponseEvent (Symfony\Component\HttpKernel\Event\GetResponseEvent) object as an argument and you can use GetResponseEvent->getRequest() to access the request. This represents the most advanced end of the scale, but the Symfony event-dispatcher component is well worth learning and will be another valuable addition to your programmer’s knowledge base. If you're interested in learning more about Symfony Events, see the Event Dispatcher chapter in the Symfony Components documentation.

Next Steps

So where to from here? Drupal 8 is still under development and there are some final pieces of the puzzle yet to be completed, particularly around web testing.

Web Testing with the Kernel Instead of Curl

Although we’re not there yet, one of the major advantages of using the Request object throughout our code instead of the PHP superglobals is the ability to mock a page request. At present our Drupal 8 functional testing still uses Simpletest, which relies on Curl to make real requests to a fully functional test-site. All of this adds overhead, complexity, and execution time to our test runner. We still have a few more places to clean up where we're accessing the superglobals, but we're almost at the point where something like this might become a reality.
Essentially, we might be able to simulate the whole request-response cycle from inside the test-runner. I don't know about you, but I think that would be pretty darn neat!

Image: ©iStockphoto.com/Gudella