Issues

ZF2-6: Zend_Config_Xml: allow extending for lower levels

Description

It would be great if it is possible to define extends in lower xml structure levels. Take a look at this xml structure below.

sliding7510

I am not sure what to do with {{staging.second-application.entries-per-page}}. Should it use {{entries-per-page=5}} or {{entries-per-page=10}}?

I think this improvement will need a complete refactoring of Zend_Config(_Xml).

Comments

corrected xml example.

This evening I tried to solve this issue and I think I´ve found a good way. I used this modified xml structure:

sliding75jumping10

My Zend_Config_Xml::__construct() is:

public function __construct($filename, $section = null, $allowModifications = false)
{
    if (empty($filename)) {
        require_once 'Zend/Config/Exception.php';
        throw new Zend_Config_Exception('Filename is not set');
    }
    
    set_error_handler(array($this, '_loadFileErrorHandler'));
    $config = simplexml_load_file($filename); // Warnings and errors are suppressed
    restore_error_handler();
    
    // Check if there was a error while loading file
    if ($this->_loadFileErrorStr !== null) {
        require_once 'Zend/Config/Exception.php';
        throw new Zend_Config_Exception($this->_loadFileErrorStr);
    }
    
    $sections = array();
    if (is_array($section)) {
        $sections = $section;
    } elseif ($section !== null) {
        if (!isset($config->$section)) {
            require_once 'Zend/Config/Exception.php';
            throw new Zend_Config_Exception("Section '$section' cannot be found in $filename");
        }
        $sections = array($section);
    }
    
    parent::__construct(
        $this->_processExtendsRecursive($config, $sections),
        $allowModifications
    );

    $this->_loadedSection = $section;
}

And I replaced Zend_Config_Xml::_processExtends() with new Zend_Config_Xml::_processExtendsRecursive():

protected function _processExtendsRecursive(SimpleXMLElement $element,
                                            array $sections = array(),
                                            $depth = 0)
{
    $config = array();
    foreach ($element->children() as $children)
    {
        $childs = $children->children();
        $config[$elementName = $children->getName()] = empty($childs)
            ? $this->_toArray($children)
            : $this->_processExtendsRecursive($children, array(), $depth+1);
        
        $attributes = $children->attributes();
        foreach ($attributes as $attribute => $value)
        {
            if ($attribute !== 'extends') {
                continue;
            }
            
            $extendedSection = (string)$value;
            if (!array_key_exists($extendedSection, $config)) {
                require_once 'Zend/Config/Exception.php';
                throw new Zend_Config_Exception("Section '$extendedSection' cannot be found");
            }
            
            $data = $config[$extendedSection];
            if (is_array($data) && is_array($config[$elementName])) {
                $config[$elementName] = $this->_arrayMergeRecursive(
                    $data, $config[$elementName]
                );
            } elseif (is_array($data)) {
                $config[$children->getName()] = $data;
            }
            
            if ($depth == 0) {
                $this->_assertValidExtend($elementName, $extendedSection);
            }
        }
    }
    
    if (!empty($sections)) {
        foreach ($config as $key => $value) {
            if (!in_array($key, $sections)) {
                unset($config[$key]);
            }
        }
    }
    
    if (empty($config)) {
        $config = (string)$element;
    }
    return $config;
}

Following script...

<?php

require_once 'Zend/Config/Xml.php';
$config = new Zend_Config_Xml('zf-5660.xml');

require_once 'Zend/Debug.php';
Zend_Debug::dump($config->toArray());

...will output this:

array(3) {
  ["production"] => array(2) {
    ["first-application"] => array(2) {
      ["paginator-type"] => string(7) "sliding"
      ["entries-per-page"] => string(1) "7"
    }
    ["second-application"] => array(2) {
      ["paginator-type"] => string(7) "sliding"
      ["entries-per-page"] => string(1) "5"
    }
  }
  ["staging"] => array(2) {
    ["first-application"] => array(2) {
      ["paginator-type"] => string(7) "jumping"
      ["entries-per-page"] => string(2) "10"
    }
    ["second-application"] => array(2) {
      ["paginator-type"] => string(7) "sliding"
      ["entries-per-page"] => string(1) "5"
    }
  }
  ["testing"] => array(2) {
    ["first-application"] => array(2) {
      ["paginator-type"] => string(7) "jumping"
      ["entries-per-page"] => string(2) "10"
    }
    ["second-application"] => array(2) {
      ["paginator-type"] => string(7) "sliding"
      ["entries-per-page"] => string(1) "5"
    }
  }
}

I also attached all these changes to Zend_Config_Xml to this issue: zf-5660.diff

Currently there is feature parity between XML and INI formats. How would this work with the INI file format?

Yeah I already thought about how to realize this improvement for INI files but I have no really good idea because INI files have only one real dimension. Using "." in INI files to allow multidimensional INI configurations is no standard I think. You can use it for all other applications without any problems but they do not know that you want to define a multidimensional configuration.

[foo]
foo.bar = test
<?php

parse_ini_file('example.ini', true);

will result in:

array(1) {
  ["foo"] =>
  array(1) {
    ["foo.bar"]=>
    string(4) "test"
  }
}

and not:

array(1) {
  ["foo"] =>
  array(1) {
    ["foo"] =>
    array(1) {
      ["bar"]=>
      string(4) "test"
    }
  }
}

Without complete redefinition of our INI format, I think it will be very tricky to allow the same functionality to INI files.

This feature cannot be implemented until 2.0 for the following reason: At the moment, the "extend" attribute is only reserved for the section-, but no deeper element. Thus, this feature would break BC, as users could still have used the "extend" attribute in they config files (short param syntax).

After talking with Matthew about it, we decided that this break (for XML is allowed).

Personally, I'm not convinced this is a good idea as it introduces potential confusion and additional complexity when writing config files for relatively little gain.

e.g.


one
        bartwo
    three
    baz

$config->app2->var now equals "three" rather than two as probably expected due to a typo.

However, having said that, if someone develops a patch with unit tests then if Matthew's happy for it to go in, then that's fine.

Reassigned to component maintainer

We'll look at this again when refactoring ZF2. I personally think that being able to load a file into another file will solve this one more cleanly with less user confusion.

Ben: Any thoughts for ZF2?

As we are not supporting inheritance in ZF2 anymore, this issue will be closed.