DEV Community

Sebastian Rapetti
Sebastian Rapetti

Posted on

6

Refactor, a solution with class abstraction

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.");
    }
}

Enter fullscreen mode Exit fullscreen mode

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.");
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay