1 <?php
2 /**
3 * Contains Config class for Alphred
4 *
5 * PHP version 5
6 *
7 * @package Alphred
8 * @copyright Shawn Patrick Rice 2014
9 * @license http://opensource.org/licenses/MIT MIT
10 * @version 1.0.0
11 * @author Shawn Patrick Rice <rice@shawnrice.org>
12 * @link http://www.github.com/shawnrice/alphred
13 * @link http://shawnrice.github.io/alphred
14 * @since File available since Release 1.0.0
15 *
16 */
17
18 namespace Alphred;
19
20 /**
21 * A simple class to manage configuration for workflows
22 *
23 * Currently, there are three handlers that are available: `json`, `sqlite`, and
24 * `ini`. These correspond to their obvious data storage types. To use, do something
25 * simple like:
26 * ````php
27 * $config = new Alphred\Config( 'ini' );
28 * $config->set( 'username', 'shawn patrick rice' );
29 * ````
30 * To get it later, just use:
31 * ````php
32 * $username = $config->read( 'username' );
33 * ````
34 *
35 * You can store arrays and more complex data with the `json` and `ini` handlers.
36 * Currently, the SQLite3 handler is a bit primitive.
37 *
38 */
39 class Config {
40
41 /**
42 * A list of valid handlers and their file extensions
43 *
44 * Current options are `json`, `sqlite`, and `ini`.
45 *
46 * @since 1.0.0
47 *
48 * @var array
49 */
50 private $handlers = [
51 // as file_extension => handler_name
52 'json' => 'json',
53 'sqlite3' => 'sqlite',
54 'ini' => 'ini',
55 ];
56
57 /**
58 * Constructs the Config object
59 *
60 * @since 1.0.0
61 * @todo Make this pluggable (to load custom handlers)
62 *
63 * @param string $handler the name of the handler
64 * @param string $filename the basename of the config file
65 */
66 public function __construct( $handler, $filename = 'config' ) {
67
68 // Do a quick check to make sure that we're running in a workflow environment
69 // because we need access to certain variables for this to work
70 if ( ! Globals::bundle() ) {
71 throw new RunningOutsideOfAlfred( "Cannot use Alphred's Config outside of the workflow environment", 4 );
72 }
73 // Make sure that the data directory has been created
74 self::create_data_directory();
75
76 if ( ! in_array( $handler, $this->handlers ) ) {
77 /**
78 * @todo Redo the exception
79 */
80 throw new Exception( "Unknown config handler: {$handler}", 4 );
81 }
82
83 // Set the handler
84 $this->handler = $handler;
85 // Construct the filename
86 $this->filename = $filename . '.' . array_search( $handler, $this->handlers );
87 // Load the handler
88 $this->load_handler( $this->handler );
89
90 }
91
92
93 /**
94 * Creates the data directory
95 *
96 * @throws \Alphred\RunningOutsideOfAlfred
97 *
98 * @return bool Whether or not the directory was created or exists
99 */
100 private function create_data_directory() {
101 // Get the data directory from the Globals array
102 if ( $dir = Globals::data() ) {
103 // If the directory does not exist, then make it
104 if ( ! file_exists( $dir ) ) {
105 // Debug-level log message
106 \Alphred\Log::log( "Creating data directory.", 0, 'debug' );
107
108 // Make the directory
109 return mkdir( $dir, 0775, true );
110 } else {
111 // Directory exists, so return true
112 return true;
113 }
114 }
115 // The workflow_data
116 throw new RunningOutsideOfAlfred( "Cannot use Alphred's Config outside of the workflow environment", 4 );
117 }
118
119 /**
120 * Gets the path for the config file
121 *
122 * @since 1.0.0
123 *
124 * @return string path to config file
125 */
126 private function get_config_file() {
127 return Globals::data() . '/' . $this->filename;
128 }
129
130 /*****************************************************************************
131 * Generic Handler Wrapper Functions
132 ****************************************************************************/
133
134 /**
135 * Loads a handler
136 *
137 * @since 1.0.0
138 *
139 * @param string $handler the name of the handler
140 * @return bool success of loading the handler
141 */
142 private function load_handler( $handler ) {
143 $method = 'load_' . $this->handler;
144 \Alphred\Log::console( 'Loading config file `' . $this->get_config_file() . '`', 0 );
145 return $this->$method();
146 }
147
148 /**
149 * Sets a value in the config
150 *
151 * @since 1.0.0
152 *
153 * @param string $key the key to set
154 * @param mixed $value the value to set
155 * @return bool success as true/false
156 */
157 public function set( $key, $value ) {
158 $method = 'set_' . $this->handler;
159 return $this->$method( $key, $value );
160 }
161
162 /**
163 * Reads a value from the config
164 *
165 * @since 1.0.0
166 *
167 * @param string $key the key to read
168 * @return mixed the values for the key
169 */
170 public function read( $key ) {
171 $method = 'read_' . $this->handler;
172 return $this->$method( $key );
173 }
174
175 /**
176 * Unsets a value from the config
177 *
178 * Note: I would name this `unset`, but that's a reserved function name.
179 *
180 * @since 1.0.0
181 *
182 * @param string $key the name of the key
183 * @return boolean
184 */
185 public function delete( $key ) {
186 $method = 'unset_' . $this->handler;
187 return $this->$method( $key );
188 }
189
190 /*****************************************************************************
191 * JSON Handler
192 ****************************************************************************/
193
194 /**
195 * Loads the json handler
196 *
197 * @since 1.0.0
198 *
199 * @return boolean success if handler was loaded
200 */
201 private function load_json() {
202 if ( ! file_exists( $this->get_config_file() ) ) {
203 file_put_contents( $this->get_config_file(), [] );
204 }
205 $this->config = json_decode( file_get_contents( $this->get_config_file() ), true );
206 return true;
207 }
208
209 /**
210 * Sets a config value
211 *
212 * @since 1.0.0
213 *
214 * @param string $key the name of the key
215 * @param mixed $value the value of the key
216 * @return boolean success
217 */
218 private function set_json( $key, $value ) {
219 // Reload the json file
220 $this->load_json();
221 $this->config[ $key ] = $value;
222 return file_put_contents( $this->get_config_file(), json_encode( $this->config ) );
223 }
224
225 /**
226 * Reads a config value
227 *
228 * @since 1.0.0
229 *
230 * @param string $key the key to read
231 * @return mixed the value of the key
232 */
233 private function read_json( $key ) {
234 if ( isset( $this->config[ $key ] ) ) {
235 return $this->config[ $key ];
236 }
237 throw new ConfigKeyNotSet( "`{$key}` is not set.", 1 );
238 }
239
240 /**
241 * Unsets a config value
242 *
243 * @since 1.0.0
244 *
245 * @param string $key the key to unset
246 * @return boolean true if the key existed; false if the key did not exist
247 */
248 private function unset_json( $key ) {
249 if ( ! isset( $this->config[ $key ] ) ) {
250 return false;
251 }
252 unset( $this->config[ $key ] );
253 file_put_contents( $this->get_config_file(), json_encode( $this->config ) );
254 return true;
255 }
256
257 /*****************************************************************************
258 * SQLite3 Handler
259 ****************************************************************************/
260
261 /**
262 * Loads the SQLite3 handler
263 *
264 * @since 1.0.0
265 *
266 * @return boolean true on success
267 */
268 private function load_sqlite() {
269 $this->db = new \SQLite3( $this->get_config_file() );
270 $this->init_db_table();
271 return true;
272 }
273
274 /**
275 * Creates the database table
276 *
277 * @return boolean true on success
278 */
279 private function init_db_table() {
280 return $this->db->exec(
281 'CREATE TABLE IF NOT EXISTS config (key TEXT NOT NULL PRIMARY KEY, value TEXT) WITHOUT ROWID;'
282 );
283 }
284
285 /**
286 * Sets a config value
287 *
288 * @since 1.0.0
289 *
290 * @param string $key the name of the key
291 * @param mixed $value the value to set
292 * @return boolean success
293 */
294 private function set_sqlite( $key, $value, $overwrite = true ) {
295 $key = $this->db->escapeString( $key );
296 $value = $this->db->escapeString( $value );
297 if ( $overwrite ) {
298 $this->db->exec( "DELETE FROM config WHERE key='{$key}';" );
299 }
300 $query = "INSERT OR REPLACE INTO config (key, value) values ('{$key}', '{$value}');";
301 return $this->db->exec( $query );
302 }
303
304 /**
305 * Reads a config value
306 *
307 * @since 1.0.0
308 *
309 * @param string $key the key to read
310 * @return mixed the value of the key
311 */
312 private function read_sqlite( $key ) {
313 $query = $this->db->prepare( 'SELECT * FROM config WHERE key=:key' );
314 $query->bindValue( ':key', $key, SQLITE3_TEXT );
315 $result = $query->execute();
316 // the assumption is that there will be only one row
317 $result = $result->fetchArray( SQLITE3_ASSOC );
318 if ( isset( $result[ 'value' ] ) ) {
319 return $result['value'];
320 }
321 throw new ConfigKeyNotSet( "`{$key}` is not set.", 1 );
322 }
323
324 /**
325 * Deletes a value from the config
326 *
327 * @since 1.0.0
328 *
329 * @param string $key the key to delete
330 * @return boolean success
331 */
332 private function unset_sqlite( $key ) {
333 $key = $this->db->escapeString( $key );
334 return $this->db->exec( "DELETE FROM config WHERE key = '{$key}';" );
335 }
336
337 /*****************************************************************************
338 * Ini Handler
339 ****************************************************************************/
340
341 /**
342 * Loads the ini handler
343 *
344 * @since 1.0.0
345 * @see Ini INI functionality
346 *
347 * @return bool true on success
348 */
349 private function load_ini() {
350 // Check if the file exists
351 if ( ! file_exists( $this->get_config_file() ) ) {
352 // Create an empty config file since none exists
353 Ini::write_ini( [], $this->get_config_file() );
354 }
355 // Load the config
356 $this->config = Ini::read_ini( $this->get_config_file() );
357 return true;
358 }
359
360 /**
361 * Sets a config value
362 *
363 * @since 1.0.0
364 * @see Ini INI functionality
365 *
366 * @param string $key the key to set
367 * @param mixed $value the value to set
368 */
369 private function set_ini( $key, $value ) {
370 // Re-initialize the config variable
371 $this->load_ini();
372 // Set the key
373 $this->config[ $key ] = $value;
374 // Write the INI file
375 return Ini::write_ini( $this->config, $this->get_config_file() );
376 }
377
378 /**
379 * Reads a value from a config file
380 *
381 * @since 1.0.0
382 * @see Ini INI functionality
383 * @throws \Alphred\ConfigKeyNotSet when the key is not set
384 *
385 * @param string $key the key to read
386 * @return mixed the value of the key
387 */
388 private function read_ini( $key ) {
389 // Re-initialize the config variable
390 $this->config = Ini::read_ini( $this->get_config_file() );
391 if ( isset( $this->config[ $key ] ) ) {
392 return $this->config[ $key ];
393 }
394 // If the config key is not sent, throw an exception rather than returning false
395 throw new ConfigKeyNotSet( "`{$key}` is not set.", 1 );
396 }
397
398 /**
399 * Unsets a config
400 *
401 * @since 1.0.0
402 * @see Ini INI functionality
403 *
404 * @param string $key the key to unset
405 * @return boolean true if the key existed; false if the key did not exist
406 */
407 private function unset_ini( $key ) {
408 // Re-initialize the config variable
409 $this->config = Ini::read_ini( $this->get_config_file() );
410 if ( isset( $this->config[ $key ] ) ) {
411 unset( $this->config[ $key ] );
412 Ini::write_ini( $this->config, $this->get_config_file() );
413 return true;
414 }
415 return false;
416 }
417
418 }
419
420
421