Writing your first Drupal service

We got a basic understanding of what a Drupal service is previously. Let's put that into action by creating a simple service in Drupal 8. Take note that this service what we are about to create doesn't do anything useful yet, and is written with a pedagogical purpose in mind.

This service will list a few best selling books from Leanpub. First, to deal with all the boilerplate of creating a module using DrupalConsole. Never ever write a module from scratch by hand!

$ drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > techbooks

 Enter the module machine name [techbooks]:
 > 

 Enter the module Path [/modules/custom]:
 > 

 Enter module description [My Awesome Module]:
 > My first service

 Enter package name [Custom]:
 > 

 Enter Drupal Core version [8.x]:
 > 

 Do you want to generate a .module file (yes/no) [no]:
 > no

 Define module as feature (yes/no) [no]:
 > no

 Do you want to add a composer.json file to your module (yes/no) [yes]:
 > no

 Would you like to add module dependencies (yes/no) [no]:
 > no


 Do you confirm generation? (yes/no) [yes]:
 > yes

Generated or updated files
 Site path: /var/www/html/drupal-8.0.2
 1 - modules/custom/techbooks/techbooks.info.yml

The next step is to write a service configuration in our module. This configuration dictates how the service class will be initialized and how it can be called from code. The Drupal convention is to write this in a <module-name>.services.yml.

services:
  techbooks.listbooks:
    class: Drupal\techbooks\ListBooks

The services configuration part begins with services: and lists all the services provided by our module. In our case, we have just one service. We give it a name, techbooks.listbooks. Its a good idea to namespace services, which can be done by prefixing a namespace name followed by a dot. The class tells Drupal what class to call to create the service. This should be the fully qualified name of the class. The services YAML file can also store any service related information as well. All the book related data like title, author etc. is stored in the same file.

parameters:
  leanpub.booklist:
    - {title: 'The Elements of Data Analytic Style', author: 'Jeff Leek', url: 'https://leanpub.com/datastyle'}
    - {title: "Build APIs You Won't Hate", author: 'Phil Sturgeon', url: 'https://leanpub.com/build-apis-you-wont-hate'}
    - {title: 'Easy Laravel 5', author: 'W. Jason Gilmore', url: 'https://leanpub.com/easylaravel'}
    - {title: 'Ansible for DevOps', author: 'Jeff Geerling', url: 'https://leanpub.com/ansible-for-devops'}
    - {title: 'Principles of Package Design', author: 'Matthias Noback', url: 'https://leanpub.com/principles-of-package-design'}
    - {title: 'Modernizing Legacy Applications In PHP', author: 'Paul M. Jones', url: 'https://leanpub.com/mlaphp'}
    - {title: 'Front-End Fundamentals', author: 'Carwin Young, Joe Fender', url: 'https://leanpub.com/front-end-fundamentals'}
    - {title: 'Talking with Tech Leads', author: 'Patrick Kua', url: 'https://leanpub.com/talking-with-tech-leads'}

We shall come back in a bit on how we will use this data. Note that creating a service is also mostly boilerplate code. Hence, we can avail Drupal Console to do the job for us and fill out the custom details.

$ drupal generate:service

 // Welcome to the Drupal service generator
 Enter the module name [techbooks]:
 > 

 Enter the service name [techbooks.default]:
 > techbooks.listbooks

 Enter the Class name [DefaultService]:
 > ListBooks

 Create an interface (yes/no) [yes]:
 > no


 Do you want to load services from the container (yes/no) [no]:
 > no


 Do you confirm generation? (yes/no) [yes]:
 > yes

Generated or updated files
 Site path: /var/www/html/drupal-8.0.2
 1 - modules/custom/techbooks/techbooks.services.yml
 2 - modules/custom/techbooks/src/ListBooks.php

 Rebuilding cache(s), wait a moment please.


 [OK] Done clearing cache(s).

We have defined a service, but haven't exactly told Drupal what it does. For this, we have to flesh out the ListBooks class inside the module's src/ directory.

namespace Drupal\techbooks;


/**
 * Class ListBooks.
 *
 * @package Drupal\techbooks
 */
class ListBooks {
  public function getBooks() {
    return t('All your books will be listed!');
  }
}

Once you have defined this service and enabled the module, you can use it anywhere in your code by invoking the service:

$book_list = \Drupal::service('techbooks.listbooks')->getBooks();

Drupal creates an instance of your listbooks service and fetches that instance on every call to the service. It effectively calls getBooks() of that instance, which returns a string. Let's add a route for listing books.

If you hit /techbooks in your browser, you should get this:

Your first service

Not a lot happening there. Let's actually list some books!

Services can take in parameters while initializing. They are just plain PHP classes in disguise after all! Let's pass the book list we defined earlier in the yaml file as a parameter to the listbooks service.

services:
  techbooks.listbooks:
    class: Drupal\techbooks\ListBooks
    arguments: ["%leanpub.booklist%"]

The only change is the arguments property. We refer to the leanpub.booklist property as the argument. The enclosing % is to indicate that it is an internal variable/property. Also, notice that we wrap the argument within quotes, failing which it will be interpreted as a literal string. For example, arguments: [leanpub.booklist] will send the string "leanpub.booklist" as argument to the ListBooks class, which is not what we want!

Let's modify the ListBooks class to take in the arguments.

class ListBooks {

  public function __construct($books) {
    $this->books = $books;
  }

  public function getBooks() {
    return $this->books;
  }
}

Changing the controller code accordingly,

public function bookList() {
  $book_list = \Drupal::service('techbooks.listbooks')->getBooks();
  $render = '';
  foreach($book_list as $book) {
    $render .= '<div><span>Title: ' . $book['title'] . '</span></div>';
    $render .= '<div><span>Author: ' . $book['author'] . '</span></div>';
  }
  return [
      '#type' => 'markup',
      '#markup' => $render
  ];
}

Now, our /techbooks page actually lists books!

Techbooks listing

It doesn't look pretty and all, but hey, we just created our first Drupal service :)

You can grab the code for this example from github and checkout the basic-service tag to see it come to life.

Resources