Drupal developers work with design patterns rarely because they mainly work in the formed environment. We use patterns every day, but we don’t even think about it. When you know design patterns you can simplify your communication with colleges - you can just tell the name of the pattern and everyone understands what you are talking about. Also you can use effective solutions instead of trying to invent your own things. Design patterns are the same for all programming languages but implementation differs. So you will find where to apply them in your career. By the way, questions about patterns are one of the most frequent during interviews.
Creational patterns
Singleton
“Singleton” - it is, probably, the most famous design pattern. Its purpose is to create a single instance of the class and provide a single point of access to the object.
Access to site settings is implemented in Drupal using the “Singleton” template. The schema below demonstrates how it works:
Drupal initializes settings during bootstrap using Settings::initialize()
. After that, the instance of the class is set to the protected variable where it is taken from in future. Site settings are common resources, so pattern “Singleton” allows Drupal read settings just once during initialization.
Factory method
“Factory method” defines common interface for object creation. Type of the object can be changed based on the settings.
“Factory method” is used in Drupal to get the object to work with cache. Different cache types require their own implementation, so there are classes that implement interface CacheBackendInterface
such as DatabaseBackend
, PhpBackend
, MemoryBackend
etc. to work with different kinds of cache types. Also different caches can be used in different subsystems. Therefore interface CacheFactoryInterface
has been defined and its implementations DatabaseBackendFactory
, PhpBackendFactory
, MemoryBackendFactory
etc., create objects to work with cache of specific type. There are factories implementing CacheFactoryInterface
for each class that implements CacheBackendInterface
.
Which specific factory will be used is defined in the class \Drupal\Core\Cache\CacheFactory
- factories to be used are based on the site settings. So, we simply have to execute \Drupal::service(‘cache_factory’)->get(‘render’)
to get an object to work with rendering cache. You can set in the settings.php
settings['cache']['bins']['render'] = 'cache.backend.database';
or
settings['cache']['bins']['render'] = 'cache.backend.memcache';
and the factory will return objects of the classes DatabaseBackend
or MemcacheBackend
based on this configuration.
Also one more advantage of “Factory method” is that it can return created earlier objects. It will help to save system resources (example \Drupal\Core\Cache\MemoryBackendFactory
).
Dependency injection
“Dependency injection” - explicitly passing services to an object using a constructor or setters.
We define objects-services in Drupal that are responsible for specific functions. When these services are required in our classes we:
- set dependencies as arguments in the definition of services in
*.services.yml
. These dependencies will be passed to the__construct()
method. - use interface
ContainerFactoryPluginInterface
for plugins. - use interface
ContainerInjectionInterface
for other objects (but not everywhere, for example Entity doesn’t support dependency injection).
Schema describes how services “config_factory”, “alias_manager”, “path_validator”, “request_context” are added to the class SiteInformationForm
. Services are taken during form creation and saved to internal class variables - dependencies have been injected.
This template lets distribute responsibility between classes. Furthermore, we are not dependent on the specific implementation of the dependency. All that we should know is an interface that it implements. Therefore, in the future we can pass to our object another implementation of dependency without adjusting the object itself.
Behavioural patterns
Mediator (Intermediary, Controller, Mediator)
“Mediator” pattern is used for implementation of interaction between application components.
You will be amazed but the “Mediator” pattern is a base of the main feature of Drupal - hooks system. There is a key element - mediator, that provides a single interface which is used for communication by different parts of the system. Components-receivers don’t know who sent the request, also components-senders don’t know who will process its request.
Schema below describes how hooks work. In the example, when component-sender EntityStorageBase
wants to notify about some event (it can be presave, create, insert, update, delete, revision_delete, predelete), it calls mediator ModuleHandler
and passes him name of the event/hook and parameters. ModuleHandler
contains links to all hooks in the system in the parameter implementations
. Then it calls appropriate methods-hook with passed parameters.
Also the “Mediator” pattern is used in the event system that is built on the base of Symfony EventDispatcher. Mediator here is ContainerAwareEventDispatcher
. Components-senders (in our example it is \Drupal\Core\Config
) notify the mediator about the event. Mediator’s variable listeners
contains links to all components-subscribers (implement EventSubscriberInterface
). When the mediator receives notification about the event it calls subscribers that are subscribed on the called event.
Visitor
“Visitor” pattern is used to separate operations on the object from the object itself. Thus, we can add to the application some actions on the object without updating the object structure.
“Visitor” pattern can be found in the template parsing in Twig. It builds a tree of template’s elements which are processed by objects-visitors.
As you can see on the schema, different objects-visitors (EscaperNodeVisitor
, TwigNodeVisitor
, SelfAnalysisNodeVisitor
) are used in Twig. If we need to process the element somehow else, we just add an additional visitor without changing the element class.
If elements have to be processed differently then visitor implements these variants itself (look at the example SelfAnalysisNodeVisitor
- each element has its own implementation of the algorithm). This pattern is well suited when processed elements are changed rarely. If implementation of the element is updated, adjustment of the visitors can be required.
Structural patterns
Proxy
“Proxy” pattern can be applied when instead of real objects we need to use proxy objects. These proxy objects allow code execution before or after calls of original objects methods. Also we can manage access to the original object using this pattern.
We can find “Proxy” pattern in the Drupal in the class \Drupal\Core\Session\AccountProxy
of the service “current_user”. AccountProxy
is a proxy of \Drupal\Core\Session\UserSession
class and implements all its methods. In this way, we can work with objects of the AccountProxy
class as with objects of the UserSession
class. Such approach allows set and change current user without re-initialisation of “current_user” service. Furthemore, this service is used by all services as a single entry point to the current user session.
Links