There are two patterns in PHP that are very similar; The Decorator Pattern and The Proxy Pattern. Because they are so similar, you can quickly mistake one for the other. Does that matter? Maybe not, but I think it's good to know the differences when communicating about them.
Similarities between Decorators and Proxies
Both the Decorator Pattern and the Proxy Pattern revolve around the idea of wrapping an instance of an existing interface (let's call that the inner instance) with a class that implements that same interface and delegates their function calls to the same functions on their inner instance.
These patterns are very useful for adding or changing functionality of an instance without breaking encapsulation. It can also change or extend functionalities of final
functions and classes. And because they usually serve one purpose they can easily be tested.
Example
interface SubscriberInterface {
public function subscribe(string $email): void;
}
class SubscriberDecorator implements SubscriberInterface {
private SubscriberInterface $inner_subscriber;
public function subscribe(string $email): void {
$this->inner_subscriber->subscribe($email);
}
}
In this example you can see that our SubscriberDecorator
implements the SubscriberInterface
and it also requires some instance of the SubscriberInterface
. After that it delegates the subcribe()
function to the same function on that instance.
Differences between Decorators and Proxies
When it comes to naming a class a Decorator or a Proxy you have to look at its intent. What is the class actually doing with the instance it is wrapping?
Required vs. Optional dependency
You might have noticed I didn't include a __construct()
method in the previous example. This was intentional, because this is where the first difference can be apparent.
A Decorator requires an instance of the interface it is wrapping, while a Proxy does not require such an instance. A Proxy can receive an instance, but is also allowed to create this instance itself. So you can create a new
Proxy on its own, while a Decorator needs another instance as dependency.
// Decorator
public function __construct(public SubscriberInterface $inner_subscriber){}
// Proxy
public function __construct(?SubscriberInterface $inner_subscriber = null){
$this->inner_subscriber = $inner_subscriber ?? new InnerSubscriber();
}
Additive vs. Restrictive
Decorators are additive; meaning they only add new functionality by wrapping the function call and returning the original value. It can however do anything before or after that call. You can for example log every value when a function is called or dispatch an event. Just make sure to return the original value.
Proxies are restrictive; meaning they can change the behavior of a function or even restrict calling a specific function by throwing an exception.
Tip: Both Decorators and Proxies are allowed to add any extra functions or parameters. It can therefore be wise to implement some magic
__isset()
,__get()
and__call()
methods on the Decorator or Proxy to pass these calls along to the inner instance as well.
General purpose vs. Specific purpose
Decorators serve a general purpose. It will add some functionality regardless of the instance it is wrapping. This means that multiple decorators should be able to be applied on top of one another in any random order and still produce the same result and added functionality.
Proxies serve a more specific purpose. It will mostly be used to change or append functionality to a specific instance of the interface. Proxies also aren't commonly stacked on top of one another as a single proxy is usually enough.
Tips for Decorators and Proxies
Here are a few tips you might consider when working with Decorators and Proxies.
Make a base abstraction
If you create multiple Decorators or Proxies of the same interface it can be beneficial to create an abstract class
of the interface or a trait
that satisfies the interface, where every function is already deferred to the function on the inner instance. If you are a package creator, you might even consider providing this implementation inside the package. This way a Decorator or Proxy can extend
or use
this implementation and only (re)declare the functions it needs.
Single responsibility Decorators
It might be tempting to slap on multiple features onto a Decorator, but the beauty of them is that they can be added or removed without changing the underlying code. So try to make tiny Decorators that focus on one thing and apply these on top of each other. Again, this simpleness makes them easier to test as well.
Examples
You can find a couple of nice examples of Decorators and Proxies in Symfony.
Their developer toolbar shows a lot of information regarding events and cache, for example. They log this information by decorating the current EventDispatcher with a TraceableEventDispatcher and the current cache adapter with a TraceableAdapter within the dev
environment.
An example of a Proxy can be found in the DeflateMarshaller of the symfony/cache
package. This Marshaller is restrictive due to its dependency on gzinflate()
& gzdeflate()
and its changes to the output of the inner instance.
Thanks for reading
I hope you enjoyed reading this article! If so, please leave a ❤️ or a 🦄 and consider subscribing! I write posts on PHP almost every week. You can also follow me on twitter for more content and the occasional tip.
Top comments (0)