Drupal 8 – a scary new beast?

Drupal 8 is a scary new beast which is now over 6 months old.

Scary new beast

It could be very unpredictable, another learning curve and a bubble burster for those who have settled down with the hook-based procedural approach. That's if you're a developer of-course; otherwise apart from a shiny new admin experience you may ask what's the fuss?

After a somewhat small encounter building out some features for this very blog with Drupal 8 I have a hand-full of observations I'd like to share, so here goes.

ninja exports - a life saver?

Well, the proper name for this is configuration management but I think ninja exports would've been a lot more interesting ...

Over the last few years I have adjusted fairly well to the usage of features for exporting configuration for content types, views, context and the rest of their friends in our processes.

I must admit I've always been one to forget committing the odd export here and there when deploying between environments, but even when I haven't, features manages to make everything a little more challenging; especially when deleting fields from entity types.

The new configuration management system provided by core allows you to export to YAML files whatever changes you make to entities and settings for views, content types, modules to be installed and more. These then get thrown into a staging folder under a config_{SOME_LONG_HASH_STRING_HERE} folder which can then be imported on another environment.

Now that sounds exactly like features right? ...

what's so ninja about it then?

Configuration is not stored in modules!

It's provided out of the box with core!

And so far I haven't forgotten to export anything ... yet.

A couple of issues

For one of my views configurations every time null appears as one of the dependency modules which I have to change on every export and I have no clue why this is the case.

module: 
   - null #Hey Andre, I'm not sure about something so here's a null just in case!
   - entityqueue 
   - node 
   - text 
   - user

I'm sure there is a reasonable explanation for this but I created two very specific helper modules that I realised they could easily be merged with an existing one. I got rid of the two modules and merged them into the existing one but every time I go to export changes they keep appearing in core.extension.yml.

menu_link_content: 0
menu_ui: 0
metatag: 0
migg_author: 0
migg_blog_alterations: 0 # Well I'm afraid he's not going anywhere ...
migg_form_alterations: 0 # Yeah, and he's not budging either.
migg_theme: 0
node: 0
page_cache: 0
path: 0
search: 0

 

welcome to the world of objects

Another core part of the Drupal 8 development experience is that forms and blocks are now defined as objects. Before in Drupal 7 for example we would define a form in a callback that would be referenced in an entry in a hook_menu() route callback mapping definition. 

is verbosity really the enemy?

Now there is the argument that this new object oriented pattern adopted brings about the inconvenience of verbosity through boiler plate code. Take for instance the case where we want to define a custom module with some functionality providing services or carrying out calculations.

In this case we have a few different functions for handling cost and price calculations ...

<?php

function mymodule_calculate_cost($item_ids, $options) {
  // Do some cost calculation here.
}

function mymodule_retrieve_quotation($item_ids, $options) {
  // Do complex quotation work here.
}

function mymodule_calculate_price($cost, $markup) {
  // Work out the selling price for some items given
  // the cost and the percentage markup.
}

function mymodule_do_more_stuff($input) {
  // Does some more awesome stuff.
}

The above shows the most likely approach we would take with Drupal 7 which is fairly straight forward and straight forward to access these functions from other files in the module or other modules by simply calling the function name.

Now for the Object-oriented Drupal 8 approach:

<?php

namespace Drupal\mymodule\Services;

class MyService {
    
  public function calculateCost($item_ids, $options) {
    // Do some cost calculations here.        
  }
  
  public function retrieveQuotation($item_ids, $options) {
    // Carrying out calculating a quotation.
  }
  
  public function calculatePrice($cost, $markup) {
    // Work out the selling price for some items given
    // the cost and the percentage markup.
  }

  public function doMoreStuff($input) {
    // Does some more awesome stuff.
  }
}


Now you might think well that isn't too bad, we've just added an object-oriented flavour by wrapping all our functionality up in a class. The tricky part is accessing this functionality. The Drupal 8 standards dictate custom functionality should be provided in a service which has to be registered explicitly in configuration and then accessed through the application's service container which provides all core, contrib and custom services.

Accessing this from a custom block would look something like this:

<?php

namespace Drupal\mymodule\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\mymodule\Services\MyService;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides our very own custom block.
 *
 * @Block(
 *   id = "my_block",
 *   admin_label = @Translation("My block"),
 * )
 */
class MyBlock extends BlockBase implements ContainerFactoryPluginInterface {
  
  private $service;

  public function __construct(MyService $service, array $configuration, 
                              $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->service = service;
  }

  public static function create(ContainerInterface $container, 
                                array $configuration, $plugin_id, $plugin_definition)) {
    return new static(
      $container->get('mymodule.service'),
      $configuration,
      $plugin_id,
      $plugin_definition
    );
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    $price = $this->service->calculatePrice(12, 150);
    return array(
      '#markup' => $this->t('The price is @price!',        
                         array('@price' => $price)),
    );  
  }
}


Now that is a huge difference from simply $price = mymodule_calculate_price(12,150); in a hook_block_view definition in Drupal 7. So you might be thinking why make something that was so simple so complicated. Here's what I would consider as a few reasons why:

maintanability in expressivity

Maintaining a collection of explicitly named and namespaced classes for services, blocks, forms and other plugins makes it easier for multiple developers to work with than a list of functions in a module file that would handle providing a range of different components and functionality.

it's the way most web frameworks work

Splitting functionality into services to be dependency injected is actually not that new, the Java EE framework for example adopted this approach before the release of Drupal 7 and the whole purpose of Symfony which provides some of the Drupal 8 foundations is about splitting an application into loosely coupled components that can work independently from each other.

my transition into the world of objects and Drupal 8

For a long time I didn't think much of an object-oriented approach taking over Drupal as I was already familiar with object-oriented programming and thought it would take away from the simplicity of Drupal's procedural approach. Though I still believe in making things as concise as possible while maintaining an understandable code-base I think the use of Symfony components and a shift in paradigm was a step forward in integrating more developers into Drupal and accepting that Drupal is not an island. Overall I'm looking forward to working with Drupal 8 more.