While the name "segregated interface" may sound like a difficult to grasp concept, and while it is closely related to the most complex facets of object-oriented programming, the truth is that a segregated interface is nothing but a regular interface, which defines a contract that provides its implementers with the exact functionality that they require to perform a set of specific tasks.
These elements play a great role in the definition of classes that have a narrowed, well-defined range of responsibilities, even when using a weakly-typed language such as PHP. Add to this the benefits of dependency injection, and you’ll start seeing segregated interfaces as a blessing, rather than a waste of time (and code).
In previous parts of this series I developed a couple of basic – yet functional - libraries which made use of some segregated interfaces to easily switch over different implementations at runtime (also known as Polymorphism). If you missed those parts or need a refresher, you can find them here:
The usage of segregated interfaces isn’t limited to creating a few standalone libraries; you can also use them to develop a model class, which perform typical CRUD operations in a MySQL table containing data, as I will be demonstrating in this article.
Before you move on and begin reading, a word of warning is in order here: to keep the whole example short and easy to follow, the model will be closely tied to the underlying storage (a MySQL database). In fact, I’m not a big fan of this approach, so if you want to create a clean domain model, the example can be easily amended by delegating the CRUD methods to a simple data mapper.
Caching model data: a quick look at the previous cache back-ends
Since I want to provide this sample user model with the ability to cache data that has been previously collected through its finders, I’m going to use a slightly modified version of the cache back-ends developed in the preceding tutorial.
As you may recall, these back-ends implemented a segregated interface called “CacheableInterface”, whose modified definition now looks like this:
(CacheableInterface.php)
<?php
interface CacheableInterface { public function set($key, $data);
public function get($key);
public function delete($key);
public function exists($key);
public function clear(); }
As you can see, the only difference between the old version of the above interface and this enhanced one is that it now declares a new “clear()” method, which will be implemented by the back-ends for clearing their respective caches.
Having said that, here’re the first class that implements the interface in question:
(ApcCache.php)
<?php
class ApcCache implements CacheableInterface { /** * Save data to the cache */ public function set($key, $data) { if (!apc_store(strtolower($key), $data)) { throw new ApcCacheException('Error saving data with the key ' . $key . ' to the APC cache.'); } return $this; }
/** * Get the specified data from the cache */ public function get($key) { if ($this->exists($key)) { if (!$data = apc_fetch(strtolower($key))) { throw new ApcCacheException('Error fetching data with the key ' . $key . ' from the APC cache.'); } return $data; } return null; }
/** * Delete the specified data from the cache */ public function delete($key) { if ($this->exists($key)) { if (!apc_delete(strtolower($key))) { throw new ApcCacheException('Error deleting data with the key ' . $key . ' from the APC cache.'); } return true; } return false; }
/** * Check if the specified cache key exists */ public function exists($key) { return (boolean) apc_exists(strtolower($key)); }
/** * Clear the cache */ public function clear($cacheType = 'user') { return apc_clear_cache($cacheType); } }
(ApcCacheException.php)
<?php
class ApcCacheException extends Exception{}
Considering that the “ApcCache” class was already discussed in depth, I’m not going to waste your valuable time explaining what it does. So, move on and look at second cache back-end, which caches data using the file system. Here it is:
(FileCache.php)
<?php
class FileCache implements CacheableInterface { protected $_cacheDir = 'cache';
/** * Constructor */ public function __construct($cacheDir = '') { if ($cacheDir !== '') { if (!is_dir($cacheDir)) { throw new FileCacheException('The specified cache directory is invalid.'); } $this->_cacheDir = $cacheDir; } }
/** * Save data to the specified cache file */ public function set($key, $data) { $cacheFile = $this->getCacheFile($key); if (!file_put_contents($cacheFile, serialize($data))) { throw new FileCacheException('Error saving data with the key ' . $key . ' to the cache file.'); } return $this; }
/** * Get data from the specified cache file */ public function get($key) { if ($this->exists($key)) { $cacheFile = $this->getCacheFile($key); if (!$data = unserialize(file_get_contents($cacheFile))) { throw new FileCacheException('Error reading data with the key ' . $key . ' from the cache file.'); } return $data; } return null; }
/** * Delete the specified cache file */ public function delete($key) { if ($this->exists($key)) { $cacheFile = $this->getCacheFile($key); if (!unlink($cacheFile)) { throw new FileCacheException('Error deleting the file cache with key ' . $key); } return true; } return false; }
/** * Check if the specified cache file exists */ public function exists($key) { $cacheFile = $this->getCacheFile($key); return file_exists($cacheFile); }
/** * Remove all the cache files */ public function clear() { $cacheFiles = $this->_cacheDir . DIRECTORY_SEPARATOR . '*.cache'; array_map('unlink', glob($cacheFiles)); }
/** * Get the specified cache file */ protected function getCacheFile($key) { return $this->_cacheDir . DIRECTORY_SEPARATOR . strtolower($key) . '.cache'; } }
(FileCacheException.php)
<?php
class FileCacheException extends Exception{}
Again, the inner workings of the “FileCache” class should be pretty familiar to you, as this cache backend was also covered in detail in the previous installment of the series. However, it’s important to note that at this point I managed to create an extendable caching system, which will be used later on by the aforementioned user model.
But I’m getting ahead of myself, since it’s necessary to provide the model with the ability to access the underlying storage mechanism in the first place. Not surprisingly, this will be achieved via a simple MySQL abstraction class, which will make use of a segregated interface as well.