Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Architecture Design Help - OOP Solid Principle

I'm trying to make open source Cache library. The purpose of library is to provide the way of storing the variable (can be object, can be be array, can be anything) to files and then retrieved it back on call. (usually those variable value is result of massive database queries and calculations).

The basic aim of the project is to practice the Object Oriented Design Principle Called Solid.

If any one can indicate where i'm violating solid principle and how to fix it

I totally understand stackoverflow is not a code writing service but hey i'm making this library open source, so it would benefit our community.

So here is my file structure.

enter image description here

I'm new to UML so please ignore if any errors found

enter image description here

here is the classes implementation.

Cache

namespace library\pingle\cache;

use library\pingle\cache\config\CacheConfigurator;
use library\pingle\cache\file\FileHandler;

/**
 * @property CacheReader $cache_reader
 * @property CacheWriter $cache_write
 */
Class Cache {

    private $config;
    private $file_hander;
    private $cache_reader;
    private $cache_write;
    private $cache_directory;
    private $cache_kept_days;
    private $cache_file_prams;
    private $function_name;
    private $file_path;

    function __construct(CacheConfigurator $config) {
        $this->file_hander = new FileHandler();
        $this->config = $config;
        list($this->cache_directory, $this->function_name, $this->cache_kept_days, $this->cache_file_prams) = $this->config->getConfig();
        $this->file_path = $this->generateFileName($this->cache_file_prams);
    }

    public function read() {
        if (is_null($this->cache_reader)) {
            $this->cache_reader = new CacheReader($this->file_hander);
        }
        return $this->cache_reader->readCache($this->file_path);
    }

    public function write($data) {
        if (is_null($this->cache_write)) {
            $this->cache_write = new CacheWriter($this->file_hander);
        }
        if (!$this->file_hander->checkDirectory($this->cache_directory . "/" . $this->function_name)) {
            $this->file_hander->createDirectory($this->cache_directory . "/" . $this->function_name);
        }
        $this->cache_write->writeCache($this->file_path, $data);
    }

    public function check() {
        if ($this->file_hander->checkFileExits($this->file_path)) {
            if (time() - filemtime($this->file_path) >= 60 * 60 * 24 * $this->cache_kept_days) {
                return false;
            }
            return true;
        } else {
            return false;
        }
    }

    private function generateFileName(Array $nameprams) {
        $this->file_name = "";
        $file = "CC";
        foreach ($nameprams as $key => $value) {
            $file .= "-$key|$value-";
        }
        $file .= ".bak";
        return $this->cache_directory . "/" . $this->function_name . "/" . $file;
    }

}

AbstractCache

<?php

namespace library\pingle\cache;

use library\pingle\cache\file\FileHandler;

abstract Class AbstractCache {

    protected $file_handler;

    public function __construct(FileHandler $file_handler) {
        $this->file_handler = $file_handler;
    }

    protected function checkDirectory($path) {
        //check directory exists
        $dircheck = $this->file_handler->checkDirectory(dirname($path));
        if ($dircheck) {
            //check directory permission
            if ($this->file_handler->checkPermission(dirname($path))) {
                return true;
            } else {
                throw new \Exception("Directory ($path) Permission Error.");
            }
        } else {
            throw new \Exception("Directory ($path) not found.");
        }
    }

}

CacheReader

<?php

namespace library\pingle\cache;

use library\pingle\cache\file\FileHandler;

/**
 * @property FileHandler $file_handler
 */
Class CacheReader extends AbstractCache {

    public function __construct(FileHandler $file_handler) {
        parent::__construct($file_handler);
    }

    public function readCache($path) {
        if ($this->checkDirectory($path)) {
            //delete the file if it exits
            if ($this->file_handler->checkFileExits($path)) {
                return $this->file_handler->readFile($path);
            } else {
                throw new \Exception("File ($path) not found");
            }
        }
    }

}

CacheWriter

<?php

namespace library\pingle\cache;

use library\pingle\cache\file\FileHandler;

/**
 * @property FileHandler $file_handler
 */
Class CacheWriter extends AbstractCache {

    public function __construct(FileHandler $file_handler) {
        parent::__construct($file_handler);
    }

    function writeCache($path, $data) {
        if ($this->checkDirectory($path)) {
            //delete the file if it exits
            if ($this->file_handler->checkFileExits($path)) {
                $this->file_handler->deleteFile($path);
            }
            //write cache
            $this->file_handler->writeFile($path, $data);
        }
    }

}

FileHandler

<?php

namespace library\pingle\cache\file;

Class FileHandler {

    public function writeFile($path, $data) {
        $content = serialize($data);
        file_put_contents($path, $content);
    }

    public function createDirectory($path) {
        mkdir($path);
    }

    public function deleteFile($path) {
        unlink($path);
    }

    public function checkDirectory($path) {
        if (file_exists($path)) {
            return true;
        } else {
            return false;
        }
    }

    public function checkPermission($path) {
        if (is_writable($path)) {
            return true;
        } else {
            return false;
        }
    }

    public function checkFileExits($path) {
        if (is_file($path)) {
            return true;
        }
        return false;
    }

    public function readFile($path) {
        return unserialize(file_get_contents($path));
    }

    public function checkFileCreated($path, $format = "Y-m-d") {
        return date($format, filemtime($path));
    }

}

CacheConfigurator

<?php
namespace library\pingle\cache\config;
/**
 * @property PramsFormatter $prams_formatter
 */
class CacheConfigurator {
    private $prams_formatter;
    private $cache_directory;
    private $cache_kept_days;
    private $cache_file_prams;
    private $function_name;
    function __construct($file_prams) {
        $this->cache_file_prams = $file_prams;
        $this->cache_directory = ""; //Def Path
    }
    public function setCacheDirectory($cache_directory) {
        $this->cache_directory = $cache_directory;
        return $this;
    }
    public function setFunction($function) {
        $this->function_name = $function;
        return $this;
    }
    public function setCacheKeptDays($cache_kept_days) {
        $this->cache_kept_days = $cache_kept_days;
        return $this;
    }
    public function getConfig() {
        $this->prams_formatter = new PramsFormatter($this->cache_file_prams);
        $this->cache_file_prams = $this->prams_formatter->getFormattedPrams();
        $this->function_name = $this->prams_formatter->cleanValue($this->function_name);
        return array($this->cache_directory, $this->function_name, $this->cache_kept_days, $this->cache_file_prams);
    }
}

PramsFormatter

<?php

namespace library\pingle\cache\config;

class PramsFormatter {

    private $cache_file_prams;

    public function __construct(Array $prams) {
        $this->cache_file_prams = $prams;
        $this->formatPrams();
    }

    public function formatPrams() {
        if (is_array($this->cache_file_prams)) {
            foreach ($this->cache_file_prams as $k => $value) {
                $this->cache_file_prams[$k] = $this->cleanValue($value);
            }
        }
    }

    public function cleanValue($value) {
        if (is_array($value)) {
            throw new \Exception("Array as paramter value is not accepted");
        } else {
            return str_replace(array(" ", "   ", ".", "/", "\\"), "-", $value);
        }
    }

    public function getFormattedPrams() {
        return $this->cache_file_prams;
    }

}

Usage

$cache_config = new CacheConfigurator(array('carrier_id' => $invoicedata['carrier_id'], 'month' => $month, 'year' => $year));
$cache_config->setFunction('Inter-department Calls');
$cache_config->setCacheKeptDays(30);
$cache_config->setCacheDirectory("bin/cache");
$cache = new Cache($cache_config);
if ($cache->check()) {
    $node = $cache->read();
} else {
    //calculate node
    $cache->write($node);
}

Git Repository with Improve Design

https://github.com/FaizRasool/EPC

like image 877
Faiz Rasool Avatar asked Sep 25 '22 12:09

Faiz Rasool


1 Answers

Very good question, but an entire book could probably be written on this, which makes it quite hard to answer.

I'd start with this simple question: what better describes caching in the choices below?

  1. Caching is a mechanism that allows to store the result of a function to a file for a number of days in order to provide fast access to it.

  2. Caching is a mechanism that allows to retain the result of an operation for as long as the associated retention policy is satisfied in order to provide fast access to it.

None of the definitions are perfect and that's not the point, but what I wanted to emphasis is that #1 explains caching with very specific infrastructure details while #2 defines the mechanism in a more abstract way.

Hopefully, you will now have realized one of the biggest flaws of your design IMO. The various abstractions are wrong:

  1. The abstraction of the storage mechanism is dependent of a specific infrastructure detail: the entire API revolves around files. What if I wanted an in-memory cache?

  2. The data retention policy algorithm is very specific: data will be retained only for a specific number of days. What if I wanted to express the cache in minutes where the counter resets evertime the data is accessed?

One advice I'd give you is to always challenge your abstractions and make sure that they are not too specific so that your code can be extensible and reusable, but not too wide either. Paying attention to the language of the problem domain greatly helps with that.

There's obviously much more than this that could be said and sometimes being technology-dependent is the right choice, but I think my answer will help...

like image 180
plalx Avatar answered Sep 28 '22 04:09

plalx