Parsing an "advanced" INI file with PHP
Basically, I would like a simple, easy, one-file way to parse an INI file with "advanced" features, like section inheritance and property nesting, like Zend_Config_Ini.
For example:
[foo]
a = 1
b.a = 2
b.b = 3
b.c = 4
c = 5
[bar : foo]
b.b = 17
c = 42
Would parse into
array(
'foo'=>array(
'a'=>'1',
'b'=>array(
'a'=>'2',
'b'=>'3',
'c'=>'4'
),
'c'=>'5'
),
'bar'=>array(
'a'=>'1',
'b'=>array(开发者_JS百科
'a'=>'2',
'b'=>'17',
'c'=>'4'
),
'c'=>'42'
)
)
PHP's built-in parse_ini_file
, doesn't handle anything other than simple INI's with simple sections and simple keys.
My problem with using Zend_Config_Ini
is that I would have to include virtually the whole Zend_Config subpackage, and is super-bloated and configurable.
Is there a small and simple library available to parse this? If not, is there an easy implementation I'm not seeing?
By small and simple, I mean something like the sfYaml of INI files.
To my (very inexperienced) eyes, I would have to parse through once with parse_ini_file
, then come back and resolve inheritance, then run through each section and expand the keys recursively...
UPDATE: Since this seems to be a popular question, I would like to note that I have a simple class implementing this on GitHub, feel free to send pull requests, issues, etc.
Not sure if I should edit my old answer or add a new one.
Try this version of it, should be what you're looking for.
function parse_ini_advanced($array) {
$returnArray = array();
if (is_array($array)) {
foreach ($array as $key => $value) {
$e = explode(':', $key);
if (!empty($e[1])) {
$x = array();
foreach ($e as $tk => $tv) {
$x[$tk] = trim($tv);
}
$x = array_reverse($x, true);
foreach ($x as $k => $v) {
$c = $x[0];
if (empty($returnArray[$c])) {
$returnArray[$c] = array();
}
if (isset($returnArray[$x[1]])) {
$returnArray[$c] = array_merge($returnArray[$c], $returnArray[$x[1]]);
}
if ($k === 0) {
$returnArray[$c] = array_merge($returnArray[$c], $array[$key]);
}
}
} else {
$returnArray[$key] = $array[$key];
}
}
}
return $returnArray;
}
function recursive_parse($array)
{
$returnArray = array();
if (is_array($array)) {
foreach ($array as $key => $value) {
if (is_array($value)) {
$array[$key] = recursive_parse($value);
}
$x = explode('.', $key);
if (!empty($x[1])) {
$x = array_reverse($x, true);
if (isset($returnArray[$key])) {
unset($returnArray[$key]);
}
if (!isset($returnArray[$x[0]])) {
$returnArray[$x[0]] = array();
}
$first = true;
foreach ($x as $k => $v) {
if ($first === true) {
$b = $array[$key];
$first = false;
}
$b = array($v => $b);
}
$returnArray[$x[0]] = array_merge_recursive($returnArray[$x[0]], $b[$x[0]]);
} else {
$returnArray[$key] = $array[$key];
}
}
}
return $returnArray;
}
Would be called like this:
$array = parse_ini_file('test.ini', true);
$array = recursive_parse(parse_ini_advanced($array));
This could be done a lot better/clearer but for a simple solution it should work just fine.
If your config is:
[foo]
a = 1
b.a = 2
b.b = 3
b.c = 4
c = 5
[bar : foo]
b.x.c = 33
b.b = 17
c = 42
[hot : bar : foo]
b.a = 83
b.d = 23
The output should be:
Array
(
[foo] => Array
(
[a] => 1
[b] => Array
(
[a] => 2
[b] => 3
[c] => 4
)
[c] => 5
)
[bar] => Array
(
[a] => 1
[b] => Array
(
[a] => 2
[b] => 17
[c] => 4
[x] => Array
(
[c] => 33
)
)
[c] => 42
)
[hot] => Array
(
[a] => 1
[b] => Array
(
[a] => 83
[b] => 17
[c] => 4
[x] => Array
(
[c] => 33
)
[d] => 23
)
[c] => 42
)
)
First to answer one thing, property nesting is avilable from parse_ini_file(), set the second param to true i.e parse_ini_file('test.ini', true); That will give you a multidimensional array i.e
Array
(
[foo] => Array
(
[a] => 1
[b.a] => 2
[b.b] => 3
[b.c] => 4
[c] => 5
)
[bar : foo] => Array
(
[b.b] => 17
[c] => 42
)
)
Here is a small function that will parse the array returned by parse_ini_file() and turn it into categories.
/**
* Parse INI files Advanced
* process_sections = true
* scanner_mode = default
*
* Supports section inheritance
* and has property nesting turned on
*
* @param string $filename
* return array
*/
function parse_ini_file_advanced($filename) {
$array = parse_ini_file($filename, true);
$returnArray = array();
if (is_array($array)) {
foreach ($array as $key => $value) {
$x = explode(':', $key);
if (!empty($x[1])) {
$x = array_reverse($x, true);
foreach ($x as $k => $v) {
$i = trim($x[0]);
$v = trim($v);
if (empty($returnArray[$i])) {
$returnArray[$i] = array();
}
if (isset($array[$v])) {
$returnArray[$i] = array_merge($returnArray[$i], $array[$v]);
}
if ($k === 0) {
$returnArray[$i] = array_merge($returnArray[$i], $array[$key]);
}
}
} else {
$returnArray[$key] = $array[$key];
}
}
} else {
return false;
}
return $returnArray;
}
It will return this:
Array
(
[foo] => Array
(
[a] => 1
[b.a] => 2
[b.b] => 3
[b.c] => 4
[c] => 5
)
[bar] => Array
(
[a] => 1
[b.a] => 2
[b.b] => 17
[b.c] => 4
[c] => 42
)
)
Last write wins i.e
[bar2 : foo2 : bar : foo]
bar2 wins with it's settings in it's own array
NOTE: the other 3 arrays WILL be there up to that point.
Hope this was what you were looking for.
I've wrote something like this and for now it's working ok for me:
$config = array();
$configSrc = parse_ini_file( $filePath, true );
foreach( $configSrc as $sectionName => $section )
{
$config[$sectionName] = array();
foreach( $section as $itemName => $item )
{
$itemNameArray = explode( '.', $itemName );
eval( sprintf('$config[$sectionName][\'%s\'] = $item;', join("']['", $itemNameArray)) );
}
}
// marge inheritance;
foreach( $config as $sectionName => $section )
{
$ancestryArray = explode( ':', $sectionName );
$ancestryCount = count( $ancestryArray );
if( $ancestryCount > 1 )
{ //
$config[$sectionNameTrimmed = trim($ancestryArray[0])] = array();
$ancestryArray = array_reverse( array_slice($ancestryArray, 1) );
foreach( $ancestryArray as $ancestryName )
{
$ancestryName = trim( $ancestryName );
if( isset($config[$ancestryName]) ) {
$config[$sectionNameTrimmed] = array_replace_recursive( $config[$sectionNameTrimmed], $config[$ancestryName] );
}
}
$config[$sectionNameTrimmed] = array_replace_recursive( $config[$sectionNameTrimmed], $section );
unset( $config[$sectionName] );
}
}
Another option - this is the pair, build and parse.
function build_ini_string_nested( $data, $path = null ){
$content = array();
foreach( $data AS $key => $val ){
if( is_array($val) ){
$content[] = build_ini_string_nested( $val, ($path ? $path. '.' : '') . $key );
}
else if( $path ) {
$content[] = $path . '[' . ($path && is_numeric($key) ? '' : $key) . '] = ' . $val;
}
else {
$content[] = $key . ' = ' . $val;
}
}
return implode("\n", $content);
}
function parse_ini_string_nested( $data, $path = null ){
if( is_string($data) )
$data = parse_ini_string($data);
if( $path )
foreach( $data AS $key => $val ){
if( strpos( $key, $path.'.' ) !== false ){
$find_node = explode('.', $path);
$this_path = reset(explode('.', substr($key, strlen($path.'.'))));
$node =& $data;
do {
$node =& $node[ array_shift($find_node) ];
} while( count($find_node) );
if( is_array($node[ $this_path ]) ){
$node[ $this_path ][] = $val;
}
else {
$node[ $this_path ] = $val;
}
}
}
else {
$drop_keys = array();
foreach( $data AS $key => $val ){
if( count(explode('.', $key)) > 1 ){
$path = reset(explode('.', $key));
$data[ $path ] = array();
$data = parse_ini_string_nested( $data, $path );
$drop_keys[] = $key;
}
}
foreach( $drop_keys AS $key ){
unset($data[ $key ]);
}
}
return $data;
}
精彩评论