PHP xpath won't return data - php

im trying to filter out some informations from an XML file using PHP xpath, somehow the response seems always to be empty. Does anyone have an idea what could cause this?
Xml example:
<account>
<a>Information1</a>
<b>Information2</b>
<c>Informatio3</c>
</account>
My actual code:
public static function user_cache($username) {
$xml = #file_get_contents('xmlfile');
$xml = #simplexml_load_string($xml);
if ( $xml ) {
$results = array(
"userid" => $xml->xpath('a')
);
}
else {
$results['message'] = 'Unable to get data.';
}
return $results;
}
{
"userid": []
}

This is more of a XPATH question. The correct syntax would be
$xml->xpath('//a')
You can find more information about XPATH's syntax here: http://www.w3schools.com/xsl/xpath_syntax.asp
On the other hand, xpath method returns an array of SimpleXMLElement, so take that into account.
http://php.net/manual/en/simplexmlelement.xpath.php

Related

Cannot serialize SimpleXMLElement to Laravel job

So, after doing some studying I have successfully managed to parse some XML that I'm getting via Guzzle via simplexml_load_string. The issue is then when I then subsequently try to dispatch a job for each of the children using the following code I get a "Serialization of 'SimpleXMLElement' is not allowed" error.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
So to try and fix this I can use the following trick to convert the XML into an array;
json_decode(json_encode($child))
however, while this does mean I can send the data to the new job, it does mean that, as far as I can work out, I have no way to access the #attributes. An alternative would be something along the lines of the following;
// ParentJob
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML, true), $this->otherVar);
}
// ChildJob
public function __construct($xml, $otherVar)
{
$this->xml = simplexml_load_string($xml);
$this->otherVar = $otherVar;
}
however it still throws a serialization error on the dispatch for some reason that I cannot work out, since it sould only be sending raw XML and not an object.
So my main question is what would be the correct way to pass and child SimpleXMLObject to a job in Laravel 5.3 ?
(short of something like looping through all the nodes/attributes and building my own collection from them)
Converting the XML into JSON that way means loosing data. I suggest keeping the XML if possible.
SimpleXMLElement::asXML() is a method. Do not forget the brackets.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML(), true), $this->otherVar);
}
Calling it as a property means that SimpleXML tries to interpret it as a child element node. This means it will be an (empty) SimpleXMLElement.
Here is a small example showing the behavior:
$node = new SimpleXMLElement('<foo/>');
var_dump($node->asXml);
var_dump($node->asXml->getName());
var_dump($node->asXml());
Output:
object(SimpleXMLElement)#2 (0) {
}
string(0) ""
string(29) "<?xml version="1.0"?>
<foo/>
"
The SimpleXmlElement can be converted to array as follows:
$xml = <<<'XML'
<root>
<x a="a1">1</x>
<y b="b2">2</y>
<z>3</z>
</root>
XML;
$xe = simplexml_load_string($xml);
$a = $xe->xpath('*');
$a = array_map(function ($e) {
$item = (array) $e;
$item['nodeName'] = $e->getName();
return $item;
}, $a);
// Now `$a` is an array (serializable object)
echo json_encode($a, JSON_PRETTY_PRINT);
Output
[
{
"#attributes": {
"a": "a1"
},
"0": "1",
"nodeName": "x"
},
{
"#attributes": {
"b": "b2"
},
"0": "2",
"nodeName": "y"
},
{
"0": "3",
"nodeName": "z"
}
]
Note, you can get the string value of a SimpleXmlElement by casting it to string:
$item['value'] = (string) $e;
Since xpath method supports relative XPath expressions, the asterisk should work even with namespaced XMLs. Consider using the DOM extension, as it is much more flexible than SimpleXML. In particular, its DOMXPath class allows to register namespaces and use the registered identifiers in the XPath expressions:
$xpath->registerNamespace('myprefix', 'http://example.com/ns');
$xpath->query('/root/myprefix:*');
As it turns out the entire reason it was not working was due to me using simplexml_load_string() on the child jobs constructor, which was turning it into a simpleXMLElement before the job was actually serialized and pushed onto the queue. the correct way to do it was to parse the XML string on the handle method, which is done after the job has been pulled from the queue for actual processing.
Now this works I can simply dispatch the child job with $child->asXML, and parse it when the job is actually being processed, meaning I can still use all the nifty simpleXML features such as attributes().
Example ParentJob:
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
Example ChildJob:
protected $xml;
protected $otherVar;
public function __construct($xml, $otherVar)
{
$this->xml = $xml;
$this->otherVar = $otherVar;
}
public function handle()
{
$child = simplexml_load_string($this->xml);
$attributes = $child->attributes();
}

PHP XML validation fails on valid XML

I use the following function to validate XML coming from a Web API before trying to parse it:
function isValidXML($xml) {
$doc = #simplexml_load_string($xml);
if ($doc) {
return true;
} else {
return false;
}
}
For some reason, it fails on the following XML. While it's a bit light in content, it looks valid to me.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><connection-response-list xmlns="http://www.ca.com/spectrum/restful/schema/response" />
Why would this fail? I tried another method of validate the XML that used DOMDocument and libxml_get_errors(), but it was actually more fickle.
EDIT: I should mention that I'm using PHP 5.3.8.
I think your interpretation is just wrong here – var_dump($doc) should give you
object(SimpleXMLElement)#1 (0) {
}
– but since it is an “empty” SimpleXMLElement, if($doc) considers it to be false-y due to PHP’s loose type comparison rules.
You should be using
if ($doc !== false)
here – a type-safe comparison.
(Had simplexml_load_string actually failed, it would have returned false – but it didn’t, see var_dump output I have shown above, that was tested with exactly the XML string you’ve given.)
SimpleXML wants some kind of "root" element. A self-closing tag at the root won't cut it.
See the following code when a root element is added:
<?php
function isValidXML($xml)
{
$doc = #simplexml_load_string($xml);
if ($doc) {
return true;
} else {
return false;
}
}
var_dump(isValidXML('<?xml version="1.0" encoding="UTF-8" standalone="yes"?><root><connection-response-list xmlns="http://www.ca.com/spectrum/restful/schema/response" /></root>'));
// returns true
print_r(isValidXML('<?xml version="1.0" encoding="UTF-8" standalone="yes"?><root><connection-response-list xmlns="http://www.ca.com/spectrum/restful/schema/response" /></root>'));
// returns 1
?>
Hope that helps.

Avoid hardcoding XML in PHP for an API request

I'm sending the following XML to an api using cURL:
$xml = "<request type='auth' timestamp='$timestamp'>
<merchantid>$merchantid</merchantid>
<account>$account</account>
<orderid>$orderid</orderid>
<amount currency='$currency'>$amount</amount>
<card>
<number>$cardnumber</number>
<expdate>$expdate</expdate>
<type>$cardtype</type>
<chname>$cardname</chname>
</card>
<sha1hash>$sha1hash</sha1hash>
</request>";
What is the best way to avoid hard coding this XML? I was thinking of using XMLWriter but seems strange as it won't be changing.
Should I use a template? Or generate it using XMLWriter / Simple XML?
As I mentioned in the comments, there's not necessarily a right answer to this but I recently had to write a project around an XML API Feed as well. I decided to go with XMLWriter and it's still very easy to interchange into others easily by using their respected .loadXML() functions.
class SomeApi extends XMLwriter {
public function __construct() {
$this->openMemory();
$this->setIndent( true );
$this->setIndentString ( " " );
$this->startDocument( '1.0', 'UTF-8', 'no' );
$this->startElement( 'root' );
}
public function addNode( $Name, $Contents ) {
$this->startElement( $Name );
$this->writeCData( $Contents );
$this->endElement();
}
public function output() {
$this->endElement();
$this->endDocument();
}
//Returns a String of Xml.
public function render() {
return $this->outputMemory();
}
}
$newRequest = new SomeApi();
$newRequest->addNode( 'some', 'Some Lots of Text' );
$Xml = $newRequest->render();
I think it's a nice clean way writing an XML Feed in PHP, furthermore as you can add internal functions such as:
$this->addHeader();
private function addHeader() {
$this->addNode( 'login', 'xxxxx' );
$this->addNode( 'password', 'xxxxx' );
}
Which then appends nodes that you'll use over & over again. Then if you suddenly need to use a DOMDocument object for example (As I needed too for XSL).
$Dom = new DOMDocument();
$Dom->loadXML( $Xml );
Should I use a template?
You actually already did use a template here.
Or generate it using XMLWriter / Simple XML?
XMLWriter and also SimpleXMLElement are components that allow you to create XML easily. For your specific case I'd use SimpleXML for a start:
$xml = new SimpleXMLElement('<request type="auth"/>');
$xml['timestamp'] = $timestamp;
$xml->merchantid = $merchantid;
$xml->account = $account;
$xml->orderid = $orderid;
$xml->addChild('amount', $amount)['currency'] = $currency;
$card = $xml->addChild('card');
$card->number = $cardnumber;
$card->expdate = $expdate;
$card->type = $cardtype;
$card->chname = $cardname;
$xml->sha1hash = $sha1hash;
See that the XML is not hardcoded any longer, only the names used are. The SimpleXML library takes care to create the XML (demo, here the output is beautified for better readability):
<?xml version="1.0"?>
<request type="auth" timestamp="">
<merchantid></merchantid>
<account></account>
<orderid></orderid>
<amount currency=""/>
<card>
<number></number>
<expdate></expdate>
<type></type>
<chname></chname>
</card>
<sha1hash></sha1hash>
</request>
Thanks to the library, the output is always valid XML and you don't need to care about the details here. You can further simplify it by wrapping it more, but I don't think this is of much use with your very little XML you have here.

PHP - JSON to SimpleXML

I have a bunch of PHP web services that construct JSON objects and deliver them using json_encode.
This works fine but I now have a requirement that the web services can also deliver in XML, depending on a given parameter.
I want to stay away from PEAR XML if possible, and hopefully find a simple solution that can be implemented with SimpleXML.
Can anyone give me any advice?
Thanks
You can create an associative array using json_decode($json,true) and try the following function to convert to xml.
function assocArrayToXML($root_element_name,$ar)
{
$xml = new SimpleXMLElement("<?xml version=\"1.0\"?><{$root_element_name}></{$root_element_name}>");
$f = function($f,$c,$a) {
foreach($a as $k=>$v) {
if(is_array($v)) {
$ch=$c->addChild($k);
$f($f,$ch,$v);
} else {
$c->addChild($k,$v);
}
}
};
$f($f,$xml,$ar);
return $xml->asXML();
}
// usage
$data = json_decode($json,true);
echo assocArrayToXML("root",$data);

Check if an xml is loaded with simplexml_load_string

I am querying an xml file with php like this :
public function trackOrderAction()
{
$request = Mage::getResourceModel( 'order/request' );
$request->setOrder($this->getRequest()->getParam('increment_id'));
$response = $request->submit(true);
$xml = simplexml_load_string($response);
$items = count($xml->OrderItems->OrderItem);
}
The xml is not ready immediately so if people try to use the function before it is ready there is an error because it is trying to get the property of a non-object. My question is what is the proper way to check the xml response to see if there is anything and stop the function if there is not?
I tried something simple like
if (empty($xml)){
die();
} else {
$items = count($xml->OrderItems->OrderItem);
}
But this does not help. Any ideas on how to check to see if the xml loaded?
From http://us.php.net/manual/en/function.simplexml-load-string.php
Returns an object of class SimpleXMLElement with properties containing
the data held within the xml document. On errors, it will return
FALSE.
public function trackOrderAction()
{
$request = Mage::getResourceModel( 'order/request' );
$request->setOrder($this->getRequest()->getParam('increment_id'));
$response = $request->submit(true);
$xml = simplexml_load_string($response);
if ( !$xml ) {
return false;
}
$items = count($xml->OrderItems->OrderItem);
}
It will return false if there was an error. So fail right away if simplexml_load_string fails and return false. Otherwise continue on with the rest of the function. Never die() in a function.

Categories