Jisse Reitsma
Jisse Reitsma

A decorator for everything

Published at Apr 24th 2024
  Trainer & Developer @ Yireo
  Trainer & Developer
  Yireo
   Soest, The Netherlands
   

What is a decorator in the first place and why is it important?

First of all, a decorator is a design pattern. But when we talk about decorators in Symfony (or: service decorators) it is a class that sits in front of another original class (or it wraps itself around the original class), to change existing functionality.

The same can be accomplished with overriding the service and then simply extending from the original class. However, overriding the service is usually only done once or twice, while the benefit of decorators is that they can be transparently added and they can be added multiple times (without a hard limit).

This pattern is made possible by injecting the original service as a dependency to the new decorator class - and then calling upon the original methods via this internal dependency (instead of calling upon an inherited parent method). It is all quite technical and not that easy to understand at first.

Decorators are quite hard to get at first. However, once you conquer them it becomes possible to modify any public method of any service in the entire codebase. And with that they are very powerful - much more powerful than service overrides, events or other extensible patterns.

What are some good and bad practices to keep in mind when working with decorators?

Once you understand decorators, a huge number of solutions can be built by using decorator. During developer training, I usually say that Shopware has all kinds of different recipes - like adding a CLI command by tagging it as such, or modifying data via subscribable events. But all of those things can be accomplished as well with decorators. This shows how powerful decorator are. You can even influence behavior in the Vue-based Administration by using service decorators!

However, with great power comes great responsibility. Some services are purposefully designed to be extended via decorators. They serve as an API for other developers. However, there are also services that are simply used in bundles for their own sake - sometimes they are marked as internal but sometimes they are not. You could hack your way into anything, but does that lead into clean code?

Likewise the code itself is also either fit for decoration or not. For instance, decorating a service interface is a great thing. But decorating a service class (that has no interface) leads to confusion. Likewise, service tagging, events and other patterns exist to create clean code. The fact that you can use a decorator doesn’t mean that you automatically should.

How flexible is Shopware for working with service decorators?

I would say very flexible: Service decorators are themselves the most flexible part of Symfony. And many parts of the codebase of Shopware are already made extensible for this. For instance, ElasticSearch modifications can be made via decorators, checkout calculators are often extended, etcetera.

However, in Shopware, with each release, existing classes are marked as internal, turned into service contracts by using interfaces, cleaned up, etcetera. And more and more concrete classes that don’t have interfaces are abstracted into abstract classes, specifically for this purpose. It is a work in progress.

But I think my point is that - even though decorators are there to be used - decorators should be applied with care. If all of the modifications in a custom Shopware instance would be done via decorators - if a decorator would be applied for everything - than the code would become a mess.