PHP Unset acting like a Reference - php

I am having trouble understanding why PHP is unsetting my children property for both objects even when I create a copy.
When I assign $singleNode = $node it shouldn't remove the singleNode child because I'm not passing a reference, but it's behaving that way.
Can anyone clear this up for me?
You can run this in PHP CLI to see what I mean
<?php
$node = new stdClass();
$node->title = 'Test';
$node->children = [1,2,3,4,5];
// Does the node have children?
if (property_exists($node, 'children')) {
echo '$node has children' . PHP_EOL;
} else {
echo '$node NOT has children' . PHP_EOL;
}
// Assign node to a new variable, and remove children
$singleNode = $node;
if (property_exists($singleNode, 'children')) {
echo '$singleNode removed children' . PHP_EOL;
unset($singleNode->children);
}
// Does the node have children?
if (property_exists($node, 'children')) {
echo '$node has children' . PHP_EOL;
} else {
echo '$node NOT has children' . PHP_EOL;
}
I found I can do this:
$singleNode = clone $node
Is that the right way to do this? Why does this happen? No matter what I assign the variable to, the variable is referencing the same item in memory?

You are having just one object. To get a second object you would have to create a clone. Technically $singleNode = $node is copying the oject handle which still refers to the same object.
See
http://php.net/manual/en/language.oop5.cloning.php and http://php.net/manual/en/language.oop5.references.php

Related

PHP array_push() 2-dimensional array

array_push($typedict[$current], "value");
Does not seem to do anything here, i output the associative array of arrays($typedict) but all of them are empty(array()). I print the current associative index out with echo to confirm that it is the correct one(it always is).
Since the print chelc at the end states "[name] => Array()" i have no clue what could be the problem as this indicates that they are indeed arrays and therefore could have stuff pushed in. Also the var $current as stated always have the correct content. at:
echo "current: ". $current;
full code:
<?php
$typedict = array();
$xsdstring = file_get_contents("infile.xsd");
$xsdstring = str_replace("xs:choice", "xs:sequence", $xsdstring);
$doc = new DOMDocument();
$doc->loadXML(mb_convert_encoding($xsdstring, 'utf-8', mb_detect_encoding($xsdstring)));
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
$xpath->registerNamespace('vc', 'http://www.w3.org/2007/XMLSchema-versioning');
function outputFormat($indent, $elementDef)
{
echo "<div>" . $indent . $elementDef->getAttribute('name')
. " type:" . $elementDef->getAttribute('type')
. " min:" . $elementDef->getAttribute('minOccurs')
. " max:" . $elementDef->getAttribute('maxOccurs')
. "</div>\n";
}
function echoElements($indent = "", $elementDef, $evaluate, &$typedict)
{
global $doc, $xpath, $current;
if($indent == "")
{
$attribute_name = $elementDef->getAttribute('name');
$typedict[$attribute_name] = array();
$current = $attribute_name;
}else{
echo "current: ". $current;
$type = $elementDef->getAttribute('name');
array_push($typedict[$current], "value");
#$typedict[$current][0] = "value";
print_r($typedict[$current]);
}
outputFormat($indent, $elementDef);
$elementDefs = $xpath->evaluate($evaluate, $elementDef);
foreach($elementDefs as $elementDef)
{
echoElements($indent . " ", $elementDef, $evaluate);
}
}
$elementDefs = $xpath->evaluate("/xs:schema/xs:element");
foreach($elementDefs as $elementDef)
{
echoElements("", $elementDef, "xs:complexType/xs:sequence/xs:element", $typedict);
}
$elementDefs = $xpath->evaluate("/xs:schema/xs:complexType");
foreach($elementDefs as $elementDef)
{
echoElements("", $elementDef, "xs:sequence/xs:element", $typedict);
}
print_r($typedict);
?>
$current does contain the correct value. As seen in the code i check it pretty much everytime it gets set. So im pretty confident about that part.
I do not want to change whatever is in $current as it behaves excactly as i want.
My goal is an array inside each entry of the associative array $typedict.
Example:
$typedict["whatever"][0] = "value";
$typedict["whatever"][1] = "value";
...
$current is the index of $typedict not any of it's contents. So in this case $current contains the string "whatever" and not "value". And this is how it should be.
Edit:
I think i figured out the problem. But i have no clue how this could happen and therefore can't fix it:
$typedict["whatever"] seems to be only visible inside the IF block since the last value is okay if chekcked inside the block.
I somehow need to declare the whole structure of $typedict as global. Not just the base array(the associative one) but alos all the arrays inside it. But i only get to know the keys later.
Edit2:
Definetly a visibilty problem:
changing:
global $doc, $xpath, $current;
to:
global $doc, $xpath, $current, $typedict;
solved it

How to pull a specific tag from a node in xml with PHP?

Here is where I set up basic variables, such as creating the new DomDoc and such as well as loading some of the Tags. This all works fine at the moment.
<?php
if (isset($_GET['edit'])&& $_GET['edit']=='delete' && isset($_GET['id'])&&!empty($_GET['id'])){
$dom = new DomDocument();
$dom->preserveWhiteSpace = false;
$dom->load("data.xml");
$root = $dom->documentElement;
$record = $root->getElementsByTagName("data");
$ID=$root->getElementsByTagName("ID");
$nodetoremove = null;
//$namenode=$root->getElementsByTagName("own_name");
//$name="";
//$datenode=$root->getElementsByTagName("sign_in");
//$date="";
$newid=$_GET['id'];
foreach($ID as $node){
$pid =$node->textContent;
Here I am checking if it's a new ID and if it is it does the following as seen.
if ($pid == $newid)
{
$nodetoremove=$node->parentNode;
}
}
The issue is here. I am able to go through the selected node I wish to delete ($nodetoremove) and select a specific element (sign_in) but I am unsure how to so. Right now all I can do is go through and print all of the elements within the nodes of $nodetoremove. Is there a way I can get the element I want from XML this way?
//Prints all information within $nodetoremove
foreach ($nodetoremove->childNodes AS $item){
print $item->nodeName . "=" . $item->nodeValue . "<br>";
}
foreach ($nodetoremove as $node) {
}
//Sets $name to the first Child of $nodetoremove
$name=$nodetoremove->firstChild->nodeValue;
//Checks if the nods to remove is not null, if it is removes $nodetoremove
if($nodetoremove!=null){
$root->removeChild($nodetoremove);
?>

DomNode get value of item

Hello I'm new with domnode and i'm trying to check the values from an xml tree which loads ok.
Here is my code but I dont understand why is not working.
private function createCSV($xml, $f)
{
foreach ($xml->getElementsByTagName('*') as $item)
{
$hasChild = $item->hasChildNodes() ? true : false;
if(!$hasChild)
{
//echo 'Doesn\'t have children';
echo 'Value: ' . $item->nodeValue;
}
else
{
//echo 'Has children';
$this->createCSV($item, $f);
}
}
}
$item->nodeValue doesnt print anything to the browser.
I read the documentation but I can't see any mistake.
PS. $item->tagname doesnt work either.
UPDATE
whe using this: echo $item->ownerDocument->saveHTML($item);
I get the tags listed but i dont get the data inside(between the tags) like innerHTML in javascript.
UPDATE
sample xml data : http://pastebin.com/dkuUUC0Q
Text nodes are also considered child nodes, but you're only iterating element nodes (get Elements ByTagName). Because of this you're almost never getting into the 2nd condition.
Try this:
if(!$xml->hasChildNodes()){
printf('Value: %s', $xml->nodeValue);
return;
}
foreach($xml->childNodes as $item)
$this->createCSV($item, $f);
XPath version:
$xpath = new DOMXPath($xml);
$text = $xpath->query('//text()[normalize-space()]');
foreach($text as $node)
printf('Value: %s', $node->nodeValue);

Check if a field/propertry exists in a variable of type object

I am using Zend_Search_Lucene, to index my website. My site indexes are not entirely similar. Some have, few fields, and some have many fields. I am trying to create a similar index through different types of table, that's why i am encountering this kind of error.
Now, when I display the result. I call some fields, which are not present in all the result which generates the error. i tried to check it with isset but it seems it totally skips the row.
foreach ($hits as $hit) {
$content .= '<div class="searchResult">';
$content .= '<h2>';
$title = array();
if(isset($hit -> name)) $title[] = $hit -> name;
if(isset($hit -> title)) $title[] = $hit -> title;
// This is the part where i get fatal error.
$content .= implode(" » ",$title);
$content .= '</h2>';
$content .= '<p>'.$this->content.'</p>';
$content .= '</div>';
}
How to check if there is anything such as $hit -> name is present in $hit
The problem you are experiencing is very very specific and has to do with the Zend_Lucene_Search implementation, not the field/property exist check in general.
In your loop, $hit is an object of class Zend_Search_Lucene_Search_QueryHit. When you write the expression $hit->name, the object calls the magic __get function to give you a "virtual property" named name. It is this magic function that throws the exception if the value to be supplied does not exist.
Normally, when a class implements __get as a convenience it should also implement __isset as a convenience (otherwise you cannot really use isset on such virtual properties, as you have found out the hard way). Since this particular class does not implement __isset as IMHO it should, you will never be able to get the name "property" blindly without triggering an exception if the relevant data does not exist.
property_exists and all other forms of reflection will also not help since we are not talking about a real property here.
The proper way to solve this is a little roundabout:
$title = array();
$names = $hit->getDocument()->getFieldNames();
if(in_array('name', $names)) $title[] = $hit -> name;
if(in_array('title',$names)) $title[] = $hit -> title;
All said, I 'd consider this a bug in ZF and probably file a report, asking for the __isset magic method to be implemented appropriately on the types it should be.
You can try property_exists.
foreach ($hits as $hit) {
$content .= '<div class="searchResult">';
$content .= '<h2>';
$title = array();
if(property_exists($hit, 'name')) $title[] = $hit -> name;
if(property_exists($hit, 'title')) $title[] = $hit -> title;
// This is the part where i get fatal error.
$content .= implode(" » ",$title);
$content .= '</h2>';
$content .= '<p>'.$this->content.'</p>';
$content .= '</div>';
}
You can also use reflection to query what fields the object has and build your content in a more programmatic way. this is good if you have a ton of fields.
$reflector = new ReflectionClass( get_class( $hit ) );
foreach( $reflector->getProperties() as $property ) {
if( in_array( $property->getName(), $SEARCH_FIELDS )
$title[] = $property->getValue( $hit );
}
more info here : http://php.net/manual/en/book.reflection.php
# Verify if exists
$hits = $index->find('id_field:100');
if (isset($hits[0])) { echo 'true'; } else { echo 'false'; }
isset will work if you simply convert your object to an array first.
<?php
class Obj {
public function GetArr() {
return (array)$this;
}
static public function GetObj() {
$obj = new Obj();
$obj->{'a'} = 1;
return $obj;
}
}
$o = \Obj::GetObj();
$a = $o->GetArr();
echo 'is_array: ' . (\is_array($a) ? 1 : 0) . '<br />';
if (\is_array($a)) {
echo '<pre>' . \print_r($a, \TRUE) . '</pre><br />';
}
echo '<pre>' . \serialize($o) . '</pre>';
?>

Get xpath of xml node within recursive function

Lets say i have some code to iterate through an XML file recursively like this:
$xmlfile = new SimpleXMLElement('http://www.domain.com/file.xml',null,true);
xmlRecurse($xmlfile,0);
function xmlRecurse($xmlObj,$depth) {
foreach($xmlObj->children() as $child) {
echo str_repeat('-',$depth).">".$child->getName().": ".$subchild."\n";
foreach($child->attributes() as $k=>$v){
echo "Attrib".str_repeat('-',$depth).">".$k." = ".$v."\n";
}
xmlRecurse($child,$depth+1);
}
}
How would i calculate the xpath of each node so i can store it for mapping to other code?
The obvious way to do it is to pass the XPath as a third parameter and build it as you dig deeper. You have to account for siblings having the same name, so you have to keep track of the number of precedent siblings with the same name as current child while iterating.
Working example:
function xmlRecurse($xmlObj,$depth=0,$xpath=null) {
if (!isset($xpath)) {
$xpath='/'.$xmlObj->getName().'/';
}
$position = array();
foreach($xmlObj->children() as $child) {
$name = $child->getName();
if(isset($position[$name])) {
++$position[$name];
}
else {
$position[$name]=1;
}
$path=$xpath.$name.'['.$position[$name].']';
echo str_repeat('-',$depth).">".$name.": $path\n";
foreach($child->attributes() as $k=>$v){
echo "Attrib".str_repeat('-',$depth).">".$k." = ".$v."\n";
}
xmlRecurse($child,$depth+1,$path.'/');
}
}
Attention though, the whole idea of mapping a whole document and storing XPath along the way seems weird. You might actually be working on the wrong solution to a totally different problem.
You can pass to your xmlRecurse third param called $xpath (with current node xPath representation) and add xpath representation of the children on each iteration:
function xmlRecurse($xmlObj,$depth,$xpath) {
$i=0;
foreach($xmlObj->children() as $child) {
echo str_repeat('-',$depth).">".$child->getName().": ".$subchild."\n";
foreach($child->attributes() as $k=>$v){
echo "Attrib".str_repeat('-',$depth).">".$k." = ".$v."\n";
}
xmlRecurse($child,$depth+1,$xpath.'/'.$child->getName().'['.$i++.']');
}
}
With SimpleXML, I think you can only do it as others have pointed out: by recursing the node path as a string argument.
With DOMDocument, you could use the $node->parentNode property to crawl back to the document element and construct it for an arbitrary node (for example if you had a reference to a node and wanted to discover where in the tree it was without prior knowledge of how you got to that node).
$domNode = dom_import_simplexml($node);
$xpath = $domNode->getNodePath();
You need PHP 5 >= 5.2.0 for this to work.
Following up on MightyE's idea about backtracking:
function whereami($node)
{
if ($node instanceof SimpleXMLElement)
{
$node = dom_import_simplexml($node);
}
elseif (!$node instanceof DOMNode)
{
die('Not a node?');
}
$q = new DOMXPath($node->ownerDocument);
$xpath = '';
do
{
$position = 1 + $q->query('preceding-sibling::*[name()="' . $node->nodeName . '"]', $node)->length;
$xpath = '/' . $node->nodeName . '[' . $position . ']' . $xpath;
$node = $node->parentNode;
}
while (!$node instanceof DOMDocument);
return $xpath;
}
I wouldn't recommend it for the case at hand (mapping a whole document, as opposed to a single given node) but it might be useful for future reference.

Categories