Problem/Motivation
To call a service from a Plugin, it is recommended to use dependency injection rather than referencing \Drupal::service() directly. As more helper functions, such as format_date() are being deprecated in favor of direct service calls, more and more plugins are needing to use services.
Adding dependency injection to a plugin currently requires a fair amount of detailed "boiler-plate" code to implement the ContainerFactoryPluginInterface: adding a constructor, adding a create() static method, getting services from the container, passing them to the constructor, creating class properties to hold the services, and a lot of comments needed to pass codesniffer.
This is a large burden of effort, especially for more junior devs who are just trying to create a simple custom block to wrap a front-end twig template and need to call something like the date.formatter service. Suddenly, a previously simple call to format_date() becomes a large effort (or more commonly, they just ignore it and don't implement dependency injection).
Proposed resolution
To improve this situation, I propose adding the list of desired services to the plugin Annotation, then simply adding a Trait to the plugin that handles all of the dependency injection.
The included patch does this. First, it adds a new "ServiceFactory" to the Plugin system that extends the existing "ContainerFactory". After performing the normal parent functions, it loops through the "services" array in the plugin definition (annotation) and fetches the desired services from the current container.
The new PluginServiceInterface defines the getter and setter for the services that were fetched from the container.
The new PluginServiceTrait implements the getter and setter, currently by keeping the array of services. In the future it could potentially set properties of the plugin directly, but the service ids would need to be converted to valid property names (left as a todo)
Thus, in practice, to convert a plugin (such as a custom block) from using \Drupal::service('service_id') into properly using dependency injection, you add the 'service_id' to the 'services' array in the annotation, add the PluginServiceInterface to the implements section, and add the "use PluginServiceTrait" (along with adding the needed "use" statements at the top). Instead of calling \Drupal::service('service_id') the plugin calls $this->getService('service_id'). About as simple as we can make it.
Example code:
namespace Drupal\my_date_block\Plugin\Block;
use Drupal\Core\Plugin\PluginServiceInterface;
use Drupal\Core\Plugin\PluginServiceTrait;
use Drupal\Core\Block\BlockBase;
/**
* Provides my custom block.
*
* @Block(
* id = "my_date_block",
* admin_label = @Translation("My Date Block"),
* services = { "date.formatter" }
* )
*/
class MyDateBlock extends BlockBase implements PluginServiceInterface {
use PluginServiceTrait;
/**
* {@inheritdoc}
*/
public function build() {
return $this->getService('date.formatter')->format(time(), 'medium');
}
}
Thanks to @eclipsegc for his help with this patch!
Remaining tasks
Test needed.
User interface changes
None.
API changes
None. Only plugins that implement the new PluginServiceInterface are affected.
Data model changes
None.