Drupal 8: An Introduction to the New Production

Routing

Routing is a common concept amongst web application frameworks and has been handled by Drupal through the hook_menu function. Hook_menu allows modules to define paths that map to functions. The same principle applies to routing, however now there are more options and configurations you can apply to control the flow of the request and minimize conditionals in controllers. Hook_menu is still a very critical component to a module's functionality, however if it delegates to a route_name, it will assume the route's configuration to dictate controller delegation.

Basic Routing Definition

//demo.routing.yml demo_route: pattern: 'demo/public' defaults: _content: '\Drupal\demo\Controller\DefaultController::demoAction' requirements: _permission: "view content"

This route definition is saying any request that accesses the URL demo/public will fetch its content from the specified controller. The defaults property defines default URL parameters and maps the route to the controller. However Drupal's implementation takes a few variants.

Defaults 3 Amigos

  • _content This route will return only the content block of the page.
  • _controller This route gives total rendering control to the controller's response. However if you're utilizing the template system you can still harness the page structure.
  • _form This route will render a form, the controller will return a structure for Drupal's form system to process.

Controller Basic Response

//lib/Drupal/demo/Controller/DefaultController.php class DefaultController implements ControllerInterface { public function demoAction(Request $request) { return new Response("An Introduction to the New Production"); } }

This returns a Response, the first parameter is the content of the Response, this is traditionally the entire html document. However since the Route is configured to default to _content the Response's body will be placed in the page templates content block. You can also return a simple string, however the advantage to using a Response object is you now have control of the headers and status code returned to this request.

Routing with Parameters

//demo.routing.yml demo_name: pattern: 'demo/public/{name}' defaults: _controller: '\Drupal\demo\Controller\DefaultController::nameAction' requirements: _permission: "view content"

The controller to handle this will receive the variables as parameters in the method. Order doesn't matter only the name, such that if your route has {name} in the pattern, you need to specify $name in the argument list for your controller's method. Symfony has enforced that all controller methods that have a corresponding route need to be suffixed with "Action". Drupal doesn't implement this strictly, however I feel it is a good practice to continue as that will help identify which methods respond to a request, and which methods assist with other operations.

//lib/Drupal/demo/Controller/DefaultController.php class DefaultController implements ControllerInterface { public function nameAction($name) { $twig = $this->container->get('twig'); $path = $this->templateDir . 'example.html.twig'; $template = $twig->loadTemplate($path); drupal_set_title("Route Parameter Example"); return $template->render(array('name' => $name)); } }

Templating

For too long the de facto templating language for Drupal was PHP. Let's not sugar coat it, you're invoking a PHP script that is intended to render data into markup. However there is nothing stopping a developer from making function calls and performing logic and anything else that the PHP runtime environment can execute.

Drupal 8 integrates with Twig, a clean and extendable template language. One of the biggest advantages for Twig and Drupal is the ability to extend a twig template. Think of your custom theme and all the variants of node-template.php. Think of all the code that is duplicated between these variations. Twig solves this problem with blocks and template inheritance. Twig defines blocks, don't get this confused with a Drupal block, however they're similar in concept. A twig block defines a region that can be overwritten by a sub-template. This allows a parent template to set up the layout and redundant markup, while allowing child templates to extend specific blocks to customize output.

Base Template

//templates/base.html.twig <div class="amazing-markup"> {% block basic %} <h1>You don't have to see this, just override it already</h1> {% endblock %} </div>

Template Inheritance

//templates/example.html.twig {% extends 'modules/demo/templates/base.html.twig' %} {% block basic %} <h1>Basic Rendering of Twig Template</h1> <p> Way to go <strong>{{ name }}!</strong> </p> {% endblock %}

The idea is you you wrap blocks in markup that will be used by the child templates and override the blocks that hold unique content or need extra markup for the child template to be displayed properly.

Services

A service is a class instance that is shared throughout the application with a defined configuration on how to build that object.

A service container is a built class based on the service definitions that facilitates object construction and dependencies between these objects. Some services require other services in order to work, the service container handles that.

A container builder is a class that parses the configuration and can be passed through operations to intelligently design and construct the service container.

Services are a great place to put reusable business logic. This also creates a clean way to reuse code and keep memory usage low. The service container implements a hybrid of factory, singleton and dependency injection.

First we have to configure our service.

  • class the fully qualified class name of the instance.
  • arguments the values passed to the constructor.
  • calls any setters or other methods that need to be invoked as part of the object's construction.
//demo.services.yml parameters: demo.quotes.aristotle: - "Knowing yourself is the beginning of all wisdom." - "What is a friend? A single soul dwelling in two bodies." - "It is the mark of an educated mind to be able to entertain a thought without accepting it." services: demo.quotecollection: class: Drupal\demo\Service\QuoteCollection arguments: - %demo.quotes.aristotle% calls: - [setAuthor, ["Aristotle"]]

Service Instance

The service is very simple, its constructor receives an array of strings(quotes) and has an author attached to this collection. The class itself is abstract enough it can be used for any quote collection, but by way of configuration, we see this service is used to manage Aristotle quotes.

//lib/Drupal/demo/Service/QuoteCollection.php <?php namespace Drupal\demo\Service; class QuoteCollection { protected $author; public function __construct($quotes) { $this->quotes = $quotes; } public function setAuthor($author) { $this->author = $author; } public function getAuthor() { return $this->author; } public function get($index) { if(isset($this->quotes[$index])){ return $this->quotes[$index]; } else{ return $this->getRandom(); } } public function getRandom() { $max = count($this->quotes); $index = floor(rand(0, $max)); return $this->quotes[$index]; } }

Conclusion

Although a lot of these concepts might seem strange to even veteran Drupal developers they're a step in the right direction. Routing files create a clean and logicless configuration for mapping requests to controllers. Templates create a clean line of separation between business logic and presentation. Services provide a mechanism for reusing code and creating loosely coupled dependencies.

Routing will alleviate the mess of hook_menu and keep application entry points manageable. Symfony already has a command to query the application's configured routes, this is a huge win for security.

Template inheritance alone will be massively beneficial to the community, this allows developers and designers to minimize code duplication while still offering custom rendering for components that have need for a deviation.

Services will allow modules to provide reusable functionality that other modules can safely depend on and reference through the service container.