As we progress in learning object-oriented programming (OOP) as PHP developers, the uses of certain established patterns become more important, such as one that I’v been using lately, the decorator pattern.
On a textbook level, the decorator pattern lets us change a class when it runs, while extending a class allows us to change it when it is designed. That difference seems trivial, but it has important consequences on the readability and scalability of your applications.
The decorator pattern is a different way of adding functionality to a class. Instead of extending it using inheritance — class A extends class B — we create a new class that is “wrapped” around the other class, which is injected into it as a dependency.
A Quick Example
Here is a quick example of a class for posts that represent events, which decorated WP_Post:
<?php class Event { /** * @var WP_Post */ protected $post; /** * Event constructor. * * @param WP_Post $post */ public function __construct( WP_Post $post ){ $this->post = $post; } /** * Get event date * * @return string */ public function get_event_date(){ return get_post_meta( $this->post->ID, 'event_date' ); } }
The constructor of this class takes the WP_Post object, and then we added a method for getting the event’s date from a meta field. Now we have a simple, declarative object for representing event dates.
The decorator pattern makes sense here, instead of extending the WP_Post class. One reason is subjective — I think it’s better to leave WP_Post’s responsibility for querying and caching separately from this class’ responsibilities — getting the event date.
The other reason is practical. WP_Post is declared with the final keyword. If a class is declared with the final keyword, then it is impossible to subclass it. The decorator pattern provides a solution to this problem. If I wanted to make a class for specific types of WordPress posts, I could not extend WP_Post, since it is a final class. But, I could decorate it.
interface subscription { public function get_length() : int; public function get_plan() : string; } class subscription_edd implements subscription { /** * @var EDD_Subscription */ protected $subscription; public function __construct( EDD_Subscription $subscription ) { $this->subscription = $subscription; } public function get_length() : int { //@TODO } public function get_plan() : string { //@TODO } } class subscription_woo { /** * @var Woo_Subscripton */ protected $subscription; public function __construct( Woo_Subscription $subscription ) { $this->subscription = $subscription } public function get_length() : int { //@TODO } public function get_plan() : string { //@TODO } }
Comparing Decorating vs Subclassing
Because it is the only option is not the only reason we use the decorator pattern instead of subclassing. I often times, when writing site-specific code to integrate with other plugins, will extend their classes, instead of using the decorator pattern which would be more appropriate.
For example, here is a class currently in use on CalderaForms.com that is used to record and recall a custom checkout field we have that asks customers if they are buying a plugin for themselves or for a client:
<?php namespace cfdc\edd; /** * Class payment * @package cfdc\edd */ class payment extends \EDD_Payment { /** * The meta key where we store who payement was for client or me */ const WHO_FOR_META_KEY = '_cfdc_who_for'; /** * Record who payment was for as meta * * @param string $who Who was for. Valid values: me|client */ public function set_who_for( string $who = 'client' ) { if ( ! in_array( $who, [ 'me', 'client' ] ) ) { $who = 'client'; } $this->update_meta( self::WHO_FOR_META_KEY, $who ); $this->add_note( "Was purchased for $who" ); } /** * Get who this was for me or client * * @return string */ public function get_who_for() : string { $who = $this->get_meta( self::WHO_FOR_META_KEY ); if ( empty( $who ) || is_string( $who ) ) { $who = 'client'; } return $who; } }
This class extends EDD_Payment with methods for reading and writing this data. It works, but I don’t love this code.
Here, I have rewritten it to follow the decorator pattern. This illustrates the difference:
<?php namespace cfdc\edd; class payment { /** * @var \EDD_Payment */ public $payment; /** * payment constructor. * * @param \EDD_Payment $payment */ public function __construct( \EDD_Payment $payment ) { $this->payment = $payment; } /** * The meta key where we store who payement was for client or me */ const WHO_FOR_META_KEY = '_cfdc_who_for'; /** * Record who payment was for as meta * * @param string $who Who was for. Valid values: me|client */ public function set_who_for( string $who = 'client' ) { if ( ! in_array( $who, [ 'me', 'client' ] ) ) { $who = 'client'; } $this->payment->update_meta( self::WHO_FOR_META_KEY, $who ); $this->payment->add_note( "Was purchased for $who" ); } /** * Get who this was for me or client * * @return string */ public function get_who_for() : string { $who = $this->payment->get_meta( self::WHO_FOR_META_KEY ); if ( empty( $who ) || is_string( $who ) ) { $who = 'client'; } return $who; } }
Now, EDD_Payment is taken in as a dependency. This means that the payment class has one job. Abiding by the single responsibility principle is not something we do just to score “good coder points.” In this situation, we’ve gained a few tangible things.
It’s now easier to read as we don’t have to worry about understanding the parent class. Also, it would be easier to change what class this is decorating than to make it extend a different class.
At this scale that is all pretty trivial. But as the complexity of the custom functionality grows, the class really does represent something very different from what it is based on. For example, in Caldera Forms Pro, which is a SaaS product, partially built in WordPress that uses Easy Digital Downloads for its eCommerce.
In the code for this, I have a class for subscriptions that decorates the EDD_Subscription class. I made this design decision for a few reasons. The first is that my subscription object does very different things than the EDD_Subscription class does. Mine doesn’t create subscriptions or databases. It gets basic customer information and metadata specific to my application.
While I like my decision to rely on Easy Digital Downloads, that might change or I may add other ways to create subscriptions. If need I can create a different class, with the same public methods, that decorates a different object. They will be interchangeable to the outside world, but totally different inside of the class.
Those key public methods should be defined in an interface that both implement. Here is an example that illustrates how we can make the internal logic invisible. I have an interface for subscriptions and two classes that implement it. One decorates Woo_Subscription and one decorates EDD_Subscription
interface subscription { public function get_length() : int; public function get_plan() : string; } class subscription_edd implements subscription { /** * @var EDD_Subscription */ protected $subscription; public function __construct( EDD_Subscription $subscription ) { $this->subscription = $subscription; } public function get_length() : int { //@TODO } public function get_plan() : string { //@TODO } } class subscription_woo { /** * @var Woo_Subscripton */ protected $subscription; public function __construct( Woo_Subscription $subscription ) { $this->subscription = $subscription } public function get_length() : int { //@TODO } public function get_plan() : string { //@TODO } }
What is great about this is that the rest of the program doesn’t care about the differences between how WooCommerce and Easy Digital Downloads handle subscriptions. That logic becomes black-boxed. This way of implementing the decorator pattern along with interfaces makes projects that integrate with multiple plugins or APIs of the same type — eCommerce plugins, payment gateways, etc. — a lot easier to manage.
A Weakness In The Decorator Pattern
When we extend a class, the subclass has access to the public and protected methods and properties of the parent class. We can do this because the subclass is the same object. When we decorate a class, the class we are decorating is a different object. Therefore protected methods of the class being decorated.
Sometimes this is by design. By putting the final keyword on a class declaration we are sending a strong signal to other developers who might be using it “don’t mess with this.” Sometimes I see final used to indicate that a class’ internal structure might change. Not allowing access to its protected properties and methods prevents non-backwards compatible subclassing.
Bonus JavaScript Example
The decorator pattern is not unique to PHP, it is a principle of software design in general. So, before I wrap up, I want to provide an example of the decorator pattern in JavaScript.
If we are working with the WordPress REST API, we might have multiple ways to get data for a post from the server. But it’s helpful to have one declarative object to get the fields we need from those responses.
Here is an example that takes the structure of a WordPress post we expect to have as a plain object and creates a structured object with functions to get the title or content of the post:
function Post( apiResponse ){ function getPostTitle() { return apiResponse.title.rendered; } function getPostContent(){ return apiResponse.content.rendered; } }
Conclusion
Like learning about any software pattern, remember the point of learning about the decorator pattern is that it gives us a tool that might be useful, in some situations. There are many practical examples in WordPress development where the decorator pattern is useful.
Many core classes, not just WP_Post are declared final, so subclassing is the only option. But, often times subclassing is an option, and I’m not saying it is bad. What I am suggesting is that it has drawbacks, and by learning an alternative we can evaluate each case on what is better — subclassing vs decorating — and decide based on what is best in that instance.
1 Comment