loading...

Refactor, a solution with class abstraction

sebastianr1982 profile image Sebastian Rapetti ・2 min read

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.

Posted on by:

sebastianr1982 profile

Sebastian Rapetti

@sebastianr1982

Hello! I am a hobbyist programmer with years of experience with PHP and the Web apps. PHP fanatic.

Discussion

markdown guide