177 lines
5.9 KiB
PHP
177 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace Guzzle\Service;
|
|
|
|
use Guzzle\Common\Exception\InvalidArgumentException;
|
|
use Guzzle\Common\Exception\RuntimeException;
|
|
|
|
/**
|
|
* Abstract config loader
|
|
*/
|
|
abstract class AbstractConfigLoader implements ConfigLoaderInterface
|
|
{
|
|
/** @var array Array of aliases for actual filenames */
|
|
protected $aliases = array();
|
|
|
|
/** @var array Hash of previously loaded filenames */
|
|
protected $loadedFiles = array();
|
|
|
|
/** @var array JSON error code mappings */
|
|
protected static $jsonErrors = array(
|
|
JSON_ERROR_NONE => 'JSON_ERROR_NONE - No errors',
|
|
JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
|
|
JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
|
|
JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
|
|
JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
|
|
JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded'
|
|
);
|
|
|
|
public function load($config, array $options = array())
|
|
{
|
|
// Reset the array of loaded files because this is a new config
|
|
$this->loadedFiles = array();
|
|
|
|
if (is_string($config)) {
|
|
$config = $this->loadFile($config);
|
|
} elseif (!is_array($config)) {
|
|
throw new InvalidArgumentException('Unknown type passed to configuration loader: ' . gettype($config));
|
|
} else {
|
|
$this->mergeIncludes($config);
|
|
}
|
|
|
|
return $this->build($config, $options);
|
|
}
|
|
|
|
/**
|
|
* Add an include alias to the loader
|
|
*
|
|
* @param string $filename Filename to alias (e.g. _foo)
|
|
* @param string $alias Actual file to use (e.g. /path/to/foo.json)
|
|
*
|
|
* @return self
|
|
*/
|
|
public function addAlias($filename, $alias)
|
|
{
|
|
$this->aliases[$filename] = $alias;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Remove an alias from the loader
|
|
*
|
|
* @param string $alias Alias to remove
|
|
*
|
|
* @return self
|
|
*/
|
|
public function removeAlias($alias)
|
|
{
|
|
unset($this->aliases[$alias]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Perform the parsing of a config file and create the end result
|
|
*
|
|
* @param array $config Configuration data
|
|
* @param array $options Options to use when building
|
|
*
|
|
* @return mixed
|
|
*/
|
|
protected abstract function build($config, array $options);
|
|
|
|
/**
|
|
* Load a configuration file (can load JSON or PHP files that return an array when included)
|
|
*
|
|
* @param string $filename File to load
|
|
*
|
|
* @return array
|
|
* @throws InvalidArgumentException
|
|
* @throws RuntimeException when the JSON cannot be parsed
|
|
*/
|
|
protected function loadFile($filename)
|
|
{
|
|
if (isset($this->aliases[$filename])) {
|
|
$filename = $this->aliases[$filename];
|
|
}
|
|
|
|
switch (pathinfo($filename, PATHINFO_EXTENSION)) {
|
|
case 'js':
|
|
case 'json':
|
|
$level = error_reporting(0);
|
|
$json = file_get_contents($filename);
|
|
error_reporting($level);
|
|
|
|
if ($json === false) {
|
|
$err = error_get_last();
|
|
throw new InvalidArgumentException("Unable to open {$filename}: " . $err['message']);
|
|
}
|
|
|
|
$config = json_decode($json, true);
|
|
// Throw an exception if there was an error loading the file
|
|
if ($error = json_last_error()) {
|
|
$message = isset(self::$jsonErrors[$error]) ? self::$jsonErrors[$error] : 'Unknown error';
|
|
throw new RuntimeException("Error loading JSON data from {$filename}: ({$error}) - {$message}");
|
|
}
|
|
break;
|
|
case 'php':
|
|
if (!is_readable($filename)) {
|
|
throw new InvalidArgumentException("Unable to open {$filename} for reading");
|
|
}
|
|
$config = require $filename;
|
|
if (!is_array($config)) {
|
|
throw new InvalidArgumentException('PHP files must return an array of configuration data');
|
|
}
|
|
break;
|
|
default:
|
|
throw new InvalidArgumentException('Unknown file extension: ' . $filename);
|
|
}
|
|
|
|
// Keep track of this file being loaded to prevent infinite recursion
|
|
$this->loadedFiles[$filename] = true;
|
|
|
|
// Merge include files into the configuration array
|
|
$this->mergeIncludes($config, dirname($filename));
|
|
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* Merges in all include files
|
|
*
|
|
* @param array $config Config data that contains includes
|
|
* @param string $basePath Base path to use when a relative path is encountered
|
|
*
|
|
* @return array Returns the merged and included data
|
|
*/
|
|
protected function mergeIncludes(&$config, $basePath = null)
|
|
{
|
|
if (!empty($config['includes'])) {
|
|
foreach ($config['includes'] as &$path) {
|
|
// Account for relative paths
|
|
if ($path[0] != DIRECTORY_SEPARATOR && !isset($this->aliases[$path]) && $basePath) {
|
|
$path = "{$basePath}/{$path}";
|
|
}
|
|
// Don't load the same files more than once
|
|
if (!isset($this->loadedFiles[$path])) {
|
|
$this->loadedFiles[$path] = true;
|
|
$config = $this->mergeData($this->loadFile($path), $config);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default implementation for merging two arrays of data (uses array_merge_recursive)
|
|
*
|
|
* @param array $a Original data
|
|
* @param array $b Data to merge into the original and overwrite existing values
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function mergeData(array $a, array $b)
|
|
{
|
|
return array_merge_recursive($a, $b);
|
|
}
|
|
}
|