I'm writing my own framework (purely for learning purposes, SPL, OOP, patterns, etc) and have written a class to manage config data throughout the framework (not front end/view, just the core framework).
Example of config data:
The framework requires a specific PHP version to run correctly, and that "required PHP version" is stored in config settings. That config data is needed in a class which checks if the running PHP version is not lower than the required PHP version.
I've read about various options for configs (for tens of hours over weeks), and the main ones seem to be:
Constants; ini files; an array in an included file; static classes.
They all have pros and cons, but the main con is they push things into global, which I'm really trying to avoid.
They also don't lend themselves to dependency injection, which I'm really trying to achieve.
I've made my own class for managing this data, and would like it reviewed, please.
Class Description
The config class is instantiated in the bootloader file before any classes which need it, so the object variable is ready to be used in any class to call the method.
Note: I'll be changing how this class is instantiated when I get around to Dependency Injection Container, so please review based on that, and ignore that the object variable for this class is global - it eventually won't be.
The config class' method getConfig()
is used in the __construct
(or getter method etc) in any class wanting config data. The getConfig()
accepts parameters so specific config data can be retrieved.
The class
NOTE: In code comments and later text, by "sub array" I mean one of the single sub arrays within the config array - such as the "php" array, or "router" array.
class coreConfig {
private $configData;
private $returnData;
private function setConfigData($key, $value = '') {
$configData = array (
'php' => array(
'current' => PHP_VERSION_ID,
'required' => '50400',
'test-index' => 'php test data',
),
'framework' => array(
'name' => 'frameworky',
'version' => '1.0',
'date' => '01/01/2015',
'email' => '[email protected]',
),
'router' => array(
'allowedchars' => 'A-z',
'url-prefix' => 'http://',
'test-index' => 'router test data',
),
);
if (!isset($configData[$key])
|| (!empty($value) && !isset($configData[$key][$value]))) {
return false;
}elseif (empty($value)) {
return $configData[$key];
} else {
return $configData[$key][$value];
}
}
public function getConfig($request = array()) {
$this->returnData = '';
// No data requested
if (empty($request)) {
$this->returnData = false;
} elseif (!is_array($request)) {
// Returns 1 single sub array
$this->returnData[$request] = $this->setConfigData($request);
} else {
// Array(s) requested
foreach ($request as $key => $value) {
if (!is_array($value)) {
// Returns entire sub array
$this->returnData[$value] = $this->setConfigData($value);
} else {
// Specific sub array value(s) requested
foreach ($value as $subKey => $subValue) {
// 3rd level sub arrays not expected
if (is_array($subValue)) {
$this->returnData[$subKey] = false;
} else {
// Returns sub array value
$this->returnData[$key][$subValue] = $this->setConfigData($key, $subValue);
}
}
}
}
}
return $this->returnData;
}
}
Options
Note: By "key" I mean send in a key which exists in the config array.
The options to send to the getConfig()
method are:
- A single key: Returns an entire single sub array
- An array of single keys: Returns multiple entire single sub arrays
- An array with a key and value(s), or multiple arrays with a key and value(s): Returns specific value(s) from the sub array(s) for each specified key
- Any mixture of 2 and 3 can be sent in one call to the method
Here are some example usages based on the above options. The results are from print_r()
:
$obj = new coreConfig;
//*Option 1*:
$config1 = $obj->getConfig('php');
// Returns single entire sub array:
Array (
[php] => Array (
[current] => 50609 [required] => 50400 [test-index] => php test data
)
)
//*Option 2*:
$config2 = $obj->getConfig(array('php', 'router'));
// Returns 2 entire sub arrays:
Array (
[php] => Array (
[current] => 50609 [required] => 50400 [test-index] => php test data
)
[router] => Array (
[allowedchars] => A-z [url-prefix] => http://
[test-index] => router test data
)
)
//*Option 3*:
$config3 = $obj->getConfig(array('php' => array('current', 'test-index')));
// Returns partial sub array:
Array (
[php] => Array (
[current] => 50609 [test-index] => php test data
)
)
//*Option 4*:
$config4 = $obj->getConfig(array(
'php' => array('current', 'test-index'),
'router'
)
);
// Returns partial sub array, and entire sub array:
Array (
[php] => Array (
[current] => 50609 [test-index] => php test data
)
[router] => Array (
[allowedchars] => A-z [url-prefix] => http://
[test-index] => router test data
)
)
My Questions
- Is my general approach sane and decent for retrieving system config data within a framework?
- Is it acceptable using the public (get) method to call a private
(set) method (same class) to set the config array and retrieve data?
e.g.$this->returnData[$value] = $this->setConfigData($value);
- Unit testing - I have no experience, but know of its existence and some coding practices to following to cater for unit testing. With my class, the config data cannot be mocked in a test, is that a problem for unit testing?
- Does the code set the array in
setConfigData()
every time data is requested bygetConfig()
? If so, would this be resolved/improved if I made the array an object - i.e.$this-configData
? In trying to potentially re-use objects - Is the
setConfigData()
method ok having thoseif/else
? It's like that because it allows thesetConfigData()
method to return only the required data, whereas this functionality being in thegetConfig()
method meant getting the entire array to check if keys existed etc. - My main niggle is to add config settings for the framework, one has to edit an array within a class method. This seems.. fiddly for config, but there are also benefits of it being in a class (DI, DIC, etc). Do you see this as a major issue as a developer adding config data in a class method array?
Is it worthwhile having the class take in so many options? Such as full config array request, partial, and a mixture, etc, all in one method call.
Or would it be better to instead have the class only accept a single value and return an entire single sub array for each method call, and then call the method as many times as needed?
//*Currently*: $allConfigData = $obj->getConfig('everything-I-could-possibly-need') //*Alternative with class only accepting and returning one full sub array*: $configData1 = $obj->getConfig('data-I-need'); $configData2 = $obj->getConfig('more-data-I-need'); $configData3 = $obj->getConfig('additional-data');
I know this is a bit broad, but I'm asking you as a seasoned developer if this could potentially be useful. Currently I do not need more than one config sub array in any class, but my framework is expanding so potentially this could become useful.