8
\$\begingroup\$

Someone asked if their singleton is good for OOP:

Using a singleton class to get and set program wide settings.

However, the answer just provides tweaks to his existing code - but the question wasn't entirely answered. What is the best practice to use for configuration files so that it doesn't become spaghetti code?

All apps need configuration settings so that it saves time and it looks neat.

I currently use something similar to what was proposed but not a singleton. However, I have a long array that feeds into different constructors. I feel that this is improper or maybe it is.

namespace App;

class Config
{
    protected static $config = array();

    public static function get($name, $default = null)
    {
        return isset(self::$config[$name]) ? self::$config[$name] : $default;
    }
    public static function add($parameters = array())
    {
        self::$config = array_merge(self::$config, $parameters);
    }
}
App\Config::add(
    array(
        'database' => array(
            'default' => array(
                'driver'    => 'mysql',
                'host'      => '127.0.0.1',
                'username'  => 'user',
                'password'  => '',
                'database'  => 'database',
                'charset'   => 'utf8',
                'collation' => 'utf8_unicode_ci'
            ),
            'tracking' => array(
                'driver'    => 'mysql',
                'host'      => '192.168.0.1',
                'username'  => 'tracking',
                'password'  => '123456',
                'database'  => 'tracking',
                'charset'   => 'utf8',
                'collation' => 'utf8_unicode_ci'
            ),
            'data' => array(
                'driver'    => 'mysql',
                'host'      => '192.168.0.11',
                'username'  => 'data',
                'password'  => 'abcder',
                'database'  => 'data_info',
                'charset'   => 'utf8',
                'collation' => 'utf8_unicode_ci'
            )
        ),

        'pheanstalk' => array(
            'default' => array(
                'host'       => 'localhost',
                'port'       => 11400,
                'queue_name' => 'queue'
            )
        ),
        'app_worker' => array(
            'log_files_path'      => ROOT.'/logs',
            'file_size_limit'     => 542880, //5MB
            'file_name_extension' => 'w_log',
        ),
        'memcache' => array(
            'default' => array(
                array(
                    'host'  => 'localhost',
                    'port'  => '11211'
                ),
                array(
                    'host'  => 'localhost',
                    'port'  => '11211'
                ),
                array(
                    'host'  => 'localhost',
                    'port'  => '11211'
                )
            )
        )
    )
);

Then in my main program:

$db_config = App\Config::get('database');

Now it's ok for me to add extra configuration needs for different object I might create and etc and I can load up a controller or piece of code to fetch it from. However, I feel that this configuration file that adds all these settings will be terribly long in the long wrong.

I would like to know if there something better, and what. I tried to use inheritance or DI but my configuration file is still long.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I personally use defines for for settings rather than utilising arrays.

A singleton is the correct route to go down and to help reduce your code and array you may want to use external files.

This is my config loader class with some simple caching that supports both database and file;

<?php
/**
 * Config loader 
 */
final class Config extends Singleton{
    public static $loadedConfigs = array();

    /**
     * Get specific config value
     * 
     * @param String $scope  Configuration scope, used to determine config location
     * @param String $module Specific config module name
     * @param String $param  (optional) Specific config option
     * 
     * @return Mixed    Requested parameter value if requested, otherwise associative array or configuration key:pair values unless config file couldn't be loaded, then false
     * @author Dean Whitehouse
     * @copyright Dean Whitehouse 2014
     * @todo verify the config name is a valid one 
     */
    public static function get($scope, $module, $param = null){
        if($param !== null && $param != ''){
            $const = strtoupper((string)$param);
        }

        $errorAppend = '; requested scope `'.$scope.'` with module `'.$module.'`;'.($param !== null ? 'Parameter requested `'.$param.'`' : '');

        if(isset(static::$loadedConfigs[$scope][$module])){
            $config = static::$loadedConfigs[$scope][$module];

            if(isset($const)){
                if(!isset($config[$const])){
                    error_log('Constant `'.$const.'` undefined'.$errorAppend);
                    return false;
                }
                else
                    $config = $config[$const];
            }
            return $config;
        }
        else{
            if($scope == 'core'){
                $file = '_app/core/config/'.$module.'.config.php';

                if(!file_exists($file)){
                    error_log('Config file doesn\'t exist at `'.$file.'`'.$errorAppend);
                    return false;
                }
                else{
                    $consts = get_defined_constants(true);
                    $consts = isset($consts['user']) ? $consts['user'] : array();

                    require_once $file;

                    $new = get_defined_constants(true);
                    $new = $new['user'];
                    $diff = array_diff_assoc($new, $consts);
                    static::$loadedConfigs[$scope][$module] = $diff;

                    if(isset($const)){
                        if(defined($const))
                            return constant($const);
                        else{
                            error_log('Constant `'.$const.'` undefined'.$errorAppend);
                            return false;
                        }
                    }

                    return static::$loadedConfigs[$scope][$module];
                }
            }
            elseif($scope == 'coreDB'){
                $check = db\sqli::query("SHOW TABLES LIKE '".db\sqli::escape($module)."'; -- Database check");

                if($check->num_rows == 0){
                    error_log('Config table doesn\'t exist'.$errorAppend);
                    return false;
                }
                else{
                    $query = 'SELECT `key`, `value` FROM `'.db\sqli::escape($module).'`';
                    if($param !== null){
                        $query .= ' WHERE `key` = '.db\sqli::escape($param).' LIMIT 1';
                    }

                    $query = db\sqli::query($query);

                    if($query->num_rows > 0){

                        $consts = get_defined_constants(true);
                        $consts = isset($consts['user']) ? $consts['user'] : array();

                        while($config = $query->fetch_assoc()){
                            define($config['key'], $config['value']);
                        }

                        $new = get_defined_constants(true);
                        $new = $new['user'];
                        $diff = array_diff_assoc($new, $consts);
                        static::$loadedConfigs[$scope][$module] = $diff;

                        if(isset($const)){
                            if(defined($const))
                                return constant($const);
                        }

                        return static::$loadedConfigs[$scope][$module];
                    }
                    else{
                        error_log('Constant `'.$const.'` undefined'.$errorAppend);
                        return false;
                    }

                }
            }
            else{
                error_log('Config scope `'.$scope.'` isn\'t recognised'.$errorAppend);
                return false;
            }
        }
    }

    /**
     * Load configuration module
     * 
     * @param String $scope  Configuration scope, used to determine config location
     * @param String $module Specific config module name
     * 
     * @return Mixed    False if configuration couldn't be loaded, otherwise associative array or configuration key:pair values
     * @author Dean Whitehouse
     * @copyright Dean Whitehouse 2014
     */
    public static function load($scope, $module){
        return static::get($scope, $module);
    }
}
?>

I call it like:

Config::load('core', 'env');
Config::load('core', 'db');

Edit

Sample config file 'env.config.php'

<?php        
define('AUTO_REPAIR', true);

define('APP_ROOT', rtrim($_SERVER['SCRIPT_NAME'], '/index.php')); //folder the root of the site is in - only if in a directory

define('SECURE', true);

define('LOG_DIR', '_app/logs/');

define('SECURE_KEY', ''); //used for CSRF, NONCE and Cookies
?>
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Can you include the structure of your define so that we can correlate your class and the call. Tks. \$\endgroup\$
    – azngunit81
    Commented Mar 17, 2014 at 12:33
  • \$\begingroup\$ Updated to include this \$\endgroup\$ Commented Mar 17, 2014 at 13:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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