4
\$\begingroup\$

I wrote an abstract Configuration class, then an ConnectionConfiguration class and then an DatabaseConnection class.

  • ConnectionConfiguration extends Configuration
  • DatabaseConnection extends ConnectionConfiguration

My reason for doing this was to learn more about object-oriented design and better PHP programming the right way. Those small classes were originally made for setting up the well-known Database wrapper.

Here is my Configuration class. It basically allows you to put in some data and retrieve it via the other classes extending from it.

namespace Bas\DatabaseConnections\Configuration;

/**
 * A configuration
 *
 * @package Bas\DatabaseConnections\Configuration
 */
class Configuration
{
    /**
     * @var array $data The data for the {@link Configuration}
     */
    private $data;

    /**
     * Creates a {@link Configuration} with data
     *
     * @param array $data The data for the {@link Configuration}
     */
    public function __construct(array $data)
    {
        $this->data = $data;
    }

    /**
     * Returns data from the {@link Configuration}
     *
     * @param string $key A optional array key to look up data
     *
     * @return mixed The selected data out of the {@link Configuration}
     *
     * @throws ConfigurationException When the data is not present
     */
    public final function getData($key = "")
    {
        $verificationHandler = new ConfigurationVerificationHandler();
        if (!$verificationHandler->isPresent($key, $this->data)) {
            throw new ConfigurationException("$key does not exist");
        }
        return empty($key) ? $this->data : $this->data[$key];
    }

    /**
     * @param string$key
     * @param mixed $replacement
     * @return mixed
     */
    public final function getOptionalData($key, $replacement)
    {
        return array_key_exists($key, $this->data) ? $this->data[$key] : $this->data[$key] = $replacement;
    }
}

namespace Bas\DatabaseConnections\Configuration;


class ConfigurationVerificationHandler
{

    /**
     * @param $key
     * @param array $data
     * @return bool
     */
    public function isPresent($key, array $data)
    {
        return (empty($key) || array_key_exists($key, $data));
    }
}

For me, there is one problem with it: the getOptionalData function doesn't really fit into a standard method which you will find in a Configuration class. Also, is the getData function supposed to check if keys exist?

Next, onto my ConnectionConfiguration. This class was basically made for the general things that belong to a Connection, which is a "host" and a "port" number for me.

namespace Bas\DatabaseConnections\Configuration\Configurations;

use Bas\DatabaseConnections\Configuration\Configuration;
use Bas\DatabaseConnections\Configuration\ConfigurationException;

/**
 * ConnectionConfiguration
 *
 * @package Bas\DatabaseConnections\Configuration\Configurations
 */
class ConnectionConfiguration extends Configuration
{
    /**
     * @return mixed
     * @throws ConfigurationException
     */
    public final function getHost()
    {
        $host = $this->getData('host');
        if(!filter_var($host, FILTER_VALIDATE_IP)) {
            throw new ConfigurationException("The host has to be an IP-address");
        }
        return $host;
    }

    /**
     * @return mixed
     * @throws ConfigurationException
     */
    public function getPort()
    {
        $port = $this->getData('port');
        if (!is_float($port) || !is_int($port)) {
            throw new ConfigurationException("The port has to be a number");
        }
        return $port;
    }
}

Then there is the problem: should the array keys we're going to need to use this class already be known?

Next, onto my DatabaseConnectionConfiguration class, which simply be used in my code at this moment. I made this class for knowing which "configuration" you will need for a Database.

namespace Bas\DatabaseConnections\Configuration\Configurations\ConnectionConfigurations;

use Bas\DatabaseConnections\Configuration\ConfigurationException;
use Bas\DatabaseConnections\Configuration\Configurations\ConnectionConfiguration;

/**
 * Class DatabaseConnectionConfiguration
 *
 * @package Bas\DatabaseConnections\Configuration\Configurations\ConnectionConfigurations
 */
class DatabaseConnectionConfiguration extends ConnectionConfiguration
{
    /**
     * Returns the port for a database connection
     *
     * @return int The port the database connection will listen on
     *
     * @throws ConfigurationException When the port is not defined as numeric
     */
    public function getPort()
    {
        $port = $this->getOptionalData('port', 3306);
        if (!is_float($port) || !is_int($port)) {
            throw new ConfigurationException("The port has to be a number");
        }
        return $port;
    }

    /**
     * Returns the username for a database connection
     *
     * @return string The username for a {@link DatabaseConnection}
     *
     * @throws ConfigurationException When the username is not defined as a string
     */
    public function getUsername()
    {
        $username = $this->getData('username');
        if (!is_string($username)) {
            throw new ConfigurationException("The username must be defined as a string");
        }
        return $username;
    }

    /**
     * Returns the database name for a database connection
     *
     * @return string The database name for a {@link DatabaseConnection}
     *
     * @throws ConfigurationException When the database name is not defined as a string
     */
    public function getDatabaseName()
    {
        $databaseName = $this->getOptionalData('database', "");
        if (!is_string($databaseName)) {
            throw new ConfigurationException("The database name must be defined as a string");
        }
        return $databaseName;
    }

    /**
     * Returns the password for a database connection
     *
     * @return string The password for a {@link DatabaseConnection}
     *
     * @throws ConfigurationException When the password is not defined as a string
     */
    public function getPassword()
    {
        $password = $this->getOptionalData('password', "");
        if (!is_string($password)) {
            throw new ConfigurationException("The password must be defined as a string");
        }
        return $password;
    }

    /**
     * Returns the options for a database connection
     *
     * @return string The options for a {@link DatabaseConnection}
     *
     * @throws ConfigurationException When the options are not defined as a array
     */
    public function getOptions()
    {
        $options = $this->getOptionalData('options', []);
        if (!is_array($options)) {
            throw new ConfigurationException("The options must be a array");
        }
        return $options;
    }

    /**
     * Returns the charset for a database connection
     *
     * @return string The charset for a {@link DatabaseConnection}
     *
     * @throws ConfigurationException When the charset is not defined as a string
     */
    public function getCharset()
    {
        $charset = $this->getOptionalData('charset', 'UTF-8');
        if(!is_string($charset)) {
            throw new ConfigurationException("The charset must be defined a string");
        }
        return $charset;
    }
}

Again, there is the problem: should the array keys we're going to need to use this class already be known? Also, should verification be handled in this class as well?

For my usage in my 'database wrapper', I used this piece of code:

$this->instance = new \PDO(
    ...,
    $this->connectionConfiguration->getUsername(),
    $this->connectionConfiguration->getPassword(),
    $this->connectionConfiguration->getOptions()
);

Also, does this code follow the principles DRY, SPR and TDA?

I think I'm going to get stuck with my design of classes at some point.

\$\endgroup\$
  • \$\begingroup\$ Very readable code, nice! \$\endgroup\$ – Alex L Jan 22 '16 at 23:30
  • \$\begingroup\$ @AlexL Thanks. I assume there is alot to make better though. \$\endgroup\$ – Bas Jan 22 '16 at 23:40
1
\$\begingroup\$

some opinionated advice:

  • First I don't like to have the same method to get a single value and to get the whole values. That's why I create a "getConfig" to retrieve all data.
  • So the "ConfigurationVerificationHandler" is no longer needed, so I removed it.
  • To create more readable and consistent exceptions, I create static factory methods in the ConfigurationException class
  • Then, I merge the getOptionalData method in the getData (with an optional argument). Notice that method does not save the replacement in the current data array because having a method doing write and read operations is considered as a bad design.
  • I extract all the validators to their own class
  • To DRY all the getSomething methods, I create a getDataWithValidation in the Configuration class.

I don't have refactored all the methods, but for the nexts it is the same process.

class ConfigurationException extends \Exception
{
    public static function missingKey($key) { return new self("$key does not exist"); }
    public static function isNotAValidIpAddress() { return new self("The host has to be an IP-address"); }
    public static function isNotAValidPortNumber() { return new self("The port has to be a number"); }
    public static function usernameIsNotAString() { return new self("The username must be defined as a string"); }
}

class Validation
{
    public static function isAnIpAddress()
    {
        return function ($ip) { return !filter_var($ip, FILTER_VALIDATE_IP); };
    }

    public static function isANumber($number)
    {
        return function ($number) { return !is_float($number) || !is_int($number); };
    }

    public static function isString()
    {
        return function ($str) { return is_string($str); };
    }
}

class Configuration
{
    /**
     * @var array $data The data for the {@link Configuration}
     */
    private $data;

    /**
     * Creates a {@link Configuration} with data
     * @param array $data The data for the {@link Configuration}
     */
    public function __construct(array $data)
    {
        $this->data = $data;
    }

    /**
     * Returns data from the {@link Configuration}
     *
     * @param string $key A optional array key to look up data
     * @return mixed The selected data out of the {@link Configuration}
     * @throws ConfigurationException When the data is not present
     */
    public final function getData($key, $optional = null)
    {
        if (!isset($this->data[$key]) && $optional === null)
            throw ConfigurationException::missingKey($key);

        return array_key_exists($key, $this->data) ? $this->data[$key] : $optional;
    }

    public final function getDataWithValidation($key, Callable $validator, ConfigurationException $exception, $optional = null)
    {
        $item = $this->getData($key, $optional);
        if(!$validator($item)) throw $exception;

        return $item;
    }

    /**
     * @return array The config array
     */
    public final function getConfig()
    {
        return $this->data;
    }
}


class ConnectionConfiguration extends Configuration
{
    /**
     * @return mixed
     * @throws ConfigurationException
     */
    public final function getHost($default = null)
    {
        return $this->getDataWithValidation(
            'host',
            Validation::isAnIpAddress(),
            ConfigurationException::isNotAValidIpAddress(),
            $default
        );
    }

    /**
     * @return mixed
     * @throws ConfigurationException
     */
    public function getPort()
    {
        return $this->getDataWithValidation(
            'port',
            Validation::isANumber(),
            ConfigurationException::isNotAValidPortNumber()
        );
    }
}

class DatabaseConnectionConfiguration extends ConnectionConfiguration
{
    /**
     * Returns the port for a database connection
     *
     * @return int The port the database connection will listen on
     *
     * @throws ConfigurationException When the port is not defined as numeric
     */
    public function getPort()
    {
        return parent::getPort(3306);
    }

    /**
     * Returns the username for a database connection
     *
     * @return string The username for a {@link DatabaseConnection}
     *
     * @throws ConfigurationException When the username is not defined as a string
     */
    public function getUsername()
    {
        $this->getWithValidation(
            'username',
            Validation::isString(),
            ConfigurationException::usernameIsNotAString()
        );
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.