The decorator design pattern is among popular design patterns. We used the decorator design pattern to implement multilingual fallback of URL alias.
Drupal uses a language fallback feature while displaying translated content. So if you don’t have a translated node, it will show the original node from the base language. Currently in language fallback, if the English (base language) node has a URL alias and no translations, when we try to access the node in a different language, the URL will be something like:
/de/node/nid i.e. in typical node path.
The client’s requirement was that when Drupal uses the language fallback for content, it should do the same for URL aliases as well. While debugging, I found that everything was handled in the AliasStorage class, which is implementing AliasStorageInterface.
This is where decorators come into the picture.
To implement a decorator to AliasStorageInterface, we needed to implement one service that would specify where to find the related class implementation.
Implement the services.yml file for your module; let’s say my_module.services.yml.
Here we are specifying decorates attributes and which service to decorate.
You can read more about decorates attributes in the Symfony documentation here.
The decorates option tells the container that the my_module.path.decorating_alias_storage service replaces the path.alias_storage. The old path.alias_storage service is renamed to my_module.path.decorating_alias_storage.inner so you can inject it into your new service.
This way we will override the original path alias service. We will also pass other required services as well.
Can we apply multiple decorators to a single service?
Yes, we can. When you apply multiple decorators to one service, the “Decoration_priority” option will allow you to specify decoration order.
Decoration_priority’s value is an integer that defaults to 0, and higher priorities mean that decorators will be applied earlier.
Suppose we have multiple decorators given below:
So in this case, the generated code will be evaluated as:
Now we just need to implement the related decorator class.
So we create FallbackAliasStorage class inside the 'my_module/src' folder. You can see class implementation in this gist.
As we want to modify the path look up process and return URL alias of the base language when translation doesn’t exist, so we will focus on the lookupPathAlias() method of class.
In this method, we will look up all the relevant aliases of the current language that don’t have a translated alias. If we are viewing translated content, then it will return the URL alias itself.
This way, we can use decorators to implement multilingual URL alias fallback.
There are some limitations with this approach as well.
For example, we may have one node in English language with some alias, say (https://mysite.com/hello-world). When we visit this node in a different language, it will give the URL alias of English node.
Now there is one more translated node with the same alias (node in German language with alias “/hello-world”), so Drupal won’t show that node as we will return the path of a different node as the path of alias fallback.
But considering our site is multi-lingual and so are the aliases, there will not be any chances of collisions like this.