Zend Framework 2.0

iterating over a Zend_Config

Details

Description

There are two xml-files.

config1.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file id="index" location="index.html" />
        <file id="main_css" location="css/main.css" />
        <file id="main_js" location="js/main.js" />
    </files>
</root>

and

config2.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file id="index" location="index.html" />
    </files>
</root>

and the sample code:

index.php
<?php
    require_once 'Zend/Config.php';
    require_once 'Zend/Config/Xml.php';
    
    $config = new Zend_config_xml('/path/to/config.xml');
    foreach ($config->files->file as $value)
        echo 'id: '.$value->id.'<br />location: '.
            htmlspecialchars($value->location).'<hr />';
?>

If the /path/to/config.xml to substitute config1.xml, then everything works correctly. But if you substitute config2.xml error

Notice: Trying to get property of non-object in D:\www\index.php on line 7

How can I iterate all items file, if their number is not known in advance (one or more than one)?

Issue Links

Activity

Hide
Rob Allen added a comment -

Can't fix at Zend_Config level. Workaround is to use UTF8 files without a BOM marker.

Show
Rob Allen added a comment - Can't fix at Zend_Config level. Workaround is to use UTF8 files without a BOM marker.
Hide
Rob Allen added a comment -

(Closed wrong issue!)

Show
Rob Allen added a comment - (Closed wrong issue!)
Hide
Rob Allen added a comment -

I'm not sure there's any way to resolve this issue due to the other than creating a dummy key to force an array:

<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file id="index" location="index.html" />
        <file />
    </files>
</root>

might work, but don't forget to test for id being null.

Show
Rob Allen added a comment - I'm not sure there's any way to resolve this issue due to the other than creating a dummy key to force an array:
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file id="index" location="index.html" />
        <file />
    </files>
</root>
might work, but don't forget to test for id being null.
Hide
Patrick van Dissel added a comment -

This issue also exists when using the toArray() method of Zend_Config.
The problem is that you just want the array to ALWAYS have the same layout.
Config files tent to grow and be extended, just like XML files.

In my opinion the Zend_Config API should work the same as the of SimpleXml, here two extended examples and their output:

Using SimpleXML

Code
<?php
// Files with single file element
$xmlString1 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
    </files>
</root>
XML;

// Files with multiple file elements
$xmlString2 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
        <file>
            <id>index2</id>
            <location>index2.html</location>
        </file>
    </files>
</root>
XML;

echo "Files with single file element, looping files->file:\n";
$xml1 = new SimpleXMLElement($xmlString1);
foreach ($xml1->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with single file element, looping files->file->id:\n";
foreach ($xml1->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}

echo "Files with multiple file elements, looping files->file:\n";
$xml2 = new SimpleXMLElement($xmlString2);
foreach ($xml2->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with multiple file elements, looping files->file->id:\n";
foreach ($xml2->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}
Output of code
Files with single file element, looping files->file:
object(SimpleXMLElement)#3 (2) {
  ["id"]=>
  string(5) "index"
  ["location"]=>
  string(10) "index.html"
}
id: index
localtion: index.html
Files with single file element, looping files->file->id:
object(SimpleXMLElement)#2 (1) {
  [0]=>
  string(5) "index"
}
id: index
Files with multiple file elements, looping files->file:
object(SimpleXMLElement)#6 (2) {
  ["id"]=>
  string(5) "index"
  ["location"]=>
  string(10) "index.html"
}
id: index
localtion: index.html
object(SimpleXMLElement)#3 (2) {
  ["id"]=>
  string(6) "index2"
  ["location"]=>
  string(11) "index2.html"
}
id: index2
localtion: index2.html
Files with multiple file elements, looping files->file->id:
object(SimpleXMLElement)#4 (1) {
  [0]=>
  string(5) "index"
}
id: index

Using Zend_Config_Xml

possibly a global Zend_Config or other Zend_Config* types issue, I have not tested that_

Code
<?php
set_include_path(
    get_include_path()
    . PATH_SEPARATOR . 'library/'
);

require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance()->setFallbackAutoloader(true);

// Files with single file element
$xmlString1 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
    </files>
</root>
XML;

// Files with multiple file elements
$xmlString2 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
        <file>
            <id>index2</id>
            <location>index2.html</location>
        </file>
    </files>
</root>
XML;

echo "Files with single file element, looping files->file:\n";
$xml1 = new Zend_Config_Xml($xmlString1);
foreach ($xml1->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with single file element, looping files->file->id:\n";
foreach ($xml1->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}

echo "Files with multiple file elements, looping files->file:\n";
$xml2 = new Zend_Config_Xml($xmlString2);
foreach ($xml2->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with multiple file elements, looping files->file->id:\n";
foreach ($xml2->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}
Output of code
Files with single file element, looping files->file:
string(5) "index"
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>44</b><br />
id: 
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>45</b><br />

localtion: 
string(10) "index.html"
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>44</b><br />
id: 
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>45</b><br />

localtion: 
Files with single file element, looping files->file->id:
<br />
<b>Warning</b>:  Invalid argument supplied for foreach() in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>48</b><br />
Files with multiple file elements, looping files->file:
object(Zend_Config)#9 (8) {
  ["_allowModifications:protected"]=>
  bool(false)
  ["_index:protected"]=>
  int(0)
  ["_count:protected"]=>
  int(2)
  ["_data:protected"]=>
  array(2) {
    ["id"]=>
    string(5) "index"
    ["location"]=>
    string(10) "index.html"
  }
  ["_skipNextIteration:protected"]=>
  NULL
  ["_loadedSection:protected"]=>
  NULL
  ["_extends:protected"]=>
  array(0) {
  }
  ["_loadFileErrorStr:protected"]=>
  NULL
}
id: index
localtion: index.html
object(Zend_Config)#11 (8) {
  ["_allowModifications:protected"]=>
  bool(false)
  ["_index:protected"]=>
  int(0)
  ["_count:protected"]=>
  int(2)
  ["_data:protected"]=>
  array(2) {
    ["id"]=>
    string(6) "index2"
    ["location"]=>
    string(11) "index2.html"
  }
  ["_skipNextIteration:protected"]=>
  NULL
  ["_loadedSection:protected"]=>
  NULL
  ["_extends:protected"]=>
  array(0) {
  }
  ["_loadFileErrorStr:protected"]=>
  NULL
}
id: index2
localtion: index2.html
Files with multiple file elements, looping files->file->id:
<br />
<b>Warning</b>:  Invalid argument supplied for foreach() in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>61</b><br />

note: Output rendered with PHP in error_reporting mode = E_ALL | E_STRICT

Show
Patrick van Dissel added a comment - This issue also exists when using the toArray() method of Zend_Config. The problem is that you just want the array to ALWAYS have the same layout. Config files tent to grow and be extended, just like XML files. In my opinion the Zend_Config API should work the same as the of SimpleXml, here two extended examples and their output:

Using SimpleXML

Code
<?php
// Files with single file element
$xmlString1 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
    </files>
</root>
XML;

// Files with multiple file elements
$xmlString2 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
        <file>
            <id>index2</id>
            <location>index2.html</location>
        </file>
    </files>
</root>
XML;

echo "Files with single file element, looping files->file:\n";
$xml1 = new SimpleXMLElement($xmlString1);
foreach ($xml1->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with single file element, looping files->file->id:\n";
foreach ($xml1->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}

echo "Files with multiple file elements, looping files->file:\n";
$xml2 = new SimpleXMLElement($xmlString2);
foreach ($xml2->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with multiple file elements, looping files->file->id:\n";
foreach ($xml2->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}
Output of code
Files with single file element, looping files->file:
object(SimpleXMLElement)#3 (2) {
  ["id"]=>
  string(5) "index"
  ["location"]=>
  string(10) "index.html"
}
id: index
localtion: index.html
Files with single file element, looping files->file->id:
object(SimpleXMLElement)#2 (1) {
  [0]=>
  string(5) "index"
}
id: index
Files with multiple file elements, looping files->file:
object(SimpleXMLElement)#6 (2) {
  ["id"]=>
  string(5) "index"
  ["location"]=>
  string(10) "index.html"
}
id: index
localtion: index.html
object(SimpleXMLElement)#3 (2) {
  ["id"]=>
  string(6) "index2"
  ["location"]=>
  string(11) "index2.html"
}
id: index2
localtion: index2.html
Files with multiple file elements, looping files->file->id:
object(SimpleXMLElement)#4 (1) {
  [0]=>
  string(5) "index"
}
id: index

Using Zend_Config_Xml

possibly a global Zend_Config or other Zend_Config* types issue, I have not tested that_
Code
<?php
set_include_path(
    get_include_path()
    . PATH_SEPARATOR . 'library/'
);

require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance()->setFallbackAutoloader(true);

// Files with single file element
$xmlString1 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
    </files>
</root>
XML;

// Files with multiple file elements
$xmlString2 = <<<XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <files>
        <file>
            <id>index</id>
            <location>index.html</location>
        </file>
        <file>
            <id>index2</id>
            <location>index2.html</location>
        </file>
    </files>
</root>
XML;

echo "Files with single file element, looping files->file:\n";
$xml1 = new Zend_Config_Xml($xmlString1);
foreach ($xml1->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with single file element, looping files->file->id:\n";
foreach ($xml1->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}

echo "Files with multiple file elements, looping files->file:\n";
$xml2 = new Zend_Config_Xml($xmlString2);
foreach ($xml2->files->file as $file) {
    var_dump($file);
    printf("%s: %s\n", 'id', $file->id);
    printf("%s: %s\n", 'localtion', $file->location);
}
echo "Files with multiple file elements, looping files->file->id:\n";
foreach ($xml2->files->file->id as $id) {
    var_dump($id);
    printf("%s: %s\n", 'id', $id);
}
Output of code
Files with single file element, looping files->file:
string(5) "index"
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>44</b><br />
id: 
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>45</b><br />

localtion: 
string(10) "index.html"
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>44</b><br />
id: 
<br />
<b>Notice</b>:  Trying to get property of non-object in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>45</b><br />

localtion: 
Files with single file element, looping files->file->id:
<br />
<b>Warning</b>:  Invalid argument supplied for foreach() in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>48</b><br />
Files with multiple file elements, looping files->file:
object(Zend_Config)#9 (8) {
  ["_allowModifications:protected"]=>
  bool(false)
  ["_index:protected"]=>
  int(0)
  ["_count:protected"]=>
  int(2)
  ["_data:protected"]=>
  array(2) {
    ["id"]=>
    string(5) "index"
    ["location"]=>
    string(10) "index.html"
  }
  ["_skipNextIteration:protected"]=>
  NULL
  ["_loadedSection:protected"]=>
  NULL
  ["_extends:protected"]=>
  array(0) {
  }
  ["_loadFileErrorStr:protected"]=>
  NULL
}
id: index
localtion: index.html
object(Zend_Config)#11 (8) {
  ["_allowModifications:protected"]=>
  bool(false)
  ["_index:protected"]=>
  int(0)
  ["_count:protected"]=>
  int(2)
  ["_data:protected"]=>
  array(2) {
    ["id"]=>
    string(6) "index2"
    ["location"]=>
    string(11) "index2.html"
  }
  ["_skipNextIteration:protected"]=>
  NULL
  ["_loadedSection:protected"]=>
  NULL
  ["_extends:protected"]=>
  array(0) {
  }
  ["_loadFileErrorStr:protected"]=>
  NULL
}
id: index2
localtion: index2.html
Files with multiple file elements, looping files->file->id:
<br />
<b>Warning</b>:  Invalid argument supplied for foreach() in <b>D:\www\htdocs\dev\config\config.php</b> on line <b>61</b><br />
note: Output rendered with PHP in error_reporting mode = E_ALL | E_STRICT
Hide
Rob Allen added a comment -

I'm open to ideas on how to solve this without breaking backwards compatibility.

Show
Rob Allen added a comment - I'm open to ideas on how to solve this without breaking backwards compatibility.
Hide
Ben Scholzen added a comment -

You could just do:

$files = (isset($files->file[0]) ? $files->file : array($files->file));

And then just work with the $files variable as array. That works pretty well.

Show
Ben Scholzen added a comment - You could just do:
$files = (isset($files->file[0]) ? $files->file : array($files->file));
And then just work with the $files variable as array. That works pretty well.
Hide
Wim Godden added a comment -

Although this is fixable, fixing it will always break backwards compatibility.
When a developer knows there's only 1 item, code such as this will be broken :

echo $config->files->file['id'];
{/code}

Because it will in fact have to become :

$file = $config->files->file->toArray();
echo $file[0]['id'];

{/code}

I would advise against modifying this for ZF 1.x - not sure how this will be handled in ZF 2, but introducing this backwards incompatibility is not a good idea in any case...

The only other option is to modify just the magic/fluent notation, but that will make things complicated and will cause inconsistency between fluent notation and array notation, which should be avoided.

Show
Wim Godden added a comment - Although this is fixable, fixing it will always break backwards compatibility. When a developer knows there's only 1 item, code such as this will be broken :
echo $config->files->file['id'];
{/code}

Because it will in fact have to become :
$file = $config->files->file->toArray(); echo $file[0]['id']; {/code} I would advise against modifying this for ZF 1.x - not sure how this will be handled in ZF 2, but introducing this backwards incompatibility is not a good idea in any case... The only other option is to modify just the magic/fluent notation, but that will make things complicated and will cause inconsistency between fluent notation and array notation, which should be avoided.
Hide
Rob Allen added a comment -

One idea I have had is to introduce a children() method that always returns an iterator/array regardless of whether there is is one or many children:

$files = $config->files->children();
foreach ($files as $file) {
    echo $file->id;
}

Thoughts on this approach?

Show
Rob Allen added a comment - One idea I have had is to introduce a children() method that always returns an iterator/array regardless of whether there is is one or many children:
$files = $config->files->children();
foreach ($files as $file) {
    echo $file->id;
}
Thoughts on this approach?
Hide
Rob Allen added a comment -

Moved to ZF2 as likely to involve a BC break.

Show
Rob Allen added a comment - Moved to ZF2 as likely to involve a BC break.

People

Vote (5)
Watch (5)

Dates

  • Created:
    Updated: