Services In Drupal 8

broken image


Drupal 8 comes with a services based architecture allowing clean dependency injection, separation of concerns and another way to modify how Drupal works without hacking core

  1. Cached
  2. Restful Web Services In Drupal 8

# @deprecated in Drupal 8.0.x and will be removed before 9.0.0. Use the other # entity. services instead. Entity.manager: # We cannot set the deprecated property here because many test cases still # rely on this service and they would fail with deprecation warnings. Definition A Drupal 8 application has several objects, some of which specialize in the performance of specific tasks across the system, e.g. Logging, mailing, authentication, etc. There might even be a simple object with a couple of methods to be shared by other classes. These objects are called services. Web services in Drupal 8 Drupal 7 and even Drupal 6 had some support for web services, but that was in the form of contributed modules. Thanks to Web Services and Context Core Initiative (WSCCI), Drupal 8 has web services built into core, and it has been greatly enhanced. Core Drupal 8 modules for web services. Drupal 8 now bundles into core the RESTful project. This is an exciting time to have this available as we start to build more decoupled solutions using often the same data set. One example where this could be used is with an existing Drupal 8 site and the need for a mobile app. Drupal 8 being moved to Symfony was driven by Web Services and Content Core Initiative (WSCCI) in a motive to transform Drupal from a first-class CMS to a first-class REST server with first-class.

You've probably heard that Drupal 8 lets you swap out a core service for your own implementation, hey, I even said it myself here and here, but how do you achieve that?

Read on to find out how to manipulate Drupal 8 services at run-time and how this compares to other popular PHP Frameworks like Laravel, Silex and Symfony

Services based architecture

Drupal 8 comes with a service-based architecture. Put simply, services are objects that are responsible for representing operations that cannot be modelled as value-objects or entities. They are responsible for providing infrastructure concerns, operating on domain objects (value-objects and entities) and executing operations on domain objects.

Services in Drupal 8 are managed and instantiated using the dependency injection container, also known as the service container.

The service container is an implementation of the Inversion of Control design pattern. Its primary role is to build services on behalf of client services, to decouple the clients from the burden of knowing how the dependent service is constructed.

In Drupal 8, the primary registration of services is done via YAML based configuration, in the form of core and each enabled module's services.yml file.

An example if you will

Lets consider the forum index storage service as an example. This service is responsible for managing the association between forum posts (nodes) and forums (taxonomy terms) in an optimized format to allow forum module to operate in the most efficient manner. It implements DrupalforumForumIndexStorageInterface. Everywhere the forum index storage service is required, it is typehinted using the interface. This complies with the Liskov substitution principle and allows an alternate implementation of ForumIndexStorageInterface to be substitued. The default forum index storage service uses the database connection to read and write to the {forum_index} table. Lets assume you're working on a client-project that needs forum module, but for performance sake you want to store forum posts in a No-SQL database like MongoDB or perhaps the managed DynamoDb service from Amazon AWS.

See

Modifying services and the container

Services In Drupal 8

Tim Milwood gave a static example of how you might edit a service in the container, but there are advanced use cases that it does not handle - what if you want to change the arguments - or what if you need to perform dynamic modifications - i.e. only if a particular module is installed, or based on some other state of the container. Consider again the forum index storage example. The default service definition looks like this:

But in our example, we don't want to inject the database. Lets assume we're using DynamoDb and we have a Dynamo client service which we need injected instead. The alias approach doesn't allow us to do this.

Drupal

This is where DrupalCoreDependencyInjectionServiceModifierInterface and the DrupalCoreDependencyInjectionServiceProviderInterface come in. To implement this interface we need to first give our class a magic name. This is one of the few non-hook places in core where convention prevails over configuration, but for those familiar with hooks magic names are nothing new.

Start by taking your module name and converting it into camel case, then add the ServiceProvider suffix. For example if our module for the Dynamo Db implementation of forum index storage has a machine name of forum_dynamo, we'd need a fully qualified class name of Drupalforum_dynamoForumDynamoServiceProvider. You can also extend from DrupalCoreDepedencyInjectionServiceProviderBase if you like, which already implements the interfaces for you. Lets look at the two possible interfaces in detail.

ServiceProviderInterface

This interface is for when your module needs to register services in the service-container in a dynamic fashion. YAML based service configuration is great, and very easy to parse - but what if you need to alter your service definition based on another condition in the environment - such as which modules are enabled. This is where ServiceProviderInterface comes in. It consists of a single method - register as follows:

The register method is called during container building, before dumping to disk. From here we can interact with other services. If you need to check which modules are enabled you can use the getParameter method on the container builder argument like so:

A great example of this in core is DrupallanguageLanguageServiceProvider, which is responsible for registering the language_request_subscriber and path_processor_language services, only if the site is multi-lingual. Clearly this couldn't be done in YAML alone.

ServiceModifierInterface

This interface is the one we want for modifying existing service definitions. Heading back to our example, we want to change the forum.index_storage service.

It consists of a single method as follows:

So we start by getting a reference to the forum.index_storage definition like so

Then we need to change the arguments like so - assuming that the dynamo client has machine name 'dynamo.client':

Again DrupallanguageLanguageServiceProvider provides a great example of this in core, changing the default language manager provided in core to the configurable one provided by the language module, configuring the language config factory override service and setting a container parameter for the default languages.

Parallels with other PHP frameworks

Now what Drupal does in this space, registering and altering is not-dissimilar to comparable approaches in other PHP frameworks such as Symfony Full-stack framework, Laravel 5 and Silex.

Symfony

Symfony bundles can define an extension, in this case the extension is the same name as the bundle, except the 'Bundle' suffix is replaced with 'Extension'. The extension class needs to implement SymfonyComponentDependencyInjectionExtensionExtensionInterface, but normally would just extend from SymfonyComponentDependencyInjectionExtensionExtension. This has a load method which is analagous to Drupals' register method.

Silex

In Silex, services are provided by service providers. Because Silex is geared towards bespoke applications rather than generic portable code each service provider is manually registered in the application using $app->register(). In this case a service provider is a class implementing SilexServiceProviderInterface which consists of a boot and a register method. The register method is analagous to Drupal's ServiceProviderInterface. The boot method serves the same use-case as the alter method in Drupal's ServiceModifierInterface.

Laravel

Like Silex, Laravel is also concerned with bespoke applications so service provider registration is done manually in your config/app.php. Service providers extend from IlluminateSupportServiceProvider. Just like Silex there are boot and register methods, and both serve the same use case as Drupal's alter and register methods respectively.

Conclusion

Being able to modify services adds a whole-new level of customisation to Drupal, but as seen by the similarities to other framworks, one which should feel familiar to developers coming from other PHP frameworks to Drupal. I think as time goes by modifying services will become one of the essential skills in a Drupal developer's bag of tricks.

If you want to hear more about how Drupal 8 will be game-changing, we're running a series of 'Get Ready for Drupal 8' seminars in many Australian capital cities on Thursday August 6th 2015. These are free to attend - but registration in advance is required. Hope to see you there.

Comments

One use case that I've tried looking into, but been a little confused by, is what to do if you want to take a service, decorate it with another class that wraps the original service, and then replace the original service definition with the new decorator.

Is there a way to do this without shuffling things around (i.e. copying the original service to a new name and then defining a new service that depends on the renamed service) ?

I've noticed that there are some decoratedService methods in SymfonyComponentDependencyInjectionDefinition but I haven't been able to find an example of how they were meant to be used.

Disclaimer: This is not a tutorial about the services module, but rather the object-oriented PHP concept of Services, the Service Container and Dependency Injection.

Intro

As a dyed-in-the-wool Drupal programmer looking to get into coding Drupal 8, there were a few modern subjects I had to familiarize myself with. Chief among them was the concept in Symfony and Drupal 8 called Services, which help keep your code decoupled and, in my opinion, easier to read.

A Service is simply an object, usually with one instance of each service's class for each service on a site. For example, Drupal 8 sites have a service for sending email, for logging errors, for making HTTP requests, and dozens of other common tasks.

While Services are objects, not all objects are suitable services. For example, a node is not a service; it is content. A View is also not a service. There is, however, a token service, which is a great example, since you only really need one token service for your entire site. The new Configuration Management systems in Drupal 8 use services extensively, and you will learn a bit about Config in this blog post. First, I'll show you how a common function, making links, uses services.


A Quick Example of Services

Services are used in a number of ways in Drupal. Many of the core systems are implemented as services, and many familiar core functions are now procedural wrappers for services.

For example, the l() function is now a wrapper for the LinkGenerator::generate() function. You call the function as such Drupal::l(), because everything is namespaced now, but that's another blog post.

The source code of l() is:

[gist:eb7774bf8c12b51c5186:quick_example.php]

This says to get the service called link_generator and call its generate() method. This way, you don't have to know where the link_generator class is defined, or even what class name it uses, but you can always find it if you need to.


An Example of Services

One way of thinking about Services, if this is a new concept for you, is the set-top-box on your TV. Whether it's made by Apple, Amazon, Google or Roku, these set-top boxes all have Services in common. I don't need to know the IP address and the API schema for Netflix in order to watch a film. If I'd rather watch HBO Go, I can ask my set-top-box to load that Service instead. The set-top-box is a Service Container that gives you a means of accessing any of these Services, obscuring the technical details. Drupal also uses the idea of a Service Container to abstract the loading and instantiation of service objects. The above code called the static::getContainer() method, returning the service container, which was then used to load link_gernerator.


A Practical Example of Services and the Service Container in Drupal 8

For the following example, let's assume we are creating a NetTV module for a Drupal 8 site that will give us some of the features we expect out of an internet TV service. In Drupal 8, you can put your custom modules in the /modules directory in the document root of your Drupal site. All your files for the following example will live in the /modules/custom/nettv directory.

Each Drupal 8 site comes with close to 300 Services defined by core. One example we might use in our set-top-box example is the Config service or Simple Configuration API. The config service is referenced by the /core/core.services.yml file, like so:

[gist:eb7774bf8c12b51c5186:practical_example.yml]

Services In Drupal 8

The ConfigFactory service is a class that can load Config information out of your Drupal site (Config has replaced variable_get() and variable_set() in Drupal 8). The code above maps an alias that can be invoked in a modulename.services.yml file with the string @config.factory. The @ symbol in this case tells Drupal's Service Container to find the ConfigFactory class and how it should be instantiated with the arguments line. The @config.storage is another service that knows where to store variables on your site - usually in YAML files. @config.typed helps to store different data types in config objects, and the @event_dispatcher helps to 'lazy load' classes so loading code and instantiating objects only happens when the objects are actually needed. This reduces the overhead of your application and keeps the site fast and lean.

Part of the NetTv module will be a nettv.services.yml file that lives in your nettv module directory:

[gist:eb7774bf8c12b51c5186:practical_example2.yml]

There are lots of things to point out about this file:

  • There is a reference to a class called WatchCartoons, which is defined as a Service provided by this module.
  • Just as with the definition of config.factory in core, we are naming a Service called nettv.watch_shows.
  • The name of the Service is anything you want. It does not depend on the class
    name, which keeps Services flexible.
  • The WatchCartoons class you write does not need to be specific to this module or
    even Drupal to be used because eventually the Service Container will call it by
    using this YAML definition. i.e. You can bring in outside libraries from github
    or other projects.
  • By using this Service model and namespacing, you could have another module
    on the site that defines a WatchCartoons class and never anticipate a conflict
    between the two.
  • When you add the ConfigFactory to your class' constructor, it will be added
    using Dependency Injection. You can rest easy knowing that Drupal's Service
    Container will take care of loading the correct code and getting the right object
    to the constructor at the appropriate time.

The Code

Before you go any farther, I'm going to save you several steps by having you clone this github repository, which contains a copy of the code for this tutorial. Go and get a copy of this module and open it in your favorite editor, then come back and keep reading.


Constructing an Object that Depends on a Service

In WatchCartoons.php, look at the constructor for the class:

[gist:eb7774bf8c12b51c5186:constructing_an_object.php]

Notice that WatchCartoons is just a basic PHP class. It does not extend or implement anything specific to Drupal. In this case, we are using ConfigFactory but any code that uses this object does not know that. The implementation is kept inside our methods.

As soon as you create a new WatchCartoons object, it anticipates instance of ConfigFactory passed in so its methods can use it to retrieve and store config data. The argument to the constructor is type-hinted, so it must be a ConfigFactory object. We made sure this would work in our nettv.services.yml above.

When you write code that expects an instance of the ConfigFactory, you're using Dependency Injection.You don't instantiate the $config_factory; your system provides it with the Service Container. You'll sometimes see a Service Container referred to as a Dependency Injection Container.


Using the Config Factory as a Service

Finally, look at the next method on the WatchCartoons class:

[gist:eb7774bf8c12b51c5186:config_factory_service.php]

The config itself is loaded from a YAML file inside your project config/install/nettv.basic_information.yml - until Drupal saves it. You can set the default values for your NetTV module like so:

[gist:eb7774bf8c12b51c5186:default_values.yml]

The code in the getBasicInformation() method will load the values from Config and use them to print out the message with the configurable variables you define. Right now there is no way to change this config once it is loaded. That's another tutorial.

Bonus: there is also an instance of Drupal::l() from before, just so you can see it in practice.


Using Your Class in Drupal

This wouldn't be a complete tutorial unless you could view the information about your NetTV service somewhere on your Drupal site, so we'll make a block. In your project you'll need a file at src/Plugin/Block/NetTVBlock.php with this code:

[gist:eb7774bf8c12b51c5186:using_class_drupal.php]

This code provides a Block you can enable through Drupal's admin interface. I assume you know how to place a block on a Drupal site, even in Drupal 8. Once you do, you will see the output from the getBasicInformation() method in the body of the block, like this:

Notice that while there are lots of OOP-isms in this project, we never really had to use the new keyword. We are not in the weeds instantiating objects, just snapping together Services, Config and finishing everything off in the Drupal UI. Once you learn how to work with Services and Dependency Injection, you'll be thinking at a higher level and you can focus on one specific task. This is one of the promises of Drupal to solve 80% of the problems for you and let you focus on the 20% of your project that is unique.

Cached


Using Service Container for a Block

In our Block definition, we used the create() method, which is part of the ContainerFactoryPluginInterface. This allows us to take advantage of the power of the Service Container when loading our block, and it keeps implementation-specific details out of the build() method of the block. If we wanted to switch out the WatchCartoons class with another one, we would simply need to make sure any new class also had a getBasicInformation() method and change the nettv.services.yml in our module. If the new class had a different means of getting this information, we still wouldn't have to touch the build() method, as long as we passed in a string. At the end of the day, this is just an example. Your mileage will vary.

The new concept of using a Service Container to instantiate objects should be a bit clearer to you now. Remember that it exists to give you a standard way to work with objects (Services) you need to include in your project. Also, remember that using it the right way will save you time and headaches while making it easier for non-Drupal programmers to read and understand your code. While this can feel like more work to a Drupal veteran, remember that procedural Drupal 7 code looks pretty dense to other coders. This method employs less 'magic naming'; instead, it says what it does and how the system should go about loading and running everything.


Further Reading

  • API.Drupal.org: BlockBase.php and ContainerFactoryPluginInterface.php

Fill out the form to learn how FFW can support your needs.

Read next:

Open-source software: A CMO's competitive advantage

Services In Drupal 8

Modifying services and the container

Tim Milwood gave a static example of how you might edit a service in the container, but there are advanced use cases that it does not handle - what if you want to change the arguments - or what if you need to perform dynamic modifications - i.e. only if a particular module is installed, or based on some other state of the container. Consider again the forum index storage example. The default service definition looks like this:

But in our example, we don't want to inject the database. Lets assume we're using DynamoDb and we have a Dynamo client service which we need injected instead. The alias approach doesn't allow us to do this.

This is where DrupalCoreDependencyInjectionServiceModifierInterface and the DrupalCoreDependencyInjectionServiceProviderInterface come in. To implement this interface we need to first give our class a magic name. This is one of the few non-hook places in core where convention prevails over configuration, but for those familiar with hooks magic names are nothing new.

Start by taking your module name and converting it into camel case, then add the ServiceProvider suffix. For example if our module for the Dynamo Db implementation of forum index storage has a machine name of forum_dynamo, we'd need a fully qualified class name of Drupalforum_dynamoForumDynamoServiceProvider. You can also extend from DrupalCoreDepedencyInjectionServiceProviderBase if you like, which already implements the interfaces for you. Lets look at the two possible interfaces in detail.

ServiceProviderInterface

This interface is for when your module needs to register services in the service-container in a dynamic fashion. YAML based service configuration is great, and very easy to parse - but what if you need to alter your service definition based on another condition in the environment - such as which modules are enabled. This is where ServiceProviderInterface comes in. It consists of a single method - register as follows:

The register method is called during container building, before dumping to disk. From here we can interact with other services. If you need to check which modules are enabled you can use the getParameter method on the container builder argument like so:

A great example of this in core is DrupallanguageLanguageServiceProvider, which is responsible for registering the language_request_subscriber and path_processor_language services, only if the site is multi-lingual. Clearly this couldn't be done in YAML alone.

ServiceModifierInterface

This interface is the one we want for modifying existing service definitions. Heading back to our example, we want to change the forum.index_storage service.

It consists of a single method as follows:

So we start by getting a reference to the forum.index_storage definition like so

Then we need to change the arguments like so - assuming that the dynamo client has machine name 'dynamo.client':

Again DrupallanguageLanguageServiceProvider provides a great example of this in core, changing the default language manager provided in core to the configurable one provided by the language module, configuring the language config factory override service and setting a container parameter for the default languages.

Parallels with other PHP frameworks

Now what Drupal does in this space, registering and altering is not-dissimilar to comparable approaches in other PHP frameworks such as Symfony Full-stack framework, Laravel 5 and Silex.

Symfony

Symfony bundles can define an extension, in this case the extension is the same name as the bundle, except the 'Bundle' suffix is replaced with 'Extension'. The extension class needs to implement SymfonyComponentDependencyInjectionExtensionExtensionInterface, but normally would just extend from SymfonyComponentDependencyInjectionExtensionExtension. This has a load method which is analagous to Drupals' register method.

Silex

In Silex, services are provided by service providers. Because Silex is geared towards bespoke applications rather than generic portable code each service provider is manually registered in the application using $app->register(). In this case a service provider is a class implementing SilexServiceProviderInterface which consists of a boot and a register method. The register method is analagous to Drupal's ServiceProviderInterface. The boot method serves the same use-case as the alter method in Drupal's ServiceModifierInterface.

Laravel

Like Silex, Laravel is also concerned with bespoke applications so service provider registration is done manually in your config/app.php. Service providers extend from IlluminateSupportServiceProvider. Just like Silex there are boot and register methods, and both serve the same use case as Drupal's alter and register methods respectively.

Conclusion

Being able to modify services adds a whole-new level of customisation to Drupal, but as seen by the similarities to other framworks, one which should feel familiar to developers coming from other PHP frameworks to Drupal. I think as time goes by modifying services will become one of the essential skills in a Drupal developer's bag of tricks.

If you want to hear more about how Drupal 8 will be game-changing, we're running a series of 'Get Ready for Drupal 8' seminars in many Australian capital cities on Thursday August 6th 2015. These are free to attend - but registration in advance is required. Hope to see you there.

Comments

One use case that I've tried looking into, but been a little confused by, is what to do if you want to take a service, decorate it with another class that wraps the original service, and then replace the original service definition with the new decorator.

Is there a way to do this without shuffling things around (i.e. copying the original service to a new name and then defining a new service that depends on the renamed service) ?

I've noticed that there are some decoratedService methods in SymfonyComponentDependencyInjectionDefinition but I haven't been able to find an example of how they were meant to be used.

Disclaimer: This is not a tutorial about the services module, but rather the object-oriented PHP concept of Services, the Service Container and Dependency Injection.

Intro

As a dyed-in-the-wool Drupal programmer looking to get into coding Drupal 8, there were a few modern subjects I had to familiarize myself with. Chief among them was the concept in Symfony and Drupal 8 called Services, which help keep your code decoupled and, in my opinion, easier to read.

A Service is simply an object, usually with one instance of each service's class for each service on a site. For example, Drupal 8 sites have a service for sending email, for logging errors, for making HTTP requests, and dozens of other common tasks.

While Services are objects, not all objects are suitable services. For example, a node is not a service; it is content. A View is also not a service. There is, however, a token service, which is a great example, since you only really need one token service for your entire site. The new Configuration Management systems in Drupal 8 use services extensively, and you will learn a bit about Config in this blog post. First, I'll show you how a common function, making links, uses services.


A Quick Example of Services

Services are used in a number of ways in Drupal. Many of the core systems are implemented as services, and many familiar core functions are now procedural wrappers for services.

For example, the l() function is now a wrapper for the LinkGenerator::generate() function. You call the function as such Drupal::l(), because everything is namespaced now, but that's another blog post.

The source code of l() is:

[gist:eb7774bf8c12b51c5186:quick_example.php]

This says to get the service called link_generator and call its generate() method. This way, you don't have to know where the link_generator class is defined, or even what class name it uses, but you can always find it if you need to.


An Example of Services

One way of thinking about Services, if this is a new concept for you, is the set-top-box on your TV. Whether it's made by Apple, Amazon, Google or Roku, these set-top boxes all have Services in common. I don't need to know the IP address and the API schema for Netflix in order to watch a film. If I'd rather watch HBO Go, I can ask my set-top-box to load that Service instead. The set-top-box is a Service Container that gives you a means of accessing any of these Services, obscuring the technical details. Drupal also uses the idea of a Service Container to abstract the loading and instantiation of service objects. The above code called the static::getContainer() method, returning the service container, which was then used to load link_gernerator.


A Practical Example of Services and the Service Container in Drupal 8

For the following example, let's assume we are creating a NetTV module for a Drupal 8 site that will give us some of the features we expect out of an internet TV service. In Drupal 8, you can put your custom modules in the /modules directory in the document root of your Drupal site. All your files for the following example will live in the /modules/custom/nettv directory.

Each Drupal 8 site comes with close to 300 Services defined by core. One example we might use in our set-top-box example is the Config service or Simple Configuration API. The config service is referenced by the /core/core.services.yml file, like so:

[gist:eb7774bf8c12b51c5186:practical_example.yml]

The ConfigFactory service is a class that can load Config information out of your Drupal site (Config has replaced variable_get() and variable_set() in Drupal 8). The code above maps an alias that can be invoked in a modulename.services.yml file with the string @config.factory. The @ symbol in this case tells Drupal's Service Container to find the ConfigFactory class and how it should be instantiated with the arguments line. The @config.storage is another service that knows where to store variables on your site - usually in YAML files. @config.typed helps to store different data types in config objects, and the @event_dispatcher helps to 'lazy load' classes so loading code and instantiating objects only happens when the objects are actually needed. This reduces the overhead of your application and keeps the site fast and lean.

Part of the NetTv module will be a nettv.services.yml file that lives in your nettv module directory:

[gist:eb7774bf8c12b51c5186:practical_example2.yml]

There are lots of things to point out about this file:

  • There is a reference to a class called WatchCartoons, which is defined as a Service provided by this module.
  • Just as with the definition of config.factory in core, we are naming a Service called nettv.watch_shows.
  • The name of the Service is anything you want. It does not depend on the class
    name, which keeps Services flexible.
  • The WatchCartoons class you write does not need to be specific to this module or
    even Drupal to be used because eventually the Service Container will call it by
    using this YAML definition. i.e. You can bring in outside libraries from github
    or other projects.
  • By using this Service model and namespacing, you could have another module
    on the site that defines a WatchCartoons class and never anticipate a conflict
    between the two.
  • When you add the ConfigFactory to your class' constructor, it will be added
    using Dependency Injection. You can rest easy knowing that Drupal's Service
    Container will take care of loading the correct code and getting the right object
    to the constructor at the appropriate time.

The Code

Before you go any farther, I'm going to save you several steps by having you clone this github repository, which contains a copy of the code for this tutorial. Go and get a copy of this module and open it in your favorite editor, then come back and keep reading.


Constructing an Object that Depends on a Service

In WatchCartoons.php, look at the constructor for the class:

[gist:eb7774bf8c12b51c5186:constructing_an_object.php]

Notice that WatchCartoons is just a basic PHP class. It does not extend or implement anything specific to Drupal. In this case, we are using ConfigFactory but any code that uses this object does not know that. The implementation is kept inside our methods.

As soon as you create a new WatchCartoons object, it anticipates instance of ConfigFactory passed in so its methods can use it to retrieve and store config data. The argument to the constructor is type-hinted, so it must be a ConfigFactory object. We made sure this would work in our nettv.services.yml above.

When you write code that expects an instance of the ConfigFactory, you're using Dependency Injection.You don't instantiate the $config_factory; your system provides it with the Service Container. You'll sometimes see a Service Container referred to as a Dependency Injection Container.


Using the Config Factory as a Service

Finally, look at the next method on the WatchCartoons class:

[gist:eb7774bf8c12b51c5186:config_factory_service.php]

The config itself is loaded from a YAML file inside your project config/install/nettv.basic_information.yml - until Drupal saves it. You can set the default values for your NetTV module like so:

[gist:eb7774bf8c12b51c5186:default_values.yml]

The code in the getBasicInformation() method will load the values from Config and use them to print out the message with the configurable variables you define. Right now there is no way to change this config once it is loaded. That's another tutorial.

Bonus: there is also an instance of Drupal::l() from before, just so you can see it in practice.


Using Your Class in Drupal

This wouldn't be a complete tutorial unless you could view the information about your NetTV service somewhere on your Drupal site, so we'll make a block. In your project you'll need a file at src/Plugin/Block/NetTVBlock.php with this code:

[gist:eb7774bf8c12b51c5186:using_class_drupal.php]

This code provides a Block you can enable through Drupal's admin interface. I assume you know how to place a block on a Drupal site, even in Drupal 8. Once you do, you will see the output from the getBasicInformation() method in the body of the block, like this:

Notice that while there are lots of OOP-isms in this project, we never really had to use the new keyword. We are not in the weeds instantiating objects, just snapping together Services, Config and finishing everything off in the Drupal UI. Once you learn how to work with Services and Dependency Injection, you'll be thinking at a higher level and you can focus on one specific task. This is one of the promises of Drupal to solve 80% of the problems for you and let you focus on the 20% of your project that is unique.

Cached


Using Service Container for a Block

In our Block definition, we used the create() method, which is part of the ContainerFactoryPluginInterface. This allows us to take advantage of the power of the Service Container when loading our block, and it keeps implementation-specific details out of the build() method of the block. If we wanted to switch out the WatchCartoons class with another one, we would simply need to make sure any new class also had a getBasicInformation() method and change the nettv.services.yml in our module. If the new class had a different means of getting this information, we still wouldn't have to touch the build() method, as long as we passed in a string. At the end of the day, this is just an example. Your mileage will vary.

The new concept of using a Service Container to instantiate objects should be a bit clearer to you now. Remember that it exists to give you a standard way to work with objects (Services) you need to include in your project. Also, remember that using it the right way will save you time and headaches while making it easier for non-Drupal programmers to read and understand your code. While this can feel like more work to a Drupal veteran, remember that procedural Drupal 7 code looks pretty dense to other coders. This method employs less 'magic naming'; instead, it says what it does and how the system should go about loading and running everything.


Further Reading

  • API.Drupal.org: BlockBase.php and ContainerFactoryPluginInterface.php

Fill out the form to learn how FFW can support your needs.

Read next:

Open-source software: A CMO's competitive advantage

'Should I wait for Drupal 9 to upgrade my Drupal 7 site?'

Restful Web Services In Drupal 8

A why-to and how-to guide from FFW on getting the most out of the measurement tools that integrate with your website.





broken image