I want to transform an array of resources that can have an infinity of children to a simple array like below. I just want to keep the information of the parent, if there is a parent. In my context, a parent is the array just above the child array.
I have this array (bigger in reality with a lof of children), but each children may have an infinity of arrays children:
$array = array (
0 =>
array (
'#id' => 'Authorization',
'#sortOrder' => '1',
'resource' =>
array (
'#id' => 'Authorization2',
'#title' => 'Authorization2',
),
),
);
And I would like to get this, recursively :
$resources = [
0 => [
'parent' => null,
'resource' => 'Authorization'],
1 => [
'Authorization' => 'Authorization',
'resource' => 'Authorization2']
];
I tried this and I get every single resource but I can't get parents for resources that has one:
public function array_values_recursive($array) {
$flat = array();
foreach($array as $key => $value) {
if (is_array($value)) {
$flat = array_merge($flat, $this->array_values_recursive($value));
}
else {
if($key === '#id') {
$flat[]['value'] = $value;
}
}
}
return $flat;
}
That did the job for me, thanks #Sammitch for the idea.
public function array_values_recursive($array, $parent = null) {
$flat = array();
$i = 0;
foreach($array as $key => $value) {
if (is_array($value)) {
//we create a new parent
if(array_key_exists('#id',$array)){
$flat = array_merge($flat, $this->array_values_recursive($value, $array['#id']));
}
//we keep the last parent known
else{
$flat = array_merge($flat, $this->array_values_recursive($value, $parent));
}
}
else {
if($key === '#id') {
if($parent){
$flat[$i]['value'] = $value;
$flat[$i]['parent'] = $parent;
}
else{
$flat[$i]['value'] = $value;
}
$i++;
}
}
}
return $flat;
}
Related
Please help me.. I spent a lot of time on it and I think I ran out of ideas.
I have that code for function array_to_xml:
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
if (!empty($value)) {
if( is_array($value)) {
if (!empty($value["#attributes"])) {
$subnode = $xml_data->addChild($key, $value["#value"]);
foreach ($value["#attributes"] as $key1 => $val1) {
$subnode->addAttribute($key1, $val1);
}
} else if ($key == "#value") {
foreach ($value as $attr => $attrVal) {
$subnode = $xml_data->addChild("$attr", $attrVal);
array_to_xml($attrVal, $subnode);
}
} else {
if (!empty($value) and $key != "kontakt") {
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
} else if (!empty($value) and $key == "kontakt") {
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
};
}
} else {
$xml_data->addChild("$key",$value);
}
}
}
}
When it hits the "contact" attribute, it creates the following structure:
<kontakt>
<1 typ="email">abc#abc.pl</1>
<2 typ="telefon">3123453141</2>
</kontakt>
How should the function be changed so that its result would be the structure shown below?
<kontakt typ="email">abc#abc.pl</kontakt>
<kontakt typ="telefon">3123453141</kontakt>
The array:
"kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
],
That's the only thing I'm missing for happiness ;)
something like that (i´ve rewritten your function):
<?php
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
foreach($value as $innerkey => $innervalue) {
$v = $innervalue['#value'];
$t = "";
if(isset($innervalue['#attributes']['typ'])) {
$t = $innervalue['#attributes']['typ'];
}
$kontakt = $xml_data->addChild('kontakt', $v);
$kontakt->addAttribute('typ', $t);
}
}
}
$data = array("kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
]
);
$xml_data = new SimpleXMLElement('<test></test>') ;
array_to_xml($data,$xml_data);
echo $xml_data->asXML();
results in
<?xml version="1.0"?>
<test><kontakt typ="email">abc#abc.pl</kontakt><kontakt typ="telefon">3123453141</kontakt></test>
playground: https://3v4l.org/Qo9uaU#v7.0.33
<?php
//the array
$data = array(
"kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
]);
//call to function
$xml = array_to_xml($data);
show($xml);
//function to convert the array to xml
function array_to_xml( $data) {
$xml = '';
foreach( $data as $key => $value ) {
foreach($value as $key1 => $value1){
$xml .= '<kontakt typ="'.$value1['#attributes']['typ'].'">';
$xml .= $value1['#value'].'</kontakt>'.PHP_EOL;
}
}
return $xml;
}
//function to show it in xml form
function show($data){
echo '<pre>';
echo nl2br(str_replace('<', '<', $data));
echo '</pre>';
}
?>
May this will help you. You can configure in case of multiple array. The above function will work in case of the provided string.
Before writing the recursive function it can help to line-out which cases there are in the data. I found three, and all what is needed to identify them is the key that is always string (for the recursive traversal):
key is #value -> the elements' node-value
key is #attributes -> the elements' attributes key/value map
key is the name of child elements -> list of children to add
Only the third case traverses into child nodes.
function array_to_xml(array $array, SimpleXMLElement $xml): void
{
foreach ($array as $key => $value) {
if ($key === '#value') {
$xml[0] = $value;
} elseif ($key === '#attributes') {
foreach ($value as $name => $item) {
$xml->addAttribute($name, $item);
}
} else {
foreach ($value as $item) {
array_to_xml($item, $xml->addChild($key));
}
}
}
}
It is not only far less code, the main difference is that the function operates on the element itself; it doesn't fetch the $key only to add it later, in the else case it adds it directly and passes the new child with the array.
The recursion is not scattered at mutliple places, but at a single one.
SimpleXMLElement itself can also be used to give each node actual method(s) and these automatically propagate over the whole document. Here an ad-hoc example:
(new class ('<test/>') extends SimpleXMLElement {
public function add(array $array): self
{
foreach ($array as $key => $value) match ($key) {
'#value' => $this[0] = $value,
'#attributes' => array_map($this->addAttribute(...), array_keys($value), $value),
default => array_map(fn ($child) => $this->addChild($key)?->add($child), $value),
};
return $this;
}
})->add($array)->asXML('php://output');
// <?xml version="1.0"?>
// <test><kontakt typ="email">abc#abc.pl</kontakt><kontakt typ="telefon">3123453141</kontakt></test>
The result is the same, one benefit could the that you don't need to think that much passing $xml around.
I have an array that looks like the following:
[
'applicant' => [
'user' => [
'username' => true,
'password' => true,
'data' => [
'value' => true,
'anotherValue' => true
]
]
]
]
What I want to be able to do is convert that array into an array that looks like:
[
'applicant.user.username',
'applicant.user.password',
'applicant.user.data.value',
'applicant.user.data.anotherValue'
]
Basically, I need to somehow loop through the nested array and every time a leaf node is reached, save the entire path to that node as a dot separated string.
Only keys with true as a value are leaf nodes, every other node will always be an array. How would I go about accomplishing this?
edit
This is what I have tried so far, but doesnt give the intended results:
$tree = $this->getTree(); // Returns the above nested array
$crumbs = [];
$recurse = function ($tree, &$currentTree = []) use (&$recurse, &$crumbs)
{
foreach ($tree as $branch => $value)
{
if (is_array($value))
{
$currentTree[] = $branch;
$recurse($value, $currentTree);
}
else
{
$crumbs[] = implode('.', $currentTree);
}
}
};
$recurse($tree);
This function does what you want:
function flattenArray($arr) {
$output = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
foreach(flattenArray($value) as $flattenKey => $flattenValue) {
$output["${key}.${flattenKey}"] = $flattenValue;
}
} else {
$output[$key] = $value;
}
}
return $output;
}
You can see it running here.
given a nested array of arbitrary depth like this:
$array = array(
1400=>
array(7300=>
array(
7301=> array(),
7302=> array(),
7305=> array(
7306=>array()
),
),
7314=>array()
),
);
how would one get the hierarchy of keys for any key.
for example:
getkeys(7305);
should return 1400,7300,7305 in that order
or
getkeys(7314);
should return 1400,7314
all array keys are unique values
Using RecursiveIteratorIterator
$array = array(
1400 => array(
7300 => array(
7301=> array(),
7302 => array(),
7305 => array(
7306=>array()
),
),
7314=>array()
),
);
function getKeys($key, $array) {
$found_path = [];
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::SELF_FIRST);
foreach ($ritit as $leafValue) {
$path = array();
foreach (range(0, $ritit->getDepth()) as $depth) {
$path[] = $ritit->getSubIterator($depth)->key();
}
if (end($path) == $key) {
$found_path = $path;
break;
}
}
return $found_path;
}
print_r(getKeys(7305, $array));
// Array
// (
// [0] => 1400
// [1] => 7300
// [2] => 7305
// )
This is very interesting problem you have so I tried to make a function that will echo your keys. If this is not good enough pls let me know I can improve code. Thanks.
<?php
$a = array(
1400=>
array(7300=>
array(
7301=> array(),
7302=> array(),
7305=> array(
7306=>array()
),
),
7314=>array()
),
);
$mykey = 7306;
$level = 0;
$result = array();
$resultarray = test($a,$mykey,$level,$result);
function test($array,$mykey,$level,$result){
$level++;
foreach($array as $key => $element){
if($key == $mykey){
echo 'found';
print_r($result);
exit;
} else if(is_array($element)){
$result[$level] = $key;
$result1 = test($element,$mykey,$level,$result);
}
}
}
The idea is to check current array branch, and if the needle key isn't found, then iterate current items and check their array child nodes by recursive function calls. Before each step down we push a current key to stack, and pop the stack if the function does not found a needle key in whole branch. So if the key found, the function returns true by the chain, preserving successful keys in the stack.
function branchTraversing(& $branch, & $key_stack, $needle_key) {
$found = false;
if (!array_key_exists($needle_key, $branch)) {
reset($branch);
while (!$found && (list($key, $next_branch) = each($branch))) {
if (is_array($next_branch)) {
array_push($key_stack, $key);
$found = branchTraversing($next_branch, $key_stack, $needle_key);
if (!$found) {
array_pop($key_stack);
}
}
}
} else {
array_push($key_stack, $needle_key);
$found = true;
}
return $found;
}
function getPath(& $array, $needle_key) {
$path = [];
branchTraversing($array, $path, $needle_key);
return $path;
}
$test_keys = [1400, 7300, 7302, 7306, 7314, 666];
foreach ($test_keys as $search_key) {
echo '<p>' . $search_key . ' => [ '
. implode(', ', getPath($array, $search_key)) . ' ]</p>';
}
I have an array that looks like the following:
[
'applicant' => [
'user' => [
'username' => true,
'password' => true,
'data' => [
'value' => true,
'anotherValue' => true
]
]
]
]
What I want to be able to do is convert that array into an array that looks like:
[
'applicant.user.username',
'applicant.user.password',
'applicant.user.data.value',
'applicant.user.data.anotherValue'
]
Basically, I need to somehow loop through the nested array and every time a leaf node is reached, save the entire path to that node as a dot separated string.
Only keys with true as a value are leaf nodes, every other node will always be an array. How would I go about accomplishing this?
edit
This is what I have tried so far, but doesnt give the intended results:
$tree = $this->getTree(); // Returns the above nested array
$crumbs = [];
$recurse = function ($tree, &$currentTree = []) use (&$recurse, &$crumbs)
{
foreach ($tree as $branch => $value)
{
if (is_array($value))
{
$currentTree[] = $branch;
$recurse($value, $currentTree);
}
else
{
$crumbs[] = implode('.', $currentTree);
}
}
};
$recurse($tree);
This function does what you want:
function flattenArray($arr) {
$output = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
foreach(flattenArray($value) as $flattenKey => $flattenValue) {
$output["${key}.${flattenKey}"] = $flattenValue;
}
} else {
$output[$key] = $value;
}
}
return $output;
}
You can see it running here.
I use PHP and mySQL with Idiorm. That might not be relevant.
My PHP array
It's a relationship between parents and childs.
0 is the root parent.
Example: Root parent 0 have the child 33 which have the child 27 which have
the child 71.
This array structure can be changed if needed for solving the problem.
array (
33 =>
array (
0 => '27',
1 => '41',
),
27 =>
array (
0 => '64',
1 => '71',
),
0 =>
array (
0 => '28',
1 => '29',
2 => '33',
),
)
My hierarchical result
Something like this, but as an array...
0 =>
28
29
33
27 =>
64
71
41
Information
The depth are unkown and it can be unlimited. I tried foreach, but it might not be the way.
My own thoughts
Some recursive function?
Some while loops?
I tried both of the above, just got a mess. It's a brainer.
The suggestion by #deceze worked. However the input array needs to change a litte, like this...
$rows = array(
array(
'id' => 33,
'parent_id' => 0,
),
array(
'id' => 34,
'parent_id' => 0,
),
array(
'id' => 27,
'parent_id' => 33,
),
array(
'id' => 17,
'parent_id' => 27,
),
);
From https://stackoverflow.com/a/8587437/476:
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['parent_id'] == $parentId) {
$children = buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
$tree = buildTree($rows);
print_r( $tree );
I added to #Jens Törnell's answers to enable defining the options for the column name of parent_id, the children array key name, and also the column name for id.
/**
* function buildTree
* #param array $elements
* #param array $options['parent_id_column_name', 'children_key_name', 'id_column_name']
* #param int $parentId
* #return array
*/
function buildTree(array $elements, $options = [
'parent_id_column_name' => 'parent_id',
'children_key_name' => 'children',
'id_column_name' => 'id'], $parentId = 0)
{
$branch = array();
foreach ($elements as $element) {
if ($element[$options['parent_id_column_name']] == $parentId) {
$children = buildTree($elements, $options, $element[$options['id_column_name']]);
if ($children) {
$element[$options['children_key_name']] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
Since the functionality is quite universal, I managed to use the above function in most of my projects.
great answer from #Jens Törnell, just wanted to add a little improvement that if your parent_id and id is actually string instead of number then above method will fail and after creating children array, it will create those childrens arrays again as separate individual array. In order to fix that you should do triple equal check and by telling data type of variable i.e (string) in comparison.
For string based Id and Parent_id in array
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ((string)$element['parent_id'] === (string)$parentId) {
$children = buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
additionally if someone desire, he can add a third parameter to function as well to specify data type of variables dynamically i.e function buildTree(array $elements, $parentId = 0, $datatype='string') but then you will have to take of any other error occur.
hope it will help someone!
public function createTree (&$list, $parentId = null) {
$tree = array();
foreach ($list as $key => $eachNode) {
if ($eachNode['parentId'] == $parentId) {
$eachNode['children'] = $this->createTree ($list,$eachNode['id']);
$tree[] = $eachNode;
unset($list[$key]);
}
}
return $tree;
}
In that function pass the associative array and if the most parent is not null then just pass the most parent id as second argument.
I had a different problem and could not find a solution that worked for me on this page. I needed to create a tree but without knowing the root id.
This means I have to go through my flat array and build branches with the most parently items at the top of the tree.
If anyone else needs to build a tree without a root parent item id, here's how I did it.
<?php
$rows = [
(object) [
'id' => 1001,
'parentid' => 1000,
'name' => 'test1.1'
],
(object) [
'id' => 1000,
'parentid' => 100,
'name' => 'test1'
],
(object) [
'id' => 1002,
'parentid' => 1000,
'name' => 'test1.2'
],
(object) [
'id' => 1004,
'parentid' => 1001,
'name' => 'test1.1.1'
],
(object) [
'id' => 1005,
'parentid' => 1004,
'name' => 'test1.1.1.1'
],
(object) [
'id' => 100,
'parentid' => 10,
'name' => 'test 0'
],
(object) [
'id' => 1006,
'parentid' => 1002,
'name' => 'test1.2.1'
],
(object) [
'id' => 1007,
'parentid' => 1002,
'name' => 'test1.2.2'
],
];
function add_child(stdClass $parent, stdClass $child) {
if ($child->parentid != $parent->id) {
throw new Exception('Attempting to add child to wrong parent');
}
if (empty($parent->children)) {
$parent->children = [];
} else {
// Deal where already in branch.
foreach ($parent->children as $idx => $chd) {
if ($chd->id === $child->id) {
if (empty($chd->children)) {
// Go with $child, since $chd has no children.
$parent->children[$idx] = $child;
return;
} else {
if (empty($child->children)) {
// Already has this child with children.
// Nothing to do.
return;
} else {
// Both childs have children - merge them.
$chd->children += $child->children;
$parent->children[$idx] = $child;
return;
}
}
}
}
}
$parent->children[] = $child;
}
function build_branch(&$branch, &$rows, &$parent = null) {
$hitbottom = false;
while (!$hitbottom) {
$foundsomething = false;
// Pass 1 - find children.
$removals = []; // Indexes of rows to remove after this loop.
foreach ($rows as $idx => $row) {
if ($row->parentid === $branch->id) {
// Found a child.
$foundsomething = true;
// Recurse - find children of this child.
build_branch($row, $rows, $branch);
add_child($branch, $row);
$removals[] = $idx;
}
}
foreach ($removals as $idx) {
unset($rows[$idx]);
}
// Pass 2 - find parents.
if ($parent === null) {
$foundparent = false;
foreach ($rows as $idx => $row) {
if ($row->id === $branch->parentid) {
// Found parent
$foundsomething = true;
$foundparent = true;
add_child($row, $branch);
unset ($rows[$idx]);
// Now the branch needs to become the parent since parent contains branch.
$branch = $row;
// No need to search for other parents of this branch.
break;
}
}
}
$hitbottom = !$foundsomething;
}
}
function build_tree(array $rows) {
$tree = [];
while (!empty($rows)) {
$row = array_shift($rows);
build_branch($row, $rows);
$tree[] = $row;
}
return $tree;
}
$tree = build_tree($rows);
print_r($tree);