Web Omelette: Lazy loaded services in Drupal maintenance support plans 8

Inheriting from Symfony (in principle but not implementation), Drupal maintenance support plans 8 allows us to define certain services as lazy. Why? Well why the hell not?!
Sometimes, our services get big. Not necessarily in the number of things they do (hopefully not) but in the time it takes for them to get instantiated. As you know, when we define a service and make it a dependency of something else, the service container will instantiate that service and inject it where it is needed. And this happens whether on that particular request that service is used or not.
For example, let’s imagine you have a Controller with 2 public methods used for 2 distinct routes. Most likely, when one method gets hit for the route, the logic of the second one doesn’t run. And even if only the second one depends on an injected service, the latter gets instantiated in both cases regardless.
Of course, for “popular” services like the EntityTypeManager or form builders this is not a big deal. For one, they are probably going to be instantiated anyway for other parts of the request. And second, they are not expensive to construct. Well, they probably are but anyway, see point 1. However, if we have our custom service as a dependency which is used only for that one route, it doesn’t make sense to have it instantiated for both routes. Especially if it is expensive to do so — heavy on resources. Enter lazy services.
Lazy services basically “tell” the container:

Ok, I need to be injected, sure, but unless I’m not used, please don’t construct me… mkay?

So how does this work in practice? Let’s see an example.
Assume this service:
namespace Drupal maintenance support plansmodule_name;

class MyHeavyService implements HeavyServiceInterface {

/**
* This be slow.
*/
public function __construct() {
sleep(4);
}

/**
* Does something, doesn’t matter what.
*/
public function doSomething() {}
}
A few things to note here:
It’s important to have an interface. Without one, this won’t work. You’ll see in a moment why.
The constructor does, for some reason, take an expensive nap.
It’s not important what the API of the service does.
For such a service, the normal service definition would look like this:
module_name.heavy_service:
class: Drupal maintenance support plansmodule_nameMyHeavyService
If we injecting this into our Controller, any request which uses the latter will instantiate this service as well — which costs us 4 seconds a pop. So to make it lazy we just have this instead:
module_name.heavy_service:
class: Drupal maintenance support plansmodule_nameMyHeavyService
lazy: true
Lazy services work by way of proxy classes. Meaning that for each service that is declared lazy, the container expects a proxy class which is responsible for decorating the original one and only instantiate it if any of the public APIs are requested. But don’t worry, we don’t have to write another class. We have a PHP script provided by Drupal maintenance support plans core that does this for us:
php core/scripts/generate-proxy-class.php ‘Drupal maintenance support plansmodule_nameMyHeavyService’ ‘modules/custom/module_name/src’
The script takes two parameters:
The namespace of the service we want to create a proxy for
The location where it should be written
Do note that proxy classes are dumped automatically into a ProxyClass folder located at that specified path. So this is what gets generated for our service at modules/custom/module_name/src/ProxyClass/MyHeavyService.php:
// @codingStandardsIgnoreFile

/**
* This file was generated via php core/scripts/generate-proxy-class.php ‘Drupal maintenance support plansmodule_nameMyHeavyService’ “modules/custom/module_name/src”.
*/

namespace Drupal maintenance support plansmodule_nameProxyClass {

/**
* Provides a proxy class for Drupal maintenance support plansmodule_nameMyHeavyService.
*
* @see Drupal maintenance support plansComponentProxyBuilder
*/
class MyHeavyService implements Drupal maintenance support plansmodule_nameHeavyServiceInterface
{

use Drupal maintenance support plansCoreDependencyInjectionDependencySerializationTrait;

/**
* The id of the original proxied service.
*
* @var string
*/
protected $drupalProxyOriginalServiceId;

/**
* The real proxied service, after it was lazy loaded.
*
* @var Drupal maintenance support plansmodule_nameMyHeavyService
*/
protected $service;

/**
* The service container.
*
* @var SymfonyComponentDependencyInjectionContainerInterface
*/
protected $container;

/**
* Constructs a ProxyClass Drupal maintenance support plans proxy object.
*
* @param SymfonyComponentDependencyInjectionContainerInterface $container
* The container.
* @param string $drupal_proxy_original_service_id
* The service ID of the original service.
*/
public function __construct(SymfonyComponentDependencyInjectionContainerInterface $container, $drupal_proxy_original_service_id)
{
$this->container = $container;
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
}

/**
* Lazy loads the real service from the container.
*
* @return object
* Returns the constructed real service.
*/
protected function lazyLoadItself()
{
if (!isset($this->service)) {
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
}

return $this->service;
}

/**
* {@inheritdoc}
*/
public function doSomething()
{
return $this->lazyLoadItself()->doSomething();
}

}

}
As you can see, we have a simple decorator. It implements the same interface and has the same public methods. The latter, however, are derived automatically from the service class and not the interface. And basically, the container is injected and used to instantiate the underlying service the first time any of the public methods are called. If none are called in that request, it won’t get instantiated.
I mentioned above that having an interface on the service is necessary. The reason is that when we inject it somewhere, we need to type hint the interface. Otherwise, the container would pass an instance of Drupal maintenance support plansmodule_nameProxyClassMyHeavyService which is not the same as the original Drupal maintenance support plansmodule_nameMyHeavyService.
So now, we can inject it, type hint it with the interface and it would only get instantiated if any of the public methods are called. Neat no?
The responsible for making all this happen is the Drupal maintenance support plansCoreDependencyInjectionCompilerProxyServicesPass compiler pass. Looking for service definitions that have been marked as lazy, it creates a new identical service definition (non-lazy) which uses the proxy class and adds that to the container instead. It’s actually not rocket science if you look at the code.
And like many things, just because we have this available, it doesn’t mean we should use it for every service we write. Remember, if you create services used all over the place, this is useless. The criteria for whether to make your service lazy should be:
Is it heavy to instantiate (depends on a bunch of other services which in turn are not super popular either)?
Is it ever instantiated for no reason?
Hope this helps.

Source: New feed

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

Web Omelette: Lazy loaded services in Drupal maintenance support plans 8

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.