Last active
March 26, 2016 11:36
-
-
Save jeremeamia/cc602630a712672baa9e to your computer and use it in GitHub Desktop.
Mini-PHP config system for fun. (MIT Licensed)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace config; | |
const DELIM = '.'; | |
/** | |
* Loads a configuration array from a file, based on it's type. | |
* | |
* @param string $path | |
* @param string|null $type | |
* | |
* @return array | |
* @throws \RuntimeException | |
* @throws \InvalidArgumentException | |
*/ | |
function load($path, $type = null) | |
{ | |
if (!is_readable($path)) { | |
throw new \InvalidArgumentException('Unable to load configuration file.'); | |
} | |
$type = $type ?: strtolower(pathinfo($path, PATHINFO_EXTENSION)); | |
switch ($type) { | |
case 'php': | |
$data = include $path; | |
break; | |
case 'json': | |
$data = json_decode(file_get_contents($path), true); | |
if (json_last_error() !== JSON_ERROR_NONE) { | |
throw new \InvalidArgumentException('Unable to parse JSON data.'); | |
} | |
break; | |
case 'ini': | |
$data = parse_ini_file($path); | |
break; | |
case 'yaml': | |
if (!class_exists('Symfony\Component\Yaml\Yaml')) { | |
throw new \RuntimeException('Symfony YAML library not available.'); | |
} | |
$data = \Symfony\Component\Yaml\Yaml::parse($path); | |
break; | |
default: | |
throw new \InvalidArgumentException('Invalid config file format.'); | |
} | |
if (!is_array($data)) { | |
throw new \InvalidArgumentException('Loading the config file must return an array.'); | |
} | |
return $data; | |
} | |
/** | |
* Validates a config array based on the provided schema, the schema can specify | |
* for each key: the default value, whether it is required, the schema of any | |
* nested values, how to validate it, and how to transform it prior to validation. | |
* | |
* @param array $config | |
* @param array $schema | |
* @param string $delim Defaults to ".". | |
* | |
* @return array | |
* @throws \InvalidArgumentException | |
*/ | |
function validate(array $config = [], array $schema = [], $delim = DELIM, $namespace = null) | |
{ | |
foreach ($schema as $key => $s) { | |
$path = trim("{$namespace}{$delim}{$key}", $delim); | |
// Get the value from the config or use the default value. | |
$value = isset($config[$key]) | |
? $config[$key] | |
: (isset($s['default']) ? $s['default'] : null); | |
// Transform the value, if needed. | |
if (isset($s['transform']) && is_callable($s['transform'])) { | |
$value = $s['transform']($value); | |
} | |
// Ensure the value exists, if required. | |
if (isset($s['required']) && $s['required'] && $value === null) { | |
throw new \InvalidArgumentException("Missing a value for \"{$path}\" in the config."); | |
} | |
// Recurse into nested structures and validate, if needed. | |
if (is_array($value) && isset($s['schema'])) { | |
if (is_array($s['schema'])) { | |
validate($value, $s['schema'], $path); | |
} elseif (is_callable($s['schema'])) { | |
array_map(function ($value) use ($s, $path) { | |
if (!$s['schema']($value)) { | |
throw new \InvalidArgumentException("Invalid type for \"{$path}\"."); | |
} | |
}, $value); | |
} else { | |
throw new \InvalidArgumentException("A sub-schema must be an array or callable."); | |
} | |
} | |
// Validate the value using the specified callback. | |
if (isset($s['validate']) && is_callable($s['validate']) | |
&& !is_null($value) && !$s['validate']($value) | |
) { | |
throw new \InvalidArgumentException("Invalid type for \"{$path}\"."); | |
} | |
$config[$key] = $value; | |
} | |
return $config; | |
} | |
/** | |
* Fetches a value, or a nested value, from a config array using a path. Returns | |
* null if the value is not found. | |
* | |
* @param array $config | |
* @param string|array $path | |
* @param string $delim | |
* | |
* @return mixed | |
*/ | |
function get(array $config, $path, $delim = DELIM) | |
{ | |
$path = is_array($path) ? $path : explode($delim, $path); | |
$key = array_shift($path); | |
$value = isset($config[$key]) ? $config[$key] : null; | |
if (is_array($value) && $path) { | |
// Call this function recursively on the nested level. | |
$value = get($value, $path); | |
} | |
// If a lazy value is encountered, invoke it to get the real value. | |
if ($value instanceof _lazy_value) { | |
$value = $value(); | |
} | |
return $value; | |
} | |
/** | |
* Wraps a callable that is meant to be a factory for a value, so that the | |
* factory logic is deferred, or lazy. | |
* | |
* @param callable $createValue | |
* | |
* @return callable | |
*/ | |
function lazy(callable $createValue) { | |
return new _lazy_value($createValue); | |
} | |
/** | |
* @internal | |
*/ | |
final class _lazy_value | |
{ | |
private $createValue; | |
public function __construct(callable $createValue) | |
{ | |
$this->createValue = $createValue; | |
} | |
public function __invoke() | |
{ | |
return call_user_func($this->createValue); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use config; | |
$data = config\load('config.json'); | |
// OR | |
$data = [ | |
'a' => 'foo', | |
'c' => ['a', 'b', 'c'], | |
'd' => ['foo' => '5'], | |
'e' => config\lazy(function() {return time();}) | |
]; | |
// Allowed rules: default <mixed>, required <bool>, schema <callable|array>, transform <callable>, validate <callable> | |
$schema = [ | |
'a' => ['validate' => 'is_string', 'required' => true], | |
'b' => ['validate' => 'is_string', 'default' => 'cheese'], | |
'c' => ['validate' => 'is_array', 'schema' => 'is_string'], | |
'd' => ['validate' => 'is_array', 'schema' => [ | |
'foo' => [ | |
'required' => true, | |
'validate' => 'is_integer', | |
'transform' => 'intval' | |
], | |
]], | |
] | |
$data = config\validate($data, $schema); | |
print_r($data); | |
var_dump(config\get($data, 'd.foo')); | |
var_dump(config\get($data, 'e')); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Heh, we've created something very similar for validating input: https://github.com/dominionenterprises/filter-php/.