1 : <?php
2 : /**
3 : * This file is part of NoiseLabs-PHP-ToolKit
4 : *
5 : * NoiseLabs-PHP-ToolKit is free software; you can redistribute it
6 : * and/or modify it under the terms of the GNU Lesser General Public
7 : * License as published by the Free Software Foundation; either
8 : * version 3 of the License, or (at your option) any later version.
9 : *
10 : * NoiseLabs-PHP-ToolKit is distributed in the hope that it will be
11 : * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
12 : * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : * Lesser General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU Lesser General Public
16 : * License along with NoiseLabs-PHP-ToolKit; if not, see
17 : * <http://www.gnu.org/licenses/>.
18 : *
19 : * Copyright (C) 2011 Vítor Brandão <noisebleed@noiselabs.org>
20 : *
21 : *
22 : * @category NoiseLabs
23 : * @package ConfigParser
24 : * @version 0.1.1
25 : * @author Vítor Brandão <noisebleed@noiselabs.org>
26 : * @copyright (C) 2011 Vítor Brandão <noisebleed@noiselabs.org>
27 : */
28 :
29 : namespace NoiseLabs\ToolKit\ConfigParser;
30 :
31 : use NoiseLabs\ToolKit\ConfigParser\ParameterBag;
32 :
33 1 : abstract class BaseConfigParser implements \ArrayAccess, \IteratorAggregate, \Countable
34 : {
35 : const VERSION = '0.1.0';
36 :
37 : /**
38 : * A set of internal options used when parsing and writing files.
39 : *
40 : * Known settings:
41 : *
42 : * 'delimiter':
43 : * The delimiter character to use between keys and values.
44 : * Defaults to '='.
45 : *
46 : * 'space_around_delimiters':
47 : * Put a blank space between keys/values and delimiters?
48 : * Defaults to TRUE.
49 : *
50 : * 'linebreak':
51 : * The linebreak to use.
52 : * Defaults to '\r\n' on Windows OS and '\n' on every other OS.
53 : *
54 : * 'interpolation':
55 : * @todo: Describe the interpolation mecanism.
56 : * Defaults to FALSE.
57 : */
58 : public $settings = array();
59 :
60 : /**
61 : *
62 : * @var array
63 : */
64 : protected $_defaults = array();
65 :
66 : /**
67 : * The configuration representation is stored here.
68 : * @var array
69 : */
70 : protected $_sections = array();
71 :
72 : /**
73 : * An array of FILE objects representing the loaded files.
74 : * @var array
75 : */
76 : protected $_files = array();
77 :
78 : /**
79 : * Booleans alias
80 : * @var array
81 : */
82 : protected $_boolean_states = array(
83 : '1' => true,
84 : 'yes' => True,
85 : 'true' => true,
86 : 'on' => true,
87 : '0' => false,
88 : 'no' => false,
89 : 'false' => false,
90 : 'off' => false
91 : );
92 :
93 : /**
94 : * Constructor.
95 : *
96 : * @param array $defaults
97 : * @param array $settings
98 : */
99 : public function __construct(array $defaults = array(), array $settings = array())
100 : {
101 5 : $this->_defaults = $defaults;
102 : // default options
103 5 : $this->settings = new ParameterBag(array(
104 5 : 'delimiter' => '=',
105 5 : 'space_around_delimiters' => true,
106 5 : 'linebreak' => "\n",
107 5 : 'throw_exceptions' => true,
108 : 'interpolation' => false
109 5 : ));
110 :
111 5 : if (!isset($settings['linebreak'])) {
112 : /*
113 : * OS detection to define the linebreak.
114 : * For Windows we use "\r\n".
115 : * For everything else "\n" is used.
116 : */
117 5 : if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
118 0 : $this->settings->set('linebreak', "\r\n");
119 0 : }
120 5 : }
121 :
122 5 : $this->settings->add($settings);
123 5 : }
124 :
125 : /**
126 : * Return an associative array containing the instance-wide defaults.
127 : */
128 : public function defaults()
129 : {
130 0 : return $this->_defaults;
131 : }
132 :
133 : /**
134 : * Note the usage of INI_SCANNER_RAW to avoid parser_ini_files from
135 : * parsing options and transforming 'false' values to empty strings.
136 : */
137 : protected function _read($filename)
138 : {
139 5 : return parse_ini_file($filename, static::HAS_SECTIONS, INI_SCANNER_RAW);
140 : }
141 :
142 : /**
143 : * Attempt to read and parse a list of filenames, returning a list of
144 : * filenames which were successfully parsed. If filenames is a string, it
145 : * is treated as a single filename. If a file named in filenames cannot be
146 : * opened, that file will be ignored. This is designed so that you can
147 : * specify a list of potential configuration file locations (for example,
148 : * the current directory, the user’s home directory, and some system-wide
149 : * directory), and all existing configuration files in the list will be
150 : * read. If none of the named files exist, the ConfigParser instance will
151 : * contain an empty dataset. An application which requires initial values
152 : * to be loaded from a file should load the required file or files using
153 : * read_file() before calling read() for any optional files:
154 : */
155 : public function read($filenames = array())
156 : {
157 5 : if (!is_array($filenames)) {
158 5 : $filenames = array($filenames);
159 5 : }
160 :
161 5 : foreach ($filenames as $filename) {
162 5 : if (is_readable($filename)) {
163 : // register a new file...
164 5 : $this->_files[] = new File($filename, 'rb');
165 : // ... and append configuration
166 5 : $this->_sections = array_replace(
167 5 : $this->_sections,
168 5 : $this->_read($filename)
169 5 : );
170 5 : }
171 5 : }
172 5 : }
173 :
174 : public function readFile($filehandler)
175 : {
176 0 : trigger_error(__METHOD__.' is not implemented yet');
177 0 : }
178 :
179 : public function readString($string)
180 : {
181 0 : $this->_sections = parse_ini_string($string, static::HAS_SECTIONS, INI_SCANNER_RAW);
182 0 : }
183 :
184 : public function readArray(array $array = array())
185 : {
186 0 : $this->_sections = $array;
187 0 : }
188 :
189 : /**
190 : * Re-read configuration from all successfully parsed files.
191 : */
192 : public function reload()
193 : {
194 0 : $filenames = array();
195 0 : foreach ($this->_files as $file) {
196 0 : $this->_sections = array_merge(
197 0 : $this->_sections,
198 0 : $this->_read($file->getPathname())
199 0 : );
200 0 : }
201 0 : }
202 :
203 : abstract protected function _buildOutputString();
204 :
205 : /**
206 : * Write an .ini-format representation of the configuration state
207 : *
208 : * @throws RuntimeException if file is not writable
209 : */
210 : public function write($filename)
211 : {
212 0 : $file = new File($filename);
213 :
214 0 : if (!$file->open('cb')) {
215 0 : $errmsg = 'Unable to write configuration as file '.$file->getPathname().' could not be opened for writing';
216 0 : if ($this->_throwExceptions()) {
217 0 : throw new \RuntimeException($errmsg);
218 : }
219 : else {
220 0 : $this->log($errmsg);
221 0 : return false;
222 : }
223 : }
224 0 : elseif (!$file->isWritable()) {
225 0 : $errmsg = 'Unable to write configuration as file '.$file->getPathname().' is not writable';
226 0 : if ($this->_throwExceptions()) {
227 0 : throw new \RuntimeException($errmsg);
228 : }
229 : else {
230 0 : $this->log($errmsg);
231 0 : return false;
232 : }
233 : }
234 :
235 0 : $file->write($this->_buildOutputString());
236 :
237 0 : $file->close();
238 0 : }
239 :
240 : /**
241 : * Returns the iterator for this group.
242 : *
243 : * @return \ArrayIterator
244 : */
245 : public function getIterator()
246 : {
247 0 : return new \ArrayIterator($this->_sections);
248 : }
249 :
250 : /**
251 : * Returns the number of sections (implements the \Countable interface).
252 : *
253 : * @return integer The number of sections
254 : */
255 : public function count()
256 : {
257 0 : return count($this->_sections);
258 : }
259 :
260 : /**
261 : * Write the stored configuration to the last file successfully parsed
262 : * in $this->read().
263 : */
264 : public function save()
265 : {
266 0 : $file = end($this->_files);
267 :
268 0 : return $this->write($file->getPathname());
269 : }
270 :
271 : /**
272 : * Removes all parsed data.
273 : *
274 : * @return void
275 : */
276 : public function clear()
277 : {
278 0 : $this->_sections = array();
279 0 : }
280 :
281 : /**
282 : * Output the current configuration representation.
283 : *
284 : * @return void
285 : */
286 : public function dump()
287 : {
288 0 : var_dump($this->_sections);
289 0 : }
290 :
291 : /**
292 : * Remove the specified section from the configuration. If the section in
293 : * fact existed, return TRUE. Otherwise return FALSE.
294 : */
295 : public function removeSection($section)
296 : {
297 0 : if (true === $this->hasSection($section)) {
298 0 : unset($this->_sections[$section]);
299 0 : return true;
300 : }
301 : else {
302 0 : return false;
303 : }
304 : }
305 :
306 : /**
307 : * Returns true if the section exists (implements the \ArrayAccess
308 : * interface).
309 : *
310 : * @param string $offset The name of the section
311 : *
312 : * @return Boolean true if the section exists, false otherwise
313 : */
314 : public function offsetExists($offset)
315 : {
316 0 : return $this->hasSection($offset);
317 : }
318 :
319 : /**
320 : * Returns the array of options associated with the section (implements
321 : * the \ArrayAccess interface).
322 : *
323 : * @param string $offset The offset of the value to get
324 : *
325 : * @return mixed The array of options associated with the section
326 : */
327 : public function offsetGet($offset)
328 : {
329 0 : return $this->hasSection($offset) ? $this->_sections[$offset] : null;
330 : }
331 :
332 : /**
333 : * Adds an array of options to the given section (implements the
334 : * \ArrayAccess interface).
335 : *
336 : * @param string $section The name of the section to insert $options.
337 : * @param array $options The array of options to be added
338 : */
339 : public function offsetSet($offset, $value)
340 : {
341 0 : $this->_sections[$offset] = $value;
342 0 : }
343 :
344 : /**
345 : * Removes the child with the given name from the form (implements the
346 : * \ArrayAccess interface).
347 : *
348 : * @param string $name The name of the child to be removed
349 : */
350 : public function offsetUnset($name)
351 : {
352 0 : $this->remove($name);
353 0 : }
354 :
355 : public function log($message, $level = 'crit')
356 : {
357 0 : error_log($message);
358 0 : }
359 :
360 : protected function _throwExceptions()
361 : {
362 0 : return (false === $this->settings->get('throw_exceptions')) ? false : true;
363 : }
364 : }
365 :
|