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)