Alphred
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  • Download

Namespaces

  • Alphred
  • None

Classes

  • Alphred
  1 <?php
  2 /**
  3  * Contains Ini 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 /**
 22  * Extends INI parsing and writing for PHP
 23  *
 24  * This class allows to read and write `ini` files. It translates `ini` files into
 25  * associative PHP arrays and translates PHP arrays into `ini` files. It supports
 26  * sectioning as well as a kind of subsectioning.
 27  *
 28  * Colons (`:`) are considered separators for sub-sections and are represented
 29  * as multi-dimensional arrays. For instance, the following array:
 30  * ````php
 31  * $array = [
 32  *  'Alphred' => [
 33  *  'log_level' => 'DEBUG',
 34  *  'log_size' => 10000,
 35  *  'plugins'  => [ 'get_password' => 'my_new_function' ]
 36  * ]];
 37  * ````
 38  * will be represented as
 39  * ````ini
 40  * [Alphred]
 41  * log_level = DEBUG
 42  * log_size = 10000
 43  *
 44  * [Alphred:plugins]
 45  * get_password = my_new_function
 46  * ````
 47  *
 48  * If you are concerned, then make sure that `\r\n` is removed from the array values
 49  * before they move into the INI file, as they may break them.
 50  *
 51  * All of these are static functions. So, to use:
 52  * ````php
 53  * $ini_file = Alphred\Ini::read_ini( '/path/to/workflow.ini' );
 54  * ````
 55  * That's it.
 56  *
 57  * To write an `ini` file, just do:
 58  * ````php
 59  * Alphred\Ini::write_ini( $config_array, '/path/to/workflow.ini' );
 60  * ````
 61  *
 62  * @since 1.0.0
 63  *
 64  */
 65 class Ini {
 66 
 67     /**
 68      * Parses an INI
 69      *
 70      * This is a slightly better INI parser in that will read a section title of
 71      * 'title:subtitle' 'subtitle' as a subsection of the section 'title'.
 72      *
 73      * @since 1.0.0
 74      *
 75      * @param  string  $file      path to the ini file to read
 76      * @param  boolean $exception whether or not to throw an exception on file not found
 77      * @return array|boolean      an array that represents the ini file
 78      */
 79     public function read_ini( $file, $exception = true ) {
 80         if ( ! file_exists( $file ) ) {
 81             if ( $exception ) {
 82                 throw new FileDoesNotExist( "File `{$file}` not found." );
 83             } else {
 84                 return false;
 85             }
 86         }
 87 
 88         // Parse the INI files
 89         $ini = parse_ini_file( $file, true );
 90         $array = [];
 91         foreach( $ini as $key => $value ) :
 92             if ( is_array( $value ) ) {
 93                 $array = array_merge_recursive( $array, self::parse_section( $key, $value ) );
 94             } else {
 95                 $array[ $key ] = $value;
 96                 // array_unshift( $array, [ $key => $value ] );
 97             }
 98         endforeach;
 99 
100         return $array;
101 
102     }
103 
104     /**
105      * Writes an INI file from an array
106      *
107      * @since 1.0.0
108      * @todo Do filesystem checks
109      *
110      * @param  array  $array  the array to be translated into an ini file
111      * @param  string $file  the full path to the ini file, should have '.ini'
112      */
113     public function write_ini( $array, $file ) {
114         // Collapse the arrays into writeable sections
115         $sections = self::collapse_sections( $array );
116         // Separate out the things that need to be in the global space from the things
117         // that need to be in sectioned spaces
118         $sections = self::separate_non_sections( $sections );
119         $global = $sections[0];
120         $sections = $sections[1];
121 
122         // sort the sections
123         ksort( $sections );
124 
125         $base = basename( $file );
126 
127       // Write a header
128         $contents = ";;;;;\r\n";
129         $contents .= "; `{$base}` generated by Alphred v" . ALPHRED_VERSION . "\r\n";
130         $contents .= "; at " . date( 'Y-M-d H:i:s', time() ) . "\r\n";
131         $contents .= ";;;;;\r\n\r\n";
132 
133         // Write things in the global space first
134         foreach( $global as $value ) :
135             // There should really be only one item in each array, but this is easy
136             foreach ( $value as $k => $v ) :
137                 $contents .= "{$k} = {$v}\r\n";
138             endforeach;
139         endforeach;
140 
141         // Now write out the sections
142         foreach ( $sections as $title => $section ) :
143 
144             // Print the section
145             if ( is_array( $section ) ) {
146                 if ( ! is_integer( $title ) ) {
147                     $contents .= "\n[$title]\n";
148                 }
149                 $contents .= self::print_section( $section );
150             } else {
151                 // Okay, the names are a bit weird here. This is
152                 // actually key => value rather than title => section
153                 // This is actually a deprecated part now, and we should
154                 // never quite get here.
155                 $contents .= "{$title} = {$section}\r\n";
156             }
157 
158         endforeach;
159 
160         file_put_contents( $file, $contents );
161     }
162 
163     /**
164      * Separates out bits from the global space and from sections
165      *
166      * @param  array $array array of values to write to an ini file
167      * @return array        a sorted array
168      */
169     private function separate_non_sections( $array ) {
170         // Bad name for the method
171 
172         // The global space
173         $global = [];
174         // Sectioned space
175         $sections = [];
176         foreach ( $array as $key => $value ) :
177             if ( is_array( $value ) ) {
178                 // If it is an array, then we assume that it's a
179                 // section, so put it in the sections array
180                 $sections[ $key ] = $value;
181             } else {
182                 // If it's not an array, then we assume that it needs
183                 // to go in the global space, so put it in the global
184                 // array
185                 $global[] = [ $key => $value ];
186             }
187         endforeach;
188         // Return the sorted array
189         return [ $global, $sections ];
190 
191     }
192 
193     /**
194      * Prints the section of an INI file
195      *
196      * @since 1.0.0
197      *
198      * @param  array $section  an array
199      * @return string          the array as an ini section
200      */
201     private function print_section( $section ) {
202         $contents = '';
203         foreach( $section as $key => $value ) :
204             if ( is_array( $value ) ) {
205                 foreach( $value as $v ) :
206                         $contents .= "{$key}[] = {$v}\r\n";
207                 endforeach;
208             } else {
209                 $contents .= "{$key} = {$value}\r\n";
210             }
211         endforeach;
212         return $contents;
213     }
214 
215     /**
216      * Collapses arrays into something that can be written in the ini
217      *
218      * @since 1.0.0
219      *
220      * @param  array $array the array to be collapsed
221      * @return array        the collapsed array
222      */
223     private function collapse_sections( $array ) {
224         return self::step_back( self::flatten_array( $array ) );
225     }
226 
227     /**
228      * Flattens an associate array
229      *
230      * @since 1.0.0
231      * @todo Better tests for numeric keys
232      *
233      * @param  array $array    an array to be flattened
234      * @param  string $prefix  a prefix for a key
235      * @return array           the array, but flattened
236      */
237     private function flatten_array( $array, $prefix = '' ) {
238             if ( ! is_array( $array ) ) {
239                 return $array;
240             }
241             if ( ! self::is_assoc( $array ) ) {
242                 return $array;
243             }
244 
245         $result = [];
246 
247         foreach ( $array as $key => $value ) :
248 
249             $new_key = $prefix . ( empty( $prefix ) ? '' : ':') . $key;
250 
251             if ( is_integer( $key ) ) {
252                 // Don't compound numeric keys; the assumption is that a numeric key will contain only
253                 // one array. @todo test this further
254                 foreach ( $value as $k => $v ) :
255                     $result[ $k ] = $v;
256                         endforeach;
257             } else if ( is_array( $value ) && self::is_assoc( $value ) ) {
258                 $result = array_merge( $result, self::flatten_array( $value, $new_key ) );
259             } else {
260                 $result[ $new_key ] = $value;
261             }
262         endforeach;
263 
264         return $result;
265     }
266 
267     /**
268      * Slightly unflattens an array
269      *
270      * So, flatten_array goes one step too far with the flattening, but I
271      * don't know how many levels down I need to flatten (2, 97?), so we just flatten
272      * all the way and then step back one level, which is what this function does.
273      *
274      * @since 1.0.0
275      *
276      * @param  array $array a flattened array
277      * @return array        a slightly less flat array
278      */
279     private function step_back( $array ) {
280         $new = [];
281         foreach( $array as $key => $value ) :
282           if ( substr_count( $key, ':' ) >= 1 ) {
283                 $pos = strrpos( $key, ':' );
284                 $section = substr( $key, 0, $pos );
285                 $new_key = substr( $key, $pos + 1 );
286                 $new[ $section ][ $new_key ] = $value;
287             } else {
288                 $new[ $key ] = $value;
289             }
290         endforeach;
291         return $new;
292     }
293 
294 
295     /**
296      * Parses an ini section into its subsections
297      *
298      * @since 1.0.0
299      *
300      * @param  string $name   a string that should be turned into an array
301      * @param  mixed $values  the values for an array
302      * @return array          the newly-dimensional array with $values
303      */
304     private function parse_section( $name, $values ) {
305         if ( false !== strpos( $name, ':' ) ) {
306             $pieces = explode( ':', $name );
307             $pieces = array_filter( $pieces, 'trim' );
308         } else {
309             return [ $name => $values ];
310         }
311         return self::nest_array( $pieces, $values );
312     }
313 
314     /**
315      * Recursively nests an array
316      *
317      * @since 1.0.0
318      *
319      * @param  array $array   the pieces to nest
320      * @param  mixed $values  the values for the bottom level of the newly dimensional array
321      * @return array          a slightly more dimensional array than we received
322      */
323     private function nest_array( $array, $values ) {
324         if ( empty( $array ) ) {
325             return $values;
326         }
327         return [ array_shift( $array ) => self::nest_array( $array, $values ) ];
328     }
329 
330     /**
331      * Checks if an array is associative
332      *
333      * Shamelessly stolen from http://stackoverflow.com/a/14669600/1399574
334      *
335      * @since 1.0.0
336      *
337      * @param  array    $array an array
338      * @return boolean         whether it is associative
339      */
340     private function is_assoc( $array ) {
341         // Keys of the array
342         $keys = array_keys( $array );
343 
344         // If the array keys of the keys match the keys, then the array must
345         // not be associative (e.g. the keys array looked like {0:0, 1:1...}).
346         return array_keys( $keys ) !== $keys;
347     }
348 }
349 
Alphred API documentation generated by ApiGen