Ini::processKey()   C
last analyzed

Complexity

Conditions 8
Paths 6

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 6
nop 3
dl 0
loc 24
rs 5.7377
c 0
b 0
f 0
1
<?php
2
namespace FMUP\Config\Ini\Extended\ZendConfig;
3
4
use FMUP\Config\Exception;
5
use FMUP\Config\Ini\Extended\ZendConfig;
6
7
/**
8
 * @see ZendConfig
9
 */
10
11
/**
12
 * @category   Zend
13
 * @package    Zend_Config
14
 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
15
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
16
 * @codeCoverageIgnore since its an open source component
17
 * @SuppressWarnings(PHPMD)
18
 */
19
class Ini extends ZendConfig
20
{
21
    /**
22
     * StringHandle that separates nesting levels of configuration data identifiers
23
     *
24
     * @var string
25
     */
26
    protected $nestSeparator = '.';
27
28
    /**
29
     * StringHandle that separates the parent section name
30
     *
31
     * @var string
32
     */
33
    protected $sectionSeparator = ':';
34
35
    /**
36
     * Whether to skip extends or not
37
     *
38
     * @var boolean
39
     */
40
    protected $skipExtends = false;
41
42
    /**
43
     * Loads the section $section from the config file $filename for
44
     * access facilitated by nested object properties.
45
     *
46
     * If the section name contains a ":" then the section name to the right
47
     * is loaded and included into the properties. Note that the keys in
48
     * this $section will override any keys of the same
49
     * name in the sections that have been included via ":".
50
     *
51
     * If the $section is null, then all sections in the ini file are loaded.
52
     *
53
     * If any key includes a ".", then this will act as a separator to
54
     * create a sub-property.
55
     *
56
     * example ini file:
57
     *      [all]
58
     *      db.connection = database
59
     *      hostname = live
60
     *
61
     *      [staging : all]
62
     *      hostname = staging
63
     *
64
     * after calling $data = new Zend_Config_Ini($file, 'staging'); then
65
     *      $data->hostname === "staging"
66
     *      $data->db->connection === "database"
67
     *
68
     * The $options parameter may be provided as either a boolean or an array.
69
     * If provided as a boolean, this sets the $allowModifications option of
70
     * Zend_Config. If provided as an array, there are three configuration
71
     * directives that may be set. For example:
72
     *
73
     * $options = array(
74
     *     'allowModifications' => false,
75
     *     'nestSeparator'      => ':',
76
     *     'skipExtends'        => false,
77
     *      );
78
     *
79
     * @param  string $filename
80
     * @param  mixed $section
81
     * @param  boolean|array $options
82
     * @throws Exception
83
     */
84
    public function __construct($filename, $section = null, $options = false)
85
    {
86
        if (empty($filename)) {
87
            throw new Exception('Filename is not set');
88
        }
89
90
        $allowModifications = false;
91
        if (is_bool($options)) {
92
            $allowModifications = $options;
93
        } elseif (is_array($options)) {
94
            if (isset($options['allowModifications'])) {
95
                $allowModifications = (bool)$options['allowModifications'];
96
            }
97
            if (isset($options['nestSeparator'])) {
98
                $this->nestSeparator = (string)$options['nestSeparator'];
99
            }
100
            if (isset($options['skipExtends'])) {
101
                $this->skipExtends = (bool)$options['skipExtends'];
102
            }
103
        }
104
105
        $iniArray = $this->loadIniFile($filename);
106
107
        if (null === $section) {
108
            // Load entire file
109
            $dataArray = array();
110
            foreach ($iniArray as $sectionName => $sectionData) {
111
                if (!is_array($sectionData)) {
112
                    $dataArray = $this->arrayMergeRecursive(
113
                        $dataArray,
114
                        $this->processKey(array(), $sectionName, $sectionData)
115
                    );
116
                } else {
117
                    $dataArray[$sectionName] = $this->processSection($iniArray, $sectionName);
118
                }
119
            }
120
            parent::__construct($dataArray, $allowModifications);
121
        } else {
122
            // Load one or more sections
123
            if (!is_array($section)) {
124
                $section = array($section);
125
            }
126
            $dataArray = array();
127
            foreach ($section as $sectionName) {
128
                if (!isset($iniArray[$sectionName])) {
129
                    throw new Exception("Section '$sectionName' cannot be found in $filename");
130
                }
131
                $dataArray = $this->arrayMergeRecursive($this->processSection($iniArray, $sectionName), $dataArray);
132
            }
133
            parent::__construct($dataArray, $allowModifications);
134
        }
135
136
        $this->loadedSection = $section;
137
    }
138
139
    /**
140
     * Load the INI file from disk using parse_ini_file(). Use a private error
141
     * handler to convert any loading errors into a Zend_Config_Exception
142
     *
143
     * @param string $filename
144
     * @throws Exception
145
     * @return array
146
     */
147
    protected function parseIniFile($filename)
148
    {
149
        set_error_handler(array($this, 'loadFileErrorHandler'));
150
        $iniArray = parse_ini_file($filename, true); // Warnings and errors are suppressed
151
        restore_error_handler();
152
153
        // Check if there was a error while loading file
154
        if ($this->loadFileErrorStr !== null) {
155
            throw new Exception($this->loadFileErrorStr);
156
        }
157
158
        return $iniArray;
159
    }
160
161
    /**
162
     * Load the ini file and preprocess the section separator (':' in the
163
     * section name (that is used for section extension) so that the resultant
164
     * array has the correct section names and the extension information is
165
     * stored in a sub-key called ';extends'. We use ';extends' as this can
166
     * never be a valid key name in an INI file that has been loaded using
167
     * parse_ini_file().
168
     *
169
     * @param string $filename
170
     * @throws Exception
171
     * @return array
172
     */
173
    protected function loadIniFile($filename)
174
    {
175
        $loaded = $this->parseIniFile($filename);
176
        $iniArray = array();
177
        foreach ($loaded as $key => $data) {
178
            $pieces = explode($this->sectionSeparator, $key);
179
            $thisSection = trim($pieces[0]);
180
            switch (count($pieces)) {
181
                case 1:
182
                    $iniArray[$thisSection] = $data;
183
                    break;
184
185
                case 2:
186
                    $extendedSection = trim($pieces[1]);
187
                    $iniArray[$thisSection] = array_merge(array(';extends' => $extendedSection), $data);
188
                    break;
189
190
                default:
191
                    throw new Exception("Section '$thisSection' may not extend multiple sections in $filename");
192
            }
193
        }
194
195
        return $iniArray;
196
    }
197
198
    /**
199
     * Process each element in the section and handle the ";extends" inheritance
200
     * key. Passes control to processKey() to handle the nest separator
201
     * sub-property syntax that may be used within the key name.
202
     *
203
     * @param  array $iniArray
204
     * @param  string $section
205
     * @param  array $config
206
     * @throws Exception
207
     * @return array
208
     */
209
    protected function processSection($iniArray, $section, $config = array())
210
    {
211
        $thisSection = $iniArray[$section];
212
213
        foreach ($thisSection as $key => $value) {
214
            if (strtolower($key) == ';extends') {
215
                if (isset($iniArray[$value])) {
216
                    $this->assertValidExtend($section, $value);
217
218
                    if (!$this->skipExtends) {
219
                        $config = $this->processSection($iniArray, $value, $config);
220
                    }
221
                } else {
222
                    throw new Exception("Parent section '$section' cannot be found");
223
                }
224
            } else {
225
                $config = $this->processKey($config, $key, $value);
226
            }
227
        }
228
        return $config;
229
    }
230
231
    /**
232
     * Assign the key's value to the property list. Handles the
233
     * nest separator for sub-properties.
234
     *
235
     * @param  array $config
236
     * @param  string $key
237
     * @param  string $value
238
     * @throws Exception
239
     * @return array
240
     */
241
    protected function processKey($config, $key, $value)
242
    {
243
        if (strpos($key, $this->nestSeparator) !== false) {
244
            $pieces = explode($this->nestSeparator, $key, 2);
245
            if (strlen($pieces[0]) && strlen($pieces[1])) {
246
                if (!isset($config[$pieces[0]])) {
247
                    if ($pieces[0] === '0' && !empty($config)) {
248
                        // convert the current values in $config into an array
249
                        $config = array($pieces[0] => $config);
250
                    } else {
251
                        $config[$pieces[0]] = array();
252
                    }
253
                } elseif (!is_array($config[$pieces[0]])) {
254
                    throw new Exception("Cannot create sub-key for '{$pieces[0]}' as key already exists");
255
                }
256
                $config[$pieces[0]] = $this->processKey($config[$pieces[0]], $pieces[1], $value);
257
            } else {
258
                throw new Exception("Invalid key '$key'");
259
            }
260
        } else {
261
            $config[$key] = $value;
262
        }
263
        return $config;
264
    }
265
}
266