One of the many advantages of object-oriented programing is that it allows us to write more descriptive code — code that by design has to be used a specific way. This helps move us from conventions to programmed rules in our code. That’s good, but it can complicate things. Arrays and standard class objects can hold any data, which is super-flexible but also leads to poorly-formed systems.
The repository pattern gives us a way to have objects that can contain arbitrary data, but also enforce rules. I’ve written about other design patterns before. Like any other software design principle, following a pattern is, in of itself, of no value. But learning these patterns and other principles gives you new tools to call on when they are the right solution to the problem you’re currently addressing.
There are several uses for a repository pattern. It can be used to avoid having to use the singleton pattern for classes that you need to reuse the same instance of in most, but not all situations throughout your project. For example, you may have a settings class that holds the settings for your plugin. You’d probably want to use the same instance of that class most of the time but have flexibility.
One way I use it is to have a lazy-loader for commonly used objects. By lazy-loading I mean creating a system to the object is only loaded when it is needed and only once.
For example, in Caldera Forms, we have a few features that sync data between fields. The configuration is generated with a PHP class, and that class is used in a few contexts — to generate the JavaScript configuration and to generate the field’s HTML. I don’t want to create a different object of that class for both uses, especially since the form might be outputted multiple times on the page.
Instead, I implemented a repository for these objects. Instead of instantiating a new and identical class 2 or 4 or 6 times per field, I use the factory, which creates it once and keeps a copy for next time I need it.
In this article, I’ll look at a few examples of the repository pattern and practical example of how to put them to use.
A Simple Repository
As I said in my introduction a repository is a collection of other items. Here is the most basic example where we have a class that has an array that we can put anything into and a method to get items out of:
<?php class ExampleOne { /** * @var array */ protected $items; /** * ExampleOne constructor. * * @param array $items Optional. Initial collection items */ public function __construct( array $items = []) { $this->items = $items; } /** * Get a value from collection * * @param string $key * * @return mixed */ public function get( string $key ) { if( isset( $this->items[ $key ] ) ){ return $this->items[ $key ]; } } /** * Set a value in collection * * @param string $key * @param mixed $value * * @return $this */ public function set( string $key, $value ) { $this->items[ $key ] = $value; return $this; } }
This isn’t very useful right now. We basically just reverse-engineered an array, using an array wrapped in a class. In fact, this is less efficient than just using an array, but it is something we can build on.
Often times we want to limit what is allowed in our repository. So we can create one array to store items and one array to “whitelist” items. In this example, I call my list “properties”. We can only set a value for the collection if it is on that list. Here is an example:
<?php class ExampleTwo { /** * Allowed properties * * @var array */ protected $properties = [ 'settings', 'client', ]; /** * Stores values of allowed properties * * @var */ protected $attributes; /** * Get a value from collection * * @param string $key * * @return mixed */ public function get( string $key ) { if( isset( $this->attributes[ $key ] ) ){ return $this->attributes[ $key ]; } } /** * Set a value in collection * * @param string $key * @param mixed $value * * @return $this */ public function set( string $key, $value ) { if ( in_array( $key, $this->properties ) ) { $this->attributes[ $key ] = $value; } return $this; } }
We can get more complicated from there, for example by introducing type checking.
Repository Classes
I typically implement the repository pattern by for collections of items. For this it’s really helpful to have a base class that has all the methods of a good repository, which are these methods:
- get() – Get an item from the collection
- set() – Add an item to the collection
- has() – Check if an item is in the collection
- allowed() – Check if an item is allowed to be added to the collection
This list and how I design repositories are heavily influenced by Laravel’s Collection class and Symfony’s ParameterBag class.
Here is an example repository, as an abstract class that implements all of those methods:
<?php use calderawp\repository\Repository; class Container { /** * Main repositories of repositories * * @var Repository */ protected $repository; /** * Get main repository used to store repositories * * @return Repository */ public function repository( ) { if( ! $this->repository ){ $this->repository = new class extends Repository{}; } return $this->repository; } }
Notice that when called for, the methods of this repository are used. For example, I used the has() method in the get() method to protect against trying to get an unset index of the attributes property instead of reusing the same isset() check. The reason this is so important is that the logic of has() might change in an extending class.
Adding A Factory
So far, objects are created outside of the repository and pushed into it. I like integrating an object factory into my repository so that it can act as a lazy loader.
Here is an example of that type of class:
<?php abstract class FactoryRepository { /** * Allowed properties * * override this in class using trait * * @var array */ protected $properties; /** * Stores values of allowed properties * * @var array */ protected $attributes; /** * Create items when not found * * @return mixed */ abstract protected function factory( $name ); /** * Check if has a stored value * * @param string $name * * @return bool */ public function has( string $name ) : bool { return isset( $this->attributes[ $name ] ); } /** * Add value to repository * * @param string $name * @param $value * * @return $this */ public function set( string $name, $value ) { if( $this->allowed( $name ) ){ $this->attributes[ $name ] = $value; } return $this; } /** * Check if is an allowed type of value to store * * @param string|int $name * * @return bool */ public function allowed( $name ) : bool { return in_array( $name, $this->getAllowedProperties() ); } /** * Get value is possible/allowed * * @param string $name * @param mixed|null $default * * @return mixed */ public function get( string $name, $default = null ) { if( $this->has( $name ) ){ return $this->attributes[ $name ]; }elseif ( $this->allowed( $name ) ){ $this->set( $name, $this->factory( $name ) ); return $this->attributes[ $name ]; } return $default; } /** * Get the allowed properites * * @return array */ public function getAllowedProperties() : array { return $this->properties; } }
This adds an abstract method called “factory” that is called when an item is a request from the get() method that isn’t set.
Here is an example implementation that would return objects of the WP_Post class:
<?php class PostRepo extends SimpleFactoryRepository{ protected function factory( $name ){ if( is_numeric( $name )){ return get_post( $name ); } $posts = get_posts( ['post_slug' => $name ] ); return $posts[0]; } }
Now we can use the post slug or post ID with the get() method and if the post hasn’t been queried for already, it’s placed into the collection, and returned. The second time you ask for it, the object is already there.
This pattern is can really increase the efficiency — both for PHP and for the developer. This is especially true if you place the repository in your object cache.
Creating A Container
One anti-pattern we see a lot in WordPress is a plugin will have a “God Singleton”. This class, which implements the singleton pattern will get loaded on every class and instantiate an instance of all of the other classes a plugin needs into properties of that class. While this avoids using the singleton on every one of those classes, it’s not a great design.
I’ve gone into this problem and various solutions before. One solution is to add a getter method for each of the classes that lazy-loads it. Now you have one “container” class that is a singleton and can create other class instances as needed. This is an implementation of the repository pattern.
Here is an example for a plugin that needs a commonly available “Settings” class and “ApiClient” class:
<?php class Container { protected static $instance; /** @var ApiClient */ protected $apiClient; /** @var Settings */ protected $settings; public function getInstance() { if( ! static::$instance ){ static::$instance = new static(); } return static::$instance; } public function getAPiClient() : ApiClient { if( ! $this->apiClient ){ $this->apiClient = new ApiClient; } return $this->apiClient; } public function getSettings() : Settings { if( ! $this->settings ){ $this->settings = new settings; } return $this->settings; } }
Now we have one singleton that gives us access to the two other classes we need, but each one is lazy-loaded.
We can use it like this:
<?php $apiClient = Container::getInstance()->getAPiClient();
It’s hard to say for sure when having to add one property and one method to add a class to this container gets to be too much. I’ve been using this pattern recently and I find that I often switch to using an arbitrary repository like I showed above inside of my container.
Parting Thoughts
One thing to keep in mind with the repository pattern is that its job is to make your life easier and help your code scale. Don’t get to worked up in these details.
Also, I have a package on Github that has several of these uses of the repository pattern implemented.
No Comments