efficent way to drill down through xml feed multiple times - php

Ive found myself drilling down through xml feeds quite allot, they are almost identical feeds apart from a couple of array names.
Ideally id want to make a sort of function that i can call, but i have no idea how to do it with this sort of data
//DRILLING DOWN TO THE PRICE ATTRIBUTE FOR EACH FEED & MAKING IT A WORKING VAR
$wh_odds = $wh_xml->response->will->class->type->market->participant;
$wh_odds_attrib = $wh_odds->attributes();
$wh_odds_attrib['odds'];
$lad_odds = $lad_xml->response->lad->class->type->market->participant;
$lad_odds_attrib = $lad_odds->attributes();
$lad_odds_attrib['odds'];
as you can see they are essentially the very similar, but im not quite sure how i can streamline the process of setting a working var without writing 3 lines each time.

The function you're probably looking for is called SimpleXMLElement::xpath().
XPath is a language of it's own designed to pick stuff out of XML files. In your case, the xpath expression to get the odds attribute of all these elements is:
response/*/class/type/market/participant/#odds
You can also replace the * with the concrete element name or allow multiple names there and what not.
$odds = $lad_xml->xpath('response/*/class/type/market/participant/#odds');
Different to your code, this has all attribute elements inside the array (you have the attribute's parent element inside the variable). An example outcome (considering two of such elements) would be:
Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[odds] => a
)
)
[1] => SimpleXMLElement Object
(
[#attributes] => Array
(
[odds] => a
)
)
)
You can turn that into strings as well easily:
$odds_strings = array_map('strval', $odds);
print_r($odds_strings);
Array
(
[0] => a
[1] => a
)
Xpath is especially useful if you say, you want to get all participant element's odds attributes:
//participant/#odds
You don't need to explicitly specify each of the parent element names.
I hope this is helpful.

You can do it like this:
function getAttrib ($xmlObj, $attrName) {
$wh_odds = $xmlObj->response->$attrName->class->type->market->participant;
$wh_odds_attrib = $wh_odds->attributes();
return $wh_odds_attrib['odds'];
}
getAttrib ($wh_xml, "will");
hope that helps.

Related

How to "normalize" a php Object from SoapClient with inconsistent structure?

I have a php stdObject that is an object, and sometimes an array of Objects.
EDIT: It's been returned by SoapClient::__soapCall.
If it has one entry:
stdClass Object
(
[Event] => stdClass Object
(
[Nr] => 7050
[Date] => 2016-11-30T00:00:00
)
)
If it's got multiple entries:
stdClass Object
(
[Event] => Array
(
[0] => stdClass Object
(
[Nr] => 7015
[Date] => 2016-04-28T00:00:00
)
[1] => stdClass Object
(
[Nr] => 7016
[Date] => 2016-04-29T00:00:00
)
[2] => stdClass Object
(
[Nr] => 7017
[Date] => 2016-06-08T00:00:00
)
)
)
I don't see why, and I'd like to correct it.
Here's how I got there:
$items = $this->getAll(); // returns an Object with n entries
foreach($items as $item){
$fullItem = $this->item->getElementByID($item->Nr); // returns the full data for this entry
$item->Days = $fullItem->Days; // that's the one that can contain objects or just the data if it has one entry
}
My Questions:
Is that normal, that the same request returns different data types?
Should I "normalize" this and make an array of objects if there's only one entry?
Or is there an other recommended way to cope with it?
I don't think it's ok in this case. You should keep the interface the same. So if you are expecting an unknown number of elements it should always be a collection, even if in some cases only one element is returned. However if you expect only one element then the collection should be omitted.
$this->getAll(); //should always return a collection
$this->getOneById($id); //should return single item without collection, return null or throw exception if no element is found
On the question should you "normalize" (refactor) it I wold say it depends on the effort estimation and the benefit you'll gain form the refactoring. If this occurs in few places only and it's not likely to be used in any other place in the future I think you shouldn't bother. In case you decide to refactor be very careful and ensure the whole application is working properly. If you have tests - great.
I didn't declare it in the question, sorry: The data is the result of a soap client call:
$this->soapClient = new \SoapClient($wsdlUrl, array('trace' => DEBUG_SOAP));
// ...
$result = $this->soapClient->__soapCall($method, array($method => $params));
I think what confused me is the standard behaviour of SoapClient::__soapCall
SOAP functions may return one, or multiple values. If only one value
is returned by the SOAP function, the return value of __soapCall will
be a simple value (e.g. an integer, a string, etc). If multiple values
are returned, __soapCall will return an associative array of named
output parameters.
http://php.net/manual/en/soapclient.soapcall.php
Also, a very similar question: PHP SoapClient type mapping behaves differently
And here are two solutions from there:
"Normalize" (what's the correct word?) the data AFTER we got it:
if (is_object($variable)){
$variable = array($variable);
}
https://stackoverflow.com/a/6408780/160968
And, that would be the equivalent to refactoring the request BEFORE we get it – there's a setting for the soapClient!
$soapConfig = array(
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => true
);
$client = new SoapClient($wsdlUrl, $soapConfig);
https://stackoverflow.com/a/8008715/160968
Awesome!

PHP -modify value of last accessed element in multidimensional associative array

I am reading a GEDCOM-formatted family tree flat file, and producing an array from the data for staging into table. If I encounter the values CONC <some value>, then, instead of adding an element, I need to append <some value> to the value of the last element that was just inserted (regardless of dimension depth).
I tried with current(...) etc but does this work for a multidimensional associative array?
please consider following element in an array:
[#N163#] => Array ( [INDI] => Array ( [TEXT] => Some data of this person) )
if the next line reads "1 CONC including his profession"
instead of adding a line as such
[#N163#] => Array (
[INDI] => Array ( [TEXT] => Some data of this person)
[INDI] => Array ( [CONC] => including his profession) )
I would like the array to look as follows:
[#N163#] => Array (
[INDI] => Array ( [TEXT] => Some data of this person including his profession) )
What I have researched thus far:
end($theArray)
to set pointer to last inserted element followed by $theArray[key($theArray)] = .... to update this element.
But I did not get this method to work for multidimensional arrays and/or it became really messy.
And:
merging two arrays using e.g. += notation,
but this only seems to overwrite a new element, not affect the last one, if keys are same
And:
examples with foreach calls, which does not help in my case.
Hope somebody can shed some light... many thanks!
When you adding $array[#N163#][INDI][TEXT] = 'smtng'; you can save position
$pos = &$array[#N163#][INDI][TEXT];
And if you need concatenate, write
$pos .= "concate line";

PHP: Get specific value from simplexml array

I am pretty new to PHP an XML and hope you can help me with this.
Searching the forum didn't help me yet to find an answer to my specific issue.
I have a PHP page with a simplexml array that looks like the following, just longer:
SimpleXMLElement Object
(
[textID] => Array
(
[0] => SimpleXMLElement Object
(
[textID] => 1
[content] => Text1
)
[1] => SimpleXMLElement Object
(
[textID] => 2
[content] => Text2
)
[2] => SimpleXMLElement Object
(
[textID] => 3
[content] => Text3
)
)
)
Now I am trying to echo out a specific value from this array by referring to its ID which is an integer.
The only way I get this working is the following but this just goes by the order within the array, not by the actual ID:
<?php echo $objTexts->textID[1]->content; ?>
Can someone tell me what I am missing here ?
Thanks, Tim
SimpleXML has no way of knowing that the textID identifies which node is which - it is just another element in the XML.
Based on your sample output, your XML is a little confusing as you have multiple elements called textID which each have a single child, also called textID, which has a different meaning. Nonetheless, what you want to do can be achieved either by looping through all the outer textID elements and testing the value of their inner textID element:
foreach ( $objTexts->textID as $item )
{
if ( $item->textID == '2' )
{
...
}
}
Or, you could use XPath, which is a fairly simple query language for XML, and is supported within SimpleXML in the form of the ->xpath() method. In your case, you want to find a textID node which contains a textID child with a particular value, so the code would look something like this:
// ->xpath always returns a plain PHP array - not a SimpleXML object
$xpath_results = $objTexts->xpath('//textID[textID=2]');
// If you're certain you only want the first result:
echo $xpath_results[0]->content;
// If you might want multiple matches
foreach ( $xpath_results as $item )
{
...
}

How to best add element to array at arbitrary index in PHP?

How can I write a solution for which the current PHP interpreter (5.4) is smart enough to simply do about 3-5 copies instead of a full on item-by-item array sort?
Note, I know a few methods to insert an element into an indexed array. However this does not satisfy my understanding. For instance in C++, you can do something using std::copy or make a struct or union as a multi-element array cursor.
So I wonder if I play by PHP's rules somehow, what syntax can one use to have, under the hood, something closer to
Copy the [range of elements from some index to the end of A] into temp C
Copy B into A[Index],
Copy C into A[Index+count(B)]
Than this...
$MasterItemList = $Page[$CurrentPage]->GetItems(); /* Returns an array with 512 Items. */
$UpdateList = GetUpdatePage(); /* Returns multi-dimensional array such that:
$result[][0]=an index and
$result[][1]=a list of items */
foreach($UpdateList as $Update)
{ foreach($Update as $cursor => $ItemList)
{
$cursor=$cursor+0; //to int..
$numitems=count($ItemList);
if($ItemList[0]->NewAddition)
{
$BeforeUpdate=array_splice($MasterItemList,0, $cursor, true);
$AfterUpdate=array_splice($MasterItemList, $cursor+$numitems, 0);
$MasterItemList=array_merge($BeforeUpdate,$ItemList,$AfterUpdate);
$Page[$CurrentPage]->OffsetCorrection+=$numitems;
}
else
{
$i=0;
foreach($ItemList as $LineItem)
{
$MasterItemList[$cursor+$i] = $LineItem;
$i++;
}
}
}
}
Forgive me if I've a few errors jotting this down, let me know and I'll correct them.
Namely though, I dont think proper referencing and scope are available to the interpreter for it to be able to do the logic directly using this method. It's already a woefully expensive looking thing.. What can be done to do this 'the right way' for PHP?
Examples:
// An Update List
Array(
[0] => Array(
[0] => 31
[1] => Array(
[1] => stdClass Object
(
[NewAddition] => false
[Name] => "********"
[Date] => 1364920943
[Active] => 1
.
.
.
)
[2] => stdClass Object
(
[NewAddition] => false
[Name] => "********"
[Date] => 1364920943
[Active] => 1
.
.
.
)
[3] => stdClass Object
(
[NewAddition] => false
[Name] => "********"
[Date] => 1364920943
[Active] => 1
.
.
.
)
)
)
)
And MasterItemList is simply an array of these same objects (class Item).
A few things to note:
This data is only accessed in a purely sequential manner anywhere it would matter for this script.
Only the first item in a newly inserted set needs to be checked for update in this part of the script. All items following in the set will be always be new.
Items trailing over 512 are auto-adjusted into the next page load. I can adjust size of pages to trade between array sorting performance & data fetch performance (async buffered).
First of all, PHP arrays are not "arrays" in the data structure sense; they are actually hash tables and doubly linked lists rolled into one. When you are indexing into an array e.g. with $list[$i] $i is hashed to find the corresponding element; it's not simple arithmetic as it is in e.g. C++.
Additionally, since arrays are also linked lists the implementation of array_splice is much more efficient than it might appear, at least if the portion being removed is small enough (hashing the new items is normally fast, and interposing items at a certain place of a linked list is constant time).
Of course this means that PHP arrays consume much more memory than a "pure" array would and they are also slower if all you are intending is index-based access. In those situations the SPL offers SplFixedArray which is an implementation of an array in the data structure sense of the word.
In your particular case, array_splice should be your first option; you can insert an array chunk with just one call:
array_splice($MasterItemList, $cursor, 0, $ItemList);

Getting a list of children from an array with Parents, without recursion in PHP

I have a situation where I have already obtained and manipulated SQL data into an array and into a tree. I am trying to avoid recursion at all costs because it has bitten me in the behind in the past.
I have an array of elements with Parent_IDs, and I'd like to be able to obtain a list of all of their children and subchildren. It shouldnt be nearly as complex as going the other way (from an array to a nested tree) which I got without issue using references, but for some reason I'm brain-frozen...
Any help appreciated.
I have the data in two possible formats because I have manipulated it already. Whichver is best for input can be used. this is a structure (print_r) of the two arrays I have:
Array
(
[202735] => Array
(
[ID] => 202735
[text] => aaafdf
[Parent] =>
)
[202737] => Array
(
[ID] => 202737
[text] => Filho 2
[Parent] => 202735
)
[202733] => Array
(
[ID] => 202733
[text] => Neto 1
[Parent] => 202731
)
[202739] => Array
(
[ID] => 202739
[text] => Neto 2
[Parent] => 202737
)
)
or
Array
(
[0] => Array
(
[ID] => 202735
[text] => aaafdf
[Parent] =>
[children] => Array
(
[0] => Array
(
[ID] => 202737
[text] => Filho 2
[Parent] => 202735
[children] => Array
(
[0] => Array
(
[ID] => 202739
[text] => Neto 2
[Parent] => 202737
)
)
)
)
)
[1] => Array
(
[ID] => 202733
[text] => Neto 1
[Parent] => 202731
)
)
Desired output in the format:
(first level parent => all children and grandchildren)
array(202731=>array(202735));
array(202735=>array(202737,202739));
or similar... Ideally i'll wrap this in a function like ListChildren($InitialParent), and return all children from such... invoking ListChildren(0) or (null) would list all elements and all subs...
(additional array elements can be ignored for the purpose of this exercise)
OBS: some data in the array is missing... namely the category above 202735, which would be 202731, but thats just because i limited the data I copied in... basically I can have either a flat array with parent ids, or a "tree" array with nested children as the source.
I ended up with this. Thanks for the help, in the end I reverted to a recursive code. I'll monitor it for performance although I suppose just within the PHP layer it wont be an issue. I had a bad experience when I went in for maintenance on a project that had DB calls in a recursive function... as the usage grew the recursvie function usage grew exponentially and the DB calls went to 100s of thousands...
anyways:
function __GetChildrenRec($Lista, $Categoria){
// Return false if $initialParent doesn't exist
if ($Categoria == 0) $Categoria = "";
if (!isset($Lista[$Categoria])) return FALSE;
// Loop data and assign children by reference
foreach ($Lista as $CategAtual) {
if ($CategAtual[Parent] == $Categoria) {
$Filhos[] = $CategAtual[ID];
$Filhos = array_merge((array)$Filhos,(array)self::__GetChildrenRec($Lista, $CategAtual[ID]));
}
}
// Return the data
return is_array($Filhos) ? $Filhos : array();
}
There really is no reason to avoid recursion unless it is causing you a genuine performance problem. Recursion is how most developers looking at problems of this nature would probably attempt to solve them, and using alternative approaches can hurt maintainability because your code is doing something that someone who needs to maintain your code at a later date doesn't expect.
Generally speaking, unrolling recursion means managing a stack. Recursion is a way of getting the language you're using to manage the stack for you, if you don't use recursion, then you'll need to manipulate a stack yourself inside your function. The simplest way of doing this is with array_push and array_pop, functions built into PHP that let you use an array as a stack.
The stack-based approach is rather complex compared to simply using recursion though, and if recursion is giving you problems then manually maintaining a stack certainly will. There are benefits to be sure, but honestly I'd suggest that you try to figure out recursion instead, as it really is easier to deal with, and while it isn't as performant as managing a stack yourself, the performance loss in probably not going to be a bottleneck in your code as the bottlenecks in PHP scripts tend to be where PHP interfaces with the outside world (databases, files, network connections, etc).
Using the first array format:
function list_children ($array, $initialParent) {
// Return false if $initialParent doesn't exist
if (!isset($array[$initialParent])) return FALSE;
// Loop data and assign children by reference
foreach ($array as &$item) {
if (isset($array[$item['parent']])) {
if (!isset($array[$item['parent']]['children'])) $array[$item['parent']]['children'] = array();
$array[$item['parent']]['children'][] = &$item;
}
}
// Return the data
return (isset($array[$initialParent]['children'])) ? $array[$initialParent]['children'] : array();
}
What this does is basically create the second array from the first, but it does it by reference - so the initial parent can still be found by it's ID, and returned. Returns the array of children, an empty array() if there are no children, or FALSE if the $initialParent doesn't exist.

Categories