Search in hierarchical data in PHP - php

Here is the data structure I have (it's simplified for clearlier understanding):
• USA
• Alabama
• Montgomery
• Birmingham
• Arizona
• Phoenix
• Mesa
• Gilbert
• Germany
• West Germany
• Bonn
• Cologne
I need to return all path for given node – i.e.: if user enter Arizona, I need to return USA → Arizona. If enter Birmingham, I need to return USA → Alabama → Birmingham.
Is there in PHP simple way to search in such structures?

If you haven't huge data structure, you can use XML parsing. It's well known and easy to implement. It has desired ability to access parent element.
Here is an simple example:
$xml = <<<XML
<list>
<state name="USA">
<region name="Alabama">
<city name="Montgomery" />
<city name="Birmingham" />
</region>
<region name="Arizona">
<city name="Phoenix" />
<city name="Mesa" />
<city name="Gilbert" />
</region>
</state>
<state name="Germany">
<region name="West Germany">
<city name="Bonn" />
<city name="Cologne" />
</region>
</state>
</list>
XML;
$doc = new \DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->loadXML($xml);
$xpath = new \DOMXPath($doc);
// XPath query to match all elements with
// attribute name equals to your searched phrase
$locations = $xpath->query("//*[#name='Cologne']");
function parse($list) {
$response = [];
foreach ($list as $node) {
$response[] = $node->attributes->getNamedItem('name')->nodeValue;
$parentNode = $node->parentNode;
// traverse up to root element
// root element has no attributes
// feel free to use any other condition, such as checking to element's name
while ($parentNode->hasAttributes()) {
$response[] = $parentNode->attributes->getNamedItem('name')->nodeValue;
$parentNode = $parentNode->parentNode;
}
}
return $response;
}
$parsedLocations = array_reverse(parse($locations));
echo implode(' → ', $parsedLocations), PHP_EOL;

Here is a possible strategy that builds up the path piece by piece: you start from the first level of the array and you check whether the searc term equals the key. If not, you check the value and otherwise if the value is an array (is_array()) you repeat the search recursively by using a prefix.
The data set:
$str = array(
"USA" => array(
"Alabama" => array(
"Montgomery",
"Birmingham"
),
"Arizona" => array(
"Phoenix",
"",
"Gilbert"
),
"West Germany" => array(
"Bonn",
"",
"Cologne"
)
),
"Germany" => array(
"West Germany" => array(
"Bonn",
"Mesa",
"Cologne"
)
)
);
The function:
function getPath($haystack, $needle, $prefix=""){
$path = "";
foreach($haystack as $key=>$value){
if($path!="")break;
if($key===$needle){
return $prefix.$key;
break;
}
elseif($value===$needle) {
return $prefix.$value;
break;
}
elseif(is_array($value)) {
$path.=getPath($value,$needle,$prefix.$key."=>");
}
}
return $path;
}
a test:
echo getPath($str,"Mesa");
In case of duplicates you will get the first result. If the search term is not found, you get an empty string.

Since "data structure" is very vague, and your only hint is that you're using PHP, I will assume that your "data structure" means the following:
[
'USA' =>
[
'Alabama' =>
[
'Montgomery',
'Birmingham'
],
'Arizona' =>
[
'Phoenix',
'Mesa',
'Gilbert'
]
],
'Germany' =>
[
'West Germany' =>
[
'Bonn',
'Cologne'
]
]
]
And I assume that you want your result in the form
['USA', 'Alabama', 'Birmingham']
If this is not the case, please inform us about how your data is actually available and how you want your result.
Is there in PHP simple way to search in such structures?
That depends on your definition of "simple".
For me, a solution that fits into a single function is "simple".
However, there is no out-of-the-box solution for this that you can use in a one-liner.
If you only need to find the "leafs", you could use a RecursiveIteratorIterator over a RecursiveArrayIterator as in this StackOverflow question.
But since you need to find intermediary keys too, that it not really an option.
The same goes for array_walk_recursive.
You could probably use ArrayIterator or array_walk, but in this example they can't really do anything a foreach loop can't, besides complicate things.
So I'd just go with a foreach loop:
function findMyThing($needle, $haystack) // Keep argument order from PHP array functions
{
// We need to set up a stack array + a while loop to avoid recursive functions for those are evil.
// Recursive functions would also complicate things further in regard of returning.
$stack =
[
[
'prefix' => [],
'value' => $haystack
]
];
// As long as there's still something there, don't stop
while(count($stack) > 0)
{
// Copy the current stack and create a new, empty one
$currentStack = $stack;
$stack = [];
// Work the stack
for($i = 0; $i < count($currentStack); $i++)
{
// Iterate over the actual array
foreach($currentStack[$i]['value'] as $key => $value)
{
// If the value is an array, then
// 1. the key is a string (so we need to match against it)
// 2. we might have to go deeper
if(is_array($value))
{
// We need to build the current prefix list regardless of what we're gonna do below
$prefix = $currentStack[$i]['prefix'];
$prefix[] = $key;
// If the current key, is the one we're looking for, heureka!
if($key == $needle)
{
return $prefix;
}
// Otherwise, push prefix & value onto the stack for the next loop to pick up
else
{
$stack[] =
[
'prefix' => $prefix,
'value' => $value
];
}
}
// If the value is NOT an array, then
// 1. the key is an integer, so we DO NOT want to match against it
// 2. we need to match against the value itself
elseif($value == $needle)
{
// This time append $value, not $key
$prefix = $currentStack[$i]['prefix'];
$prefix[] = $value;
return $prefix;
}
}
}
}
// At this point we searched the entire array and didn't find anything, so we return an empty array
return [];
}
Then just use it like
$path = findMyThing('Alabama', $array);

#Siguza
avoid recursive functions for those are evil
Recursion is not evil (or eval) and works well with stacks to
function df($v,array &$in,array &$stack,$search) {
$stack[] = $v;
if ( $v == $search ) {
return [true,$stack];
}
if ( is_array($in) ) {
foreach ($in as $vv => $k) {
if ( is_array($k) ) {
$r = df($vv, $k, $stack, $search);
if ($r[0]) {
return $r;
}
}
else if ($k == $search) {
$stack[] = $k;
return [true,$stack];
}
}
}
array_pop($stack);
return [false,null];
}
Usage:
$s = [];
$r = df('',$in,$s,'Bonn');
print_r($r);
$s = [];
$r = df('',$in,$s,'West Germany');
print_r($r);
$s = [];
$r = df('',$in,$s,'NtFound');
print_r($r);
Output:
Array
(
[0] => 1
[1] => Array
(
[0] =>
[1] => Germany
[2] => West Germany
[3] => Bonn
)
)
Array
(
[0] => 1
[1] => Array
(
[0] =>
[1] => Germany
[2] => West Germany
)
)
Array
(
[0] =>
[1] =>
)

According to you data structure.
$data['USA'] = ['Alabama' => ['Montgomery','Birmingham'],'Arizona' => ['Phoenix','Mesa','Gilbert']];
$data['Germany'] = ['West Germany' => ['Bonn','Cologne']];
function getHierarchy($location, $data){
$totalCountries = count($data);
//Get Array Keys of rows eg countries.
$keys = array_keys($data);
$hierarchy= [];
//Loop Through Countries
for($i = 0; $i < $totalCountries; $i++){
//If we have found the country then return it.
if($location == $keys[$i]) return [$keys[$i]];
$hierarchy[] = $keys[$i];
foreach($data[$keys[$i]] as $city => $places){
// if we have found the city then return it with country.
if($city == $location){
$hierarchy[] = $city;
return $hierarchy;
}
// if we have found the place in our places array then return it with country -> city -> place.
if(in_array($location, $places)){
$hierarchy[] = $city;
$hierarchy[] = $location;
return $hierarchy;
}
}
// Reset Hirarcy if we do not found our location in previous country.
$hierarchy = [];
}
}
$found = getHierarchy('Birmingham', $data);
if($found){
echo implode(' -> ', $found);
// Output will be USA -> Alabama -> Birmingham
}
It can only find only one country city and places and if any location is found then it will break the whole function and return the first location with city and place.
Here is more improved version which can find multiple locations as well.
https://gist.github.com/touqeershafi/bf89351f3b226aae1a29
Hope it helps you.

Related

Convert a nested Array to Scalar and then reconvert the scalar to nested array

I have an array that looks like this.
$array = [
0 => 'abc',
1 => [
0 => 'def',
1 => 'ghi'
],
'assoc1' => [
'nassoc' => 'jkl',
'nassoc2' => 'mno',
'nassoc3' => '',
'nassoc4' => false
]
];
The $array can have numeric keys or be an assoc array or a mixed one. The level of nesting is not known. Also the values of the array can also be bool or null or an empty string ''
I need to able to convert this into a scalar array with key value pairs. And then later reconvert it back to the exact same array.
So the scalar array could look like
$arrayScalar = [
'0' => 'abc',
'1[0]' => 'def',
'1[1]' => 'ghi',
'assoc1[nassoc]' => 'jkl',
'assoc1[nassoc2]' => 'mno',
'assoc1[nassoc3]' => '',
'assoc1[nassoc4]' => false
];
And then later be able to get back to the initial $array.
I wrote a parser and it does not currently handle bool values correctly.
I have a feeling this is at best a super hacky method to do what I am after. Also I have been able to test it only so much.
function flattenNestedArraysRecursively($nestedArray, $parent = '', &$flattened = [])
{
$keys = array_keys($nestedArray);
if (empty($keys)) {
$flattened[$parent] = 'emptyarray';
} else {
foreach ($keys as $value) {
if (is_array($nestedArray[$value])) {
$reqParent = (!empty($parent)) ? $parent . '|!|' . $value : $value;
$this->flattenNestedArraysRecursively($nestedArray[$value], $reqParent, $flattened);
} else {
$reqKey = (!empty($parent)) ? $parent . '|!|' . $value : $value;
$flattened[$reqKey] = $nestedArray[$value];
}
}
}
return $flattened;
}
function reCreateFlattenedArray($flatArray): array
{
$arr = [];
foreach ($flatArray as $key => $value) {
$keys = explode('|!|', $key);
$arr = $this->reCreateArrayRecursiveWorker($keys, $value, $arr);
}
return $arr;
}
function reCreateArrayRecursiveWorker($keys, $value, $existingArr)
{
//Outside to Inside
$keyCur = array_shift($keys);
//Check if keyCur Exists in the existingArray
if (key_exists($keyCur, $existingArr)) {
// Check if we have reached the deepest level
if (empty($keys)) {
//Return the Key => value mapping
$existingArr[$keyCur] = $value;
return $existingArr;
} else {
// If not then continue to go deeper while appending deeper levels values to current key
$existingArr[$keyCur] = $this->reCreateArrayRecursiveWorker($keys, $value, $existingArr[$keyCur]);
return $existingArr;
}
} else {
// If Key does not exists in current Array
// Check deepest
if (empty($keys)) {
//Return the Key => value mapping
$existingArr[$keyCur] = $value;
return $existingArr;
} else {
// Add the key
$existingArr[$keyCur] = $this->reCreateArrayRecursiveWorker($keys, $value, []);
return $existingArr;
}
}
}
Is there a better more elegant way of doing this, maybe http_build_query or something else I am not aware of.
Sandbox link -> http://sandbox.onlinephpfunctions.com/code/50b3890e5bdc515bc145eda0a1b34c29eefadcca
Flattening:
Your approach towards recursion is correct. I think we can make it more simpler.
We loop over the array. if the value is an array in itself, we recursively make a call to this new child subarray.
This way, we visit each key and each value. Now, we are only left to manage the keys to assign them when adding to our final resultant array, say $arrayScalar.
For this, we make a new function parameter which takes the parent key into account when assigning. That's it.
Snippet:
$arrayScalar = [];
function flatten($array,&$arrayScalar,$parent_key){
foreach($array as $key => $value){
$curr_key = empty($parent_key) ? $key : $parent_key . '[' . $key . ']';
if(is_array($value)){
flatten($value,$arrayScalar,$curr_key);
}else{
$arrayScalar[$curr_key] = $value;
}
}
}
flatten($array,$arrayScalar,'');
var_export($arrayScalar);
Demo: http://sandbox.onlinephpfunctions.com/code/1e3092e9e163330f43d495cc9d4acb672289a987
Unflattening:
This one is a little tricky.
You might have already noticed that the keys in the flattened array are of the form key1[key2][key3][key4] etc.
So, we collect all these individually in a new array, say $split_key. It might look like this.
array (
'key1',
'key2',
'key3',
'key4',
)
To achieve the above, we do a basic string parsing and added in-between keys to the array whenever we reach the end of the key string or [ or ].
Next, to add them to our final resultant array, we loop over the collected keys and check if they are set in our final array. If not so, set them. We now pass child array reference to our temporary variable $temp. This is to edit the same copy of the array. In the end, we return the result.
Snippet:
<?php
function unflatten($arrayScalar){
$result = [];
foreach($arrayScalar as $key => $value){
if(is_int($key)) $key = strval($key);
$split_key = [];
$key_len = strlen($key);
$curr = '';
// collect them as individual keys
for($i = 0; $i < $key_len; ++$i){
if($key[ $i ] == '[' || $key[ $i ] == ']'){
if(strlen($curr) === 0) continue;
$split_key[] = $curr;
$curr = '';
}else{
$curr .= $key[ $i ];
}
if($i === $key_len - 1 && strlen($curr) > 0){
$split_key[] = $curr;
}
}
// collecting them ends
//add them to our resultant array.
$temp = &$result;
foreach($split_key as $sk){
if(!isset($temp[ $sk ])){
$temp[ $sk ] = [];
}
$temp = &$temp[$sk];
}
$temp = $value;
}
return $result;
}
var_export(unflatten($arrayScalar));
Demo: http://sandbox.onlinephpfunctions.com/code/66136a699c3c5285eed3d3350ed4faa5bbce4b76

Question amout mtownsend/request-xml (XML to array) plugin

Have a question about mtownsend/request-xml (XML to array) plugin.
So, plugin makes XML file to array.
I use it in my Laravel projects and there is several reason, because I need exact it, but here is one problem.
Have two simple XML files
first file oneitem.xml with one item <flat> into <post>
<?xml version="1.0" encoding="UTF-8"?>
<data>
<post>
<flat>
<roms>4</roms>
<baths>2</baths>
</flat>
</post>
</data>
second file severalitems.xml one with several items <flat> into <post>:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<post>
<flat>
<roms>4</roms>
<baths>2</baths>
</flat>
<flat>
<roms>5</roms>
<baths>1</baths>
</flat>
<flat>
<roms>7</roms>
<baths>3</baths>
</flat>
</post>
</data>
Then, I use a simple code to make an array from this files, and show the result array for each:
$xmlone = XmlToArray::convert(file_get_contents('public/xml/test/oneitem.xml'));
$oneflat = $xmlone['post'];
print_r($oneflat);
$xmlseveral = XmlToArray::convert(file_get_contents('public/xml/test/severalitems.xml'));
$severalflats = $xmlseveral['post'];
print_r($severalflats);
If we try to make an array from first file (with one flat), and find all we have in posts we have this result:
Array ( [flat] => Array ( [roms] => 4 [baths] => 2 ) )
If we do the same in second file (with several `flat), we have this result
Array ( [flat] => Array ( [0] => Array ( [roms] => 4 [baths] => 2 ) [1] => Array ( [roms] => 5 [baths] => 1 ) [2] => Array ( [roms] => 7 [baths] => 3 ) )
So, if we have several items, plugin adds a additional arrays with keys, [0], [1], [2]....
I need to do it the same, even there is just one item flat into posts. So the results have same formats.
I know, that it makes plugin. If plugin see, that there is just one flat in post, he makes result array simple.
The code of main file of plugin is here, but I cant understand, which lines do it...
Thanks for your help
public static function convert($xml, $outputRoot = false)
{
$array = self::xmlStringToArray($xml);
if (!$outputRoot && array_key_exists('#root', $array)) {
unset($array['#root']);
}
return $array;
}
protected static function xmlStringToArray($xmlstr)
{
$doc = new DOMDocument();
$doc->loadXML($xmlstr);
$root = $doc->documentElement;
$output = self::domNodeToArray($root);
$output['#root'] = $root->tagName;
return $output;
}
protected static function domNodeToArray($node)
{
$output = [];
switch ($node->nodeType) {
case XML_CDATA_SECTION_NODE:
case XML_TEXT_NODE:
$output = trim($node->textContent);
break;
case XML_ELEMENT_NODE:
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
$child = $node->childNodes->item($i);
$v = self::domNodeToArray($child);
if (isset($child->tagName)) {
$t = $child->tagName;
if (!isset($output[$t])) {
$output[$t] = [];
}
$output[$t][] = $v;
} elseif ($v || $v === '0') {
$output = (string) $v;
}
}
if ($node->attributes->length && !is_array($output)) { // Has attributes but isn't an array
$output = ['#content' => $output]; // Change output into an array.
}
if (is_array($output)) {
if ($node->attributes->length) {
$a = [];
foreach ($node->attributes as $attrName => $attrNode) {
$a[$attrName] = (string) $attrNode->value;
}
$output['#attributes'] = $a;
}
foreach ($output as $t => $v) {
if (is_array($v) && count($v) == 1 && $t != '#attributes') {
$output[$t] = $v[0];
}
}
}
break;
}
return $output;
}
}
** Thanks for your help!**
Looks like its not posstible. As I understood, standart PHP tools to XML to array convertation makes it so. The plugin based on them.
Anyway, I think there is a way to solve it throught changing source code of this plugin, but I soleved in my situation, by workung with results of XML to array convertation, and check if result array have one flat or several.

PHP array filter by pre text from value

I have an array like below
Array
(
[0] => country-indonesia
[1] => country-myanmar
[2] => access-is_airport
[3] => heritage-is_seagypsy
)
From that array I want to make separate array only for [country] ,[access], [heritage]
So for that I have to check array value by text before '-'. I am not sure how to do it. so i can't apply code here. I just have the array in PHP
A modified answer, if you want to get the specific types only.
<?php
$arr = [
'country-indonesia',
'country-myanmar',
'access-is_airport',
'heritage-is_seagypsy',
];
$new_array = [];
$types = ['country', 'heritage', 'access'];
foreach ($arr as $element) {
$fac = explode('-', $element);
foreach ($types as $type) {
if ($fac[0] === $type) {
$new_array[$type][] = $fac[1];
}
}
}
$country = $new_array['country'];
$access = $new_array['access'];
$heritage = $new_array['heritage'];
var_dump($new_array);
A simple and easy solution in 3 lines of code using array_walk
<?php
$arr = [
'country-indonesia',
'country-myanmar',
'access-is_airport',
'heritage-is_seagypsy',
];
$new_array = [];
array_walk($arr, function($item) use (&$new_array){
//if(false === strpos($item, '-')) return;
list($key,$value) = explode('-', $item, 2);
$new_array[$key][] = $value;
});
print_r($new_array);
Gives this output:
Array
(
[country] => Array
(
[0] => indonesia
[1] => myanmar
)
[access] => Array
(
[0] => is_airport
)
[heritage] => Array
(
[0] => is_seagypsy
)
)
If you don't want empty and duplicate entries:
<?php
$arr = [
'country-indonesia',
'country-myanmar',
'access-is_airport',
'heritage-is_seagypsy',
];
$new_array = [];
array_walk($arr, function($item) use (&$new_array){
if(false === strpos($item, '-')) return;
list($key,$value) = explode('-', $item, 2);
if(empty($value) || array_key_exists($key, $new_array) && in_array($value, $new_array[$key])) return;
$new_array[$key][] = $value;
});
print_r($new_array);
you can do it by using explode and in_array functions
<?php
$arr = ["country-indonesia","country-myanmar","access-is_airport","heritage-is_seagypsy"];
$newArr = array();
foreach($arr as $k=> $val){
$valArr = explode("-", $val);
if(!in_array($valArr[0], $newArr)){
$newArr[] = $valArr[0];
}
}
print_r($newArr);
?>
live demo
You need PHP's strpos() function.
Just loop through every element of the array and try something like:
if( strpos($array[$i], "heritage") != false )
{
// Found heritage, do something with it
}
(Rough example written from my cellphone while feeding baby, may have typos but it's the basics of what you need)
Read further here: http://php.net/manual/en/function.strpos.php
//first lets set a variable equal to our array for ease in working with i.e
// also create a new empty array to hold our filtered values
$countryArray = array();
$accessArray = array();
$heritageArray = array();
$oldArray = Array(country-indonesia, country-myanmar, access-is_airport, heritage-is_seagypsy);
//Next loop through our array i.e
for($x = 0; $x < count($oldArray); $x++){
// now filter through the array contents
$currentValue = $oldArray[$x];
// check whether the current index has any of the strings in it [country] ,[access], [heritage] using the method : strpos()
if(strpos($currentValue,'country')){
//if this particular value contains the keyword push it into our new country array //using the array_push() function.
array_push($countryArray,$currentValue);
}elseif(strpos($currentValue,'access')){
// else check for the access string in our current value
// once it's found the current value will be pushed to the $accessArray
array_push($accessArray,$currentValue);
}elseif(strpos($currentValue,'heritage')){
// check for the last string value i.e access. If found this too should be pushed to //the new heritage array i.e
array_push($heritageArray,$currentValue);
}else{
// do nothing
}
}
//I believe that should work: cheers hope

Convert PHP array from XML that contains duplicate elements

Up until now, I've been using the snippet below to convert an XML tree to an array:
$a = json_decode(json_encode((array) simplexml_load_string($xml)),1);
..however, I'm now working with an XML that has duplicate key values, so the array is breaking when it loops through the XML. For example:
<users>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
Is there a better method to do this that allows for duplicate Keys, or perhaps a way to add an incremented value to each key when it spits out the array, like this:
$array = array(
users => array(
user_1 => x,
user_2 => y,
user_3 => z
)
)
I'm stumped, so any help would be very appreciated.
Here is a complete universal recursive solution.
This class will parse any XML under any structure, with or without tags, from the simplest to the most complex ones.
It retains all proper values and convert them (bool, txt or int), generates adequate array keys for all elements groups including tags, keep duplicates elements etc etc...
Please forgive the statics, it s part of a large XML tools set I used, before rewriting them all for HHVM or pthreads, I havent got time to properly construct this one, but it will work like a charm for straightforward PHP.
For tags, the declared value is '#attr' in this case but can be whatever your needs are.
$xml = "<body>
<users id='group 1'>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
<users id='group 2'>
<user>x</user>
<user>y</user>
<user>z</user>
</users>
</body>";
$result = xml_utils::xml_to_array($xml);
result:
Array ( [users] => Array ( [0] => Array ( [user] => Array ( [0] => x [1] => y [2] => z ) [#attr] => Array ( [id] => group 1 ) ) [1] => Array ( [user] => Array ( [0] => x [1] => y [2] => z ) [#attr] => Array ( [id] => group 2 ) ) ) )
Class:
class xml_utils {
/*object to array mapper */
public static function objectToArray($object) {
if (!is_object($object) && !is_array($object)) {
return $object;
}
if (is_object($object)) {
$object = get_object_vars($object);
}
return array_map('objectToArray', $object);
}
/* xml DOM loader*/
public static function xml_to_array($xmlstr) {
$doc = new DOMDocument();
$doc->loadXML($xmlstr);
return xml_utils::dom_to_array($doc->documentElement);
}
/* recursive XMl to array parser */
public static function dom_to_array($node) {
$output = array();
switch ($node->nodeType) {
case XML_CDATA_SECTION_NODE:
case XML_TEXT_NODE:
$output = trim($node->textContent);
break;
case XML_ELEMENT_NODE:
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
$child = $node->childNodes->item($i);
$v = xml_utils::dom_to_array($child);
if (isset($child->tagName)) {
$t = xml_utils::ConvertTypes($child->tagName);
if (!isset($output[$t])) {
$output[$t] = array();
}
$output[$t][] = $v;
} elseif ($v) {
$output = (string) $v;
}
}
if (is_array($output)) {
if ($node->attributes->length) {
$a = array();
foreach ($node->attributes as $attrName => $attrNode) {
$a[$attrName] = xml_utils::ConvertTypes($attrNode->value);
}
$output['#attr'] = $a;
}
foreach ($output as $t => $v) {
if (is_array($v) && count($v) == 1 && $t != '#attr') {
$output[$t] = $v[0];
}
}
}
break;
}
return $output;
}
/* elements converter */
public static function ConvertTypes($org) {
if (is_numeric($org)) {
$val = floatval($org);
} else {
if ($org === 'true') {
$val = true;
} else if ($org === 'false') {
$val = false;
} else {
if ($org === '') {
$val = null;
} else {
$val = $org;
}
}
}
return $val;
}
}
You can loop through each key in your result and if the value is an array (as it is for user that has 3 elements in your example) then you can add each individual value in that array to the parent array and unset the value:
foreach($a as $user_key => $user_values) {
if(!is_array($user_values))
continue; //not an array nothing to do
unset($a[$user_key]); //it's an array so remove it from parent array
$i = 1; //counter for new key
//add each value to the parent array with numbered keys
foreach($user_values as $user_value) {
$new_key = $user_key . '_' . $i++; //create new key i.e 'user_1'
$a[$new_key] = $user_value; //add it to the parent array
}
}
var_dump($a);
First of all this line of code contains a superfluous cast to array:
$a = json_decode(json_encode((array) simplexml_load_string($xml)),1);
^^^^^^^
When you JSON-encode a SimpleXMLElement (which is returned by simplexml_load_string when the parameter could be parsed as XML) this already behaves as-if there would have been an array cast. So it's better to remove it:
$sxml = simplexml_load_string($xml);
$array = json_decode(json_encode($sxml), 1);
Even the result is still the same, this now allows you to create a subtype of SimpleXMLElement implementing the JsonSerialize interface changing the array creation to your needs.
The overall method (as well as the default behaviour) is outlined in a blog-series of mine, on Stackoverflow I have left some more examples already as well:
PHP convert XML to JSON group when there is one child (Jun 2013)
Resolve namespaces with SimpleXML regardless of structure or namespace (Oct 2014)
XML to JSON conversion in PHP SimpleXML (Dec 2014)
Your case I think is similar to what has been asked in the first of those three links.

Moving single array to multi-dimension array

My problem.. I have a PHP array that looks like this:
[1013] => [1154]
[1013] => [1322]
[1154] => [1525]
[1525] => [1526]
How can I take that and move it to something like this:
[1013] => [1154] => [1525] => [1526]
[1013] => [1322]
So it sort of makes a tree that associates to the top level array item. I do not have control over how the data comes to me, it's generated through a third-party API and given to me just like that.
Logic: Client 1013 is the main account. Client 1154 is a client of 1013. Client 1322 is a client of 1013. Client 1525 is a client of 1154. I want to get it to a multi-dimension array so I can show this in a tree format.
Here you go!:
<?php
// dataset
$clientset = array(
array(1013, 1154),
array(1013, 1322),
array(1154, 1525),
array(1525, 1526)
);
$children = array();
// make an array with children to see which nodes have none
foreach($clientset as $set) {
if(!isset($children[$set[0]])) $children[$set[0]] = array($set[1]);
else $children[$set[0]][] = $set[1];
}
// array with parents
$parents = array();
foreach($clientset as $set) {
$parents[$set[1]] = $set[0];
}
// for each node with no children, begin the search!
foreach($clientset as $set) {
if(!isset($children[$set[1]])) {
echo getPath($set[1]).'</br>';
}
}
// recursively search to parents and print them
function getPath($child) {
global $parents;
if($parents[$child]) {
return (getPath($parents[$child]).' => '.$child);
} else return $child;
}
?>
This outputs:
1013 => 1322
1013 => 1154 => 1525 => 1526
The idea is to see which nodes have no children. Then, iterate through their parents. You probably don't need the output like it is right now, but I'm sure you can work it out this way. Enjoy!
You can use array_walk php function to apply callback to each element of source array. Callback should create a new array according to your requirements. Callback function will take 2 parameters: value of current array element and it's key. Using that it's simple to build array you need.
Chris, you should have just emailed me first. :-p
$test_array = array('1','2','3','4','5','6');
$output_string = '';
for ($i = 0; $i < sizeof($test_array) -1; $i++)
{
$output_string .= '{"'.$test_array[$i].'":';
}
$output_string .= $test_array[$i];
for ($i = 0; $i < sizeof($test_array)-1; $i++) { $output_string .= '}'; }
$new_array = json_decode($output_string, true);
var_dump($new_array);

Categories