Drupal 8 Services Change Everything

Sara Olson, Marketing Analyst
#Featured | Posted

In previous versions of Drupal, certain subsystems could be completely swapped out for non-core implementations. The database abstraction layer is an example of this within core itself. Various cache backends and session handling (Memcache, Redis) are a contrib example. In Drupal 8, with the adoption of dependency-injection, literally every core subsystem, or service, can be overridden and changed. For a quick look at just how many different services there are, take a look at core.services.yml (note, this doesn't even include all the services provided by core's modules).

So what does this mean?

For the examples already present in previous versions, this works much the same way. For everything else, this means that application builders can selectively extend parts of core that aren't sufficient for their needs without hacking core. One quick example is the book.manager service defined by the book module. If an application needs to structure the relationship that defines a book slightly differently, the service can be replaced with a custom one and a single method can be overridden:

  1. class MyCustomBookManager extends BookManager {
  2.  
  3. /**
  4.   * {@inheritdoc}
  5.   *
  6.   * Structure child nodes in a custom way.
  7.   */
  8. public function loadBookChildren($nid, $hidden = FALSE) {
  9. // This will get the book's child nodes as the core book module normally does.
  10. $child_nids = parent::loadBookChildren($nid, $hidden);
  11.  
  12. foreach ($child_nids as $nid => $child_node) {
  13. // Add more child nodes using a custom field (for instance).
  14. ...
  15. }
  16.  
  17. return $child_nids;
  18. }
  19.  
  20. }

Another example comes from the Beta 2 Beta module (note, a different solution went into core instead, so wasn't needed in the end). We needed to support an upgrade path from Beta 7, but core's automated entity updates were getting in the way. So the quick fix was to swap out core's entity definition update manager for a custom one that could be configured to simply skip those updates until they would run successfully. This was done using beta2beta.services.yml:

  1. services:
  2. entity.definition_update_manager:
  3. class: Drupal\beta2beta\Entity\EntityDefinitionUpdateManager
  4. arguments: ['@entity.manager', '@state']

Note that the custom service can inject dependencies that the core one doesn't use. In this case, we inject the @state service since we need to check if automatic updates have been disabled or not.Then in the custom entity definition manager:

  1. /**
  2.   * {@inheritdoc}
  3.   */
  4. public function applyUpdates() {
  5. if ($this->state->get('beta2beta.disable_entity_definition_updates', FALSE)) {
  6. // Automatic updates are disabled.
  7. drupal_set_message($this->t('Automatic entity definition updates are disabled by the Beta 2 Beta module.'), 'warning');
  8. return;
  9. }
  10. parent::applyUpdates();
  11. }

Now while this is all very easy, and extremely powerful, I intentionally used the term application builder above, since if the contrib space started overriding a bunch of core services, modules would quickly become mutually exclusive to one another. I consider overriding services a last resort, as core provides many many new and old ways of overriding things (from traditional alter hooks, to the newly introduced concept of event subscribers, which I'll cover in a future post).That being said, modules can override or alter services provided by core or another module [by implementing the ServiceModifierInterface. The Drupal 8 version of the Features module does just this to get around a limitation set by core that won't allow modules with duplicate configurations to be enabled.The ability in Drupal 8 to customize and extend fundamental subsystems of core has potential for some truly amazing applications. It allows developers to do more complex things with much less boilerplate code.

Sara Olson

Marketing Analyst