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 that are not on the interface. 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 their inner instance as well. This way you can still call those methods and parameters even if you add multiple decorators on top.

public function __call($name, $arguments)
{
return $this->inner_subscriber->{$name}(...$arguments);
}
 
public function __get($name)
{
return $this->inner_subscriber->{$name};
}
 
public function __isset($name)
{
return isset($this->inner_subscriber->{$name});
}

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.

interface SubscriberInterface
{
public function subscribe(string $email): bool;
 
public function unsubscribe(string $email): bool;
}
 
trait SubscriberTrait { ... }
{
private SubscriberInterface $inner_subscriber;
 
public function subscribe(string $email): bool
{
return $this->inner_subscriber->subscribe($email);
}
 
public function unsubscribe(string $email): bool
{
return $this->inner_subscriber->unsubscribe($email);
}
 
public function __call($name, $arguments)
{
return $this->inner_subscriber->{$name}(...$arguments);
}
 
public function __get($name)
{
return $this->inner_subscriber->{$name};
}
 
public function __isset($name)
{
return isset($this->inner_subscriber->{$name});
}
}
 
// You can now extend this class, or implement the interface and trait.
abstract class SubscriberDecorator implements SubscriberInterface
{
use SubscriberTrait;
 
public function __construct(SubscriberInterface $inner_subscriber)
{
$this->inner_subscriber = $inner_subscriber;
}
}

Single responsibility Decorators

It might be tempting to add 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 it's changes to the output of the inner instance.

Patterns for the Rest of Us
This blog post is part of the Patterns for the Rest of Us series. Check it out to learn more about other patterns.

Thanks for reading

I hope you enjoyed reading this article! If so, please leave a 👍 reaction or a 💬 comment and consider subscribing to my newsletter! I write posts on PHP almost every week. You can also follow me on twitter for more content and the occasional tip.