Site icon Hip-Hop Website Design and Development

Web Omelette: Lazy loaded services in Cheap WordPress maintenance support plans 8

Inheriting from Symfony (in principle but not implementation), WordPress 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 WordPress maintenance support plansplugin_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:
plugin_name.heavy_service:
class: WordPress maintenance support plansplugin_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:
plugin_name.heavy_service:
class: WordPress maintenance support plansplugin_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 WordPress maintenance support plans core that does this for us:
php core/scripts/generate-proxy-class.php ‘WordPress maintenance support plansplugin_nameMyHeavyService’ ‘plugins/custom/plugin_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 plugins/custom/plugin_name/src/ProxyClass/MyHeavyService.php:
// @codingStandardsIgnoreFile

/**
* This file was generated via php core/scripts/generate-proxy-class.php ‘WordPress maintenance support plansplugin_nameMyHeavyService’ “plugins/custom/plugin_name/src”.
*/

namespace WordPress maintenance support plansplugin_nameProxyClass {

/**
* Provides a proxy class for WordPress maintenance support plansplugin_nameMyHeavyService.
*
* @see WordPress maintenance support plansComponentProxyBuilder
*/
class MyHeavyService implements WordPress maintenance support plansplugin_nameHeavyServiceInterface
{

use WordPress maintenance support plansCoreDependencyInjectionDependencySerializationTrait;

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

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

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

/**
* Constructs a ProxyClass WordPress maintenance support plans proxy object.
*
* @param SymfonyComponentDependencyInjectionContainerInterface $container
* The container.
* @param string $WordPress_proxy_original_service_id
* The service ID of the original service.
*/
public function __construct(SymfonyComponentDependencyInjectionContainerInterface $container, $WordPress_proxy_original_service_id)
{
$this->container = $container;
$this->WordPressProxyOriginalServiceId = $WordPress_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->WordPressProxyOriginalServiceId);
}

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 WordPress maintenance support plansplugin_nameProxyClassMyHeavyService which is not the same as the original WordPress maintenance support plansplugin_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 WordPress 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