summaryrefslogtreecommitdiff
path: root/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php
blob: cd06f5722f5c5109bc93f72d08bc007c2bba1f54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<?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);
    }
}