Working on my little PHP framework I encountered a situation when the code suffered of duplication. Situation apparently, cannot be resolved because code concern two different part of the framework.
The Mess
I have two classes, one for get a connection to persistent storage and the other for get a connection to cache. Classes return different objects, but do action at the same way.
Code is not optimized but that's another story. I wish focus on duplication and refactor.
Storage Factory:
<?php /** * Storage Factory. */ class StorageFactory { private $driver; private $supportedDriver = [ 'pdo' => PdoStorage::class, 'mysqli' => MysqliStorage::class, 'mongodb' => MongoDbStorage::class, ]; private $options; public function __construct(string $driver, array $options) { $this->driver = $driver; $this->options = $options; } public function getConnection() : StorageInterface { $driver = $this->driver; $options = $this->options; if (isset($this->supportedDriver[$driver])) { $storageClass = $this->supportedDriver[$driver]; return new $storageClass($options); } throw new InvalidArgumentException("[$driver] not supported."); } }
And Cache Factory:
<?php /** * Cache Factory. */ class CacheFactory { private $driver; private $supportedDriver = [ 'disk' => DiskCache::class, 'memcached' => MemcachedCache::class, ]; private $options; public function __construct(string $driver, array $options) { $this->driver = $driver; $this->options = $options; } public function get() : CacheInterface { $driver = $this->driver; $options = $this->options; if (isset($this->supportedDriver[$driver])) { $cache = $this->supportedDriver[$driver]; return new $cache($options); } throw new InvalidArgumentException("[$driver] not supported."); } }
Previous code is an obvious example of duplication :(
Questions begin
How resolve this problem?
Cache is a type of storage, a very fast storage. I can merge all inside one factory class but *Storage
classes and *Cache
classes in my framework implement different interfaces.
Furthermore, I wish use PHP return types declaration I cannot have a method that returns two different types of objects.
I analize code...
Where are the differences in the classes?
I have to isolate the differences in the code:
<?php // Storage class private $supportedDriver = [ 'pdo' => PdoStorage::class, 'mysqli' => MysqliStorage::class, 'mongodb' => MongoDbStorage::class, ]; // Cache class private $supportedDriver = [ 'disk' => DiskCache::class, 'memcached' => MemcachedCache::class, ]; // Storage class public function getConnection() : StorageInterface { //code } // Cache class public function get() : CacheInterface { //code }
And now?
Now I know the differences, must to split them in two concrete implementation and move duplicate code into an abstract class.
Abstract class:
<?php /** * Abstract Storage Factory. */ abstract class AbstractStorageFactory { protected $driver; protected $supportedDriver = []; protected $options; public function __construct(string $driver, array $options) { $this->driver = $driver; $this->options = $options; } protected function returnStorageObject() { $driver = $this->driver; $options = $this->options; if (isset($this->supportedDriver[$driver])) { $class = $this->supportedDriver[$driver]; return new $class($options); } throw new InvalidArgumentException("[$driver] not supported."); } /** * Get storage object. * * @return object */ abstract public function get(); }
Concrete factories:
<?php /** * Storage Factory. */ class StorageFactory extends AbstractStorageFactory { protected $supportedDriver = [ 'pdo' => PdoStorage::class, 'mysqli' => MysqliStorage::class, 'mongodb' => MongoDbStorage::class, ]; public function get() : StorageInterface { return $this->returnStorageObject(); } } /** * Cache Factory. */ class CacheFactory extends AbstractStorageFactory { protected $supportedDriver = [ 'disk' => DiskCache::class, 'memcached' => MemcachedCache::class, ]; public function get() : CacheInterface { return $this->returnStorageObject(); } }
Finally
Code duplication is disappeared and I used the PHP return types.
I wrote this little article not for the code, but for share the reasoning did for reach final result.
Top comments (0)