Create Multidimensional Tree Object From Array In PHP - php

I need to take an array that turns the key (split by /) of each element into a child array, and assigns the data in the right format in the new array.
There can be multiple levels of nesting, realistically never more then 10, but that is to be decided.
For example;
given the input of
$i_have_this = [
"Base/child" => [
[
"filename" => "child-1",
"last_modified" => "29/01/2020"
],
[
"filename" => "child-2",
"last_modified" => "29/01/2020"
],
[
"filename" => "child-3",
"last_modified" => "29/01/2020"
]
],
"Base/child/grandChild1" => [
[
"filename" => "grandChild1-1",
"last_modified" => "29/01/2020"
]
],
"Base/child/grandChild2" => [
[
"filename" => "grandChild2-1",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-2",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-3",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-4",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-5",
"last_modified" => "29/01/2020"
]
]
];
I would like the output of
$want_this = [
'name' => 'Base',
'children' => [
[
'name' => 'child',
'children' => [
["name" => "child-1"],
["name" => "child-2"],
["name" => "child-3"],
[
"name" => "grandChild1",
"children" => [
["name" => "grandChild1-1"]
]
],
[
"name" => "grandChild2",
"children" => [
["name" => "grandChild2-1"],
["name" => "grandChild2-2"],
["name" => "grandChild2-3"],
["name" => "grandChild2-4"]
]
],
]
]
]
];
So far I have;
foreach($i_have_this as $path => $value) {
$temp = &$want_this;
foreach (explode('/', $path) as $key) {
$temp = &$temp[$key];
}
$temp = $value;
}
but can't quite finish it off.
Example code run here

I think you could treat this the same way that most use "dot" notation for arrays (like in Laravel). Just replace "." with "/" in your case:
Example Code
function unflatten($data) {
$output = [];
foreach ($data as $key => $value) {
$parts = explode('/', $key);
$nested = &$output;
while (count($parts) > 1) {
$nested = &$nested[array_shift($parts)];
if (!is_array($nested)) $nested = [];
}
$nested[array_shift($parts)] = $value;
}
return $output;
}
print_r(unflatten($i_have_this));

Related

PHP : Recursively add parant child to the multi dimension array

I have a function which gives an array of the below format
$result = [
[
"name" => "text",
"id" => "928610",
"entity_type" => "node"
],
[
"name" => "folder",
"id" => "987620",
"entity_type" => "folder"
],
[
"name" => "text",
"id" => "956720",
"entity_type" => "node"
],
];
Each Folder "entity_type" => "folder" item has again child which returns same format array.
like if we run a foreach loop $result and if it is "entity_type" => "folder" then we pass the id to a function it will also give a similar array format as that of result.
So i need if it is "entity_type" => "folder" the below key added to the "entity_type" => "folder" item
"children" => [
'#theme' => 'child_elements',
'#child_elements' => [
[
'name' => 'text',
"id" => "333421",
"entity_type" => "node"
],
[
'name' => 'folder',
"id" => "897622",
"entity_type" => "folder"
],
[
'name' => 'text',
"id" => "342214",
"entity_type" => "node"
],
],
],
and recursively it should keep on adding if "entity_type" => "folder"
The final array should be
$result = [
[
"name" => "text",
"id" => "928610",
"entity_type" => "node"
],
[
"name" => "folder",
"id" => "987620",
"entity_type" => "folder"
"children" => [
'#theme' => 'child_elements',
'#child_elements' => [
[
'name' => 'text',
"id" => "333421",
"entity_type" => "node"
],
[
'name' => 'folder',
"id" => "897622",
"entity_type" => "folder"
],
[
'name' => 'text',
"id" => "342214",
"entity_type" => "node"
],
],
],
],
[
"name" => "text",
"id" => "956720",
"entity_type" => "node"
],
];
public function buildTree($elements) {
$branch = [];
$branch = ['#theme' => 'child_elements'];
foreach ($elements as $key => $element) {
foreach($element as $keys => $values){
if ($element['bundle'] == 'folder') {
$child = $this->loadElements($element['id']);
$branch['#child_elements'] = $child;
$element[$key]['children'] = $branch['#child_elements'];
$this->buildTree($child);
array_push($branch, $element);
}else{
$branch['#child_elements'][] = $element;
}
}
}
return $branch;
}
you need to use some logic statement and use array_push when the condition is met.

Multidimensional array using objects

Is it possible to stop the loop from overwriting the previous value?
<?php
foreach (array('email1', 'email2') as $line) {
$x = [
$line => [
"Reports" => [
(object) [
"ReportType" => "1",
"SummaryFrequency" => [
(object) [
"FrequencyType" => "8011",
"SecondsPast" => "32400",
],
],
"Filter" => (object) [
"ClauseType" => "or",
"RuleField" => "",
"RuleOperator" => "",
"RuleValue" => "",
"ClauseChildren" => [
(object) [
"ClauseType" => "",
"RuleField" => "BackupJobDetail.TimeSinceStarted",
"RuleOperator" => "int_lte",
"RuleValue" => "86400",
],
],
],
],
],
],
];
}
print_r($x);
https://phpize.online/?phpses=e658de0de3dc1ed5a4d8d27ecebf567a&sqlses=null&php_version=php8&sql_version=mysql57
At the moment you overwrite $x each time, if you want to make it an array with $line as each index, create an empty array and then add the new items in with new index...
$x = [];
foreach (array('email1', 'email2') as $line) {
$x[$line] = [
"Reports" => [
(object) [

How to create an array tree based on path-style values?

I have a problem with multidimensional data arrays. I have a data array like this:
[
[
"name" => "netSnmp",
"oid" => "1.3.6.1.4.1.8072"
"status" => "current"
], [
"name" => "netSnmpObjects",
"oid" => "1.3.6.1.4.1.8072.1"
], [
"name" => "netSnmpEnumerations",
"oid" => "1.3.6.1.4.1.8072.3"
], [
"name" => "netSnmpModuleIDs",
"oid" => "1.3.6.1.4.1.8072.3.1"
], [
"name" => "netSnmpAgentOIDs",
"oid" => "1.3.6.1.4.1.8072.3.2"
], [
"name" => "netSnmpDomains",
"oid" => "1.3.6.1.4.1.8072.3.3"
], [
"name" => "netSnmpNotificationPrefix",
"oid" => "1.3.6.1.4.1.8072.4"
], [
"name" => "netSnmpNotifications",
"oid" => "1.3.6.1.4.1.8072.4.0"
], [
"name" => "netSnmpNotificationObjects",
"oid" => "1.3.6.1.4.1.8072.4.1"
]
]
I am looking for a simple way to create an array tree, based on the oid value from the array above. Those oid values are a dot-separated path. The more parts, the deeper in the final tree the corresponding item should be put.
The desired output:
[
"text" => "netSnmp",
"oid" => "1.3.6.1.4.1.8072",
"nodes" => [
[
"oid" => "1.3.6.1.4.1.8072.1",
"text" => "netSnmpObjects"
], [
"oid" => "1.3.6.1.4.1.8072.3",
"text" => "netSnmpEnumerations",
"nodes" => [
[
"text" => "netSnmpModuleIDs",
"oid" => "1.3.6.1.4.1.8072.3.1"
], [
"text" => "netSnmpAgentOIDs",
"oid" => "1.3.6.1.4.1.8072.3.2"
], [
"text" => "netSnmpDomains",
"oid" => "1.3.6.1.4.1.8072.3.3"
]
]
], [
"oid" => "1.3.6.1.4.1.8072.4",
"text" => "netSnmpNotificationPrefix"
"nodes" => [
[
"text" => "netSnmpNotifications",
"oid" => "1.3.6.1.4.1.8072.4.0"
], [
"text" => "netSnmpNotificationObjects",
"oid" => "1.3.6.1.4.1.8072.4.1"
]
]
]
]
]
Any idea how I can solve it?
You can key your data by oid in a new associated array: that way you can look up a certain parent key in a fast way.
Then it is just a matter of getting the parent key of a any given key (chopping off the final dot-separated part) and see where you have it in your new associated array, and then appending it to its nodes array.
Here is a function that does that:
function makeTree($arr) {
$keyed = [];
foreach ($arr as $item) $keyed[$item["oid"]] = $item;
$root = null;
foreach ($keyed as &$item) {
$key = $item["oid"];
$parent = substr($key, 0, strrpos($key, "."));
if (isset($keyed[$parent])) {
$keyed[$parent]["nodes"][] =& $item;
} else {
$root = $key;
}
}
return $keyed[$root];
}
You can run it like this on your sample data:
$arr = [
[
"name" => "netSnmp",
"oid" => "1.3.6.1.4.1.8072",
"status" => "current"
], [
"name" => "netSnmpObjects",
"oid" => "1.3.6.1.4.1.8072.1"
], [
"name" => "netSnmpEnumerations",
"oid" => "1.3.6.1.4.1.8072.3"
], [
"name" => "netSnmpModuleIDs",
"oid" => "1.3.6.1.4.1.8072.3.1"
], [
"name" => "netSnmpAgentOIDs",
"oid" => "1.3.6.1.4.1.8072.3.2"
], [
"name" => "netSnmpDomains",
"oid" => "1.3.6.1.4.1.8072.3.3"
], [
"name" => "netSnmpNotificationPrefix",
"oid" => "1.3.6.1.4.1.8072.4"
], [
"name" => "netSnmpNotifications",
"oid" => "1.3.6.1.4.1.8072.4.0"
], [
"name" => "netSnmpNotificationObjects",
"oid" => "1.3.6.1.4.1.8072.4.1"
]
];
$result = makeTree($arr);
print_r($result);

Removing part of an array from a multidimensional nested array

I've a four levels of nested array like this:
$array = [
[
'website' => [
'id' => 'one'
],
'children' => [
[
'website' => [
'id' => 'one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.one.one.two'
],
'children' => []
]
]
],
[
'website' => [
'id' => 'one.one.two'
],
'children' => [
[
'website' => [
'id' => 'one.one.two.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.one.two.two'
],
'children' => []
]
]
]
]
],
[
'website' => [
'id' => 'one.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.one'
],
'children' => [
[
'website' => [
'id' => 'one.two.one.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.two.one.two'
],
'children' => []
]
]
],
[
'website' => [
'id' => 'one.two.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.two.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.two.two.two'
],
'children' => []
]
]
]
]
]
]
]
];
now, I'd like to remove some of the array element based on some rules. For simplicity, let's say, we'd like to remove array elements that has 'id' equals to one.one.two or one.two.one.
I was looking into some answers provided on stackoverflow and was trying to apply them to solve my issue, but didn't go much. The most chalanging thing is to unset part of an array where it's not the farthest point in the array.
I was trying this to note at which level I want to delete the arrays and then trying to unset them using those array index.
$pointer = [];
foreach($array as $key => $level1) {
$level1pointer = $key;
$pointer[] = markToDelete($level1, $level1pointer);
foreach($level1['children'] as $key => $level2) {
$level2pointer = $key;
$pointer[] = markToDelete($level2, $level1pointer, $level2pointer);
foreach($level2['children'] as $key => $level3) {
$level3pointer = $key;
$pointer[] = markToDelete($level3, $level1pointer, $level2pointer, $level3pointer);
foreach($level3['children'] as $key => $level4) {
$level4pointer = $key;
$pointer[] = markToDelete($level4, $level1pointer, $level2pointer, $level3pointer, $level4pointer);
}
}
}
}
function markToDelete($array, $level1 = null, $level2 = null, $level3 = null, $level4 = null) {
$exclusionList = [
'one.one.two',
'one.two.one'
];
if (!empty($array['website']) && in_array($array['website']['id'], $exclusionList)) {
print_r('marking for deletion: '. $array['website']['id'] . PHP_EOL);
return [
'id' => $array['website']['id'],
'level1' => $level1,
'level2' => $level2,
'level3' => $level3,
'level4' => $level4
];
}
return [];
}
I was also trying to use Iterator like this:
$it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array), \RecursiveIteratorIterator::LEAVES_ONLY);
$newArray = [];
foreach($it as $key => $value) {
$exclusionList = [
'one.one.two',
'one.two.one'
];
if(!in_array($value, $exclusionList)) {
print_r(sprintf('value: %s is ready to be deleted', $value).PHP_EOL);
$newArray[] = $value;
}
}
but I need a way to unset the array when looping through the iterator.
I'd like to get an output like this:
$array = [
[
'website' => [
'id' => 'one'
],
'children' => [
[
'website' => [
'id' => 'one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one'
],
'children' => [
[
'website' => [
'id' => 'one.one.one.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.one.one.two'
],
'children' => []
]
]
],
]
],
[
'website' => [
'id' => 'one.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.two'
],
'children' => [
[
'website' => [
'id' => 'one.two.two.one'
],
'children' => []
],
[
'website' => [
'id' => 'one.two.two.two'
],
'children' => []
]
]
]
]
]
]
]
];
I'd really appreciate help on how to resolve this in more efficient way. Thanks.
Here's a recursive function that will do what you want. It traverses the array, looking for children whose website id is in the list of ids to be removed and unsetting them. Note that because of your additional top-level array, you need to iterate the function over those values.
function delete_entries(&$array, $ids_to_delete) {
foreach ($array['children'] as $index => &$child) {
if (in_array($child['website']['id'], $ids_to_delete)) {
unset($array['children'][$index]);
}
delete_entries($child, $ids_to_delete);
}
}
foreach ($array as &$arr) {
delete_entries($arr, array('one.one.two', 'one.two.one'));
}
var_export($array);
The output is as you desire but too long to reproduce here. See the demo on 3v4l.org
Update
The above code won't delete entries on the top level because the array structure is different from lower levels in the array. That can be dealt with in the outer foreach loop:
$excluded = array('two', 'one.one.two', 'one.two.one');
foreach ($array as $key => &$arr) {
if (in_array($arr['website']['id'], $excluded)) {
unset($array[$key]);
}
else {
delete_entries($arr, $excluded);
}
}
Updated demo

PHP multi dimensional array find all unique paths

I have a PHP multi-dimensional array organized with each node listing its parent nodes under it. I'm trying to transform the array so that the output lists hierarchically with each node listing any child nodes and only listing unique paths within the array.
for instance this input array:
$input = [
[
"name" => "home",
"parents" => [],
],
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "newsdetail",
"parents" => [
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "knowledge",
"parents" => [],
],
];
Should output this array:
$output = [
[
"name" => "home",
"children" => [
[
"name" => "newslist",
"children" => [
[
"name" => "newsdetail",
"children" => [],
],
],
],
],
],
[
"name" => "knowledge",
"children" => [],
],
];
This could probably be done in a much nicer way, but this method works. just procedural functions as a proof of concept.
<?php
$input = [
[
"name" => "home",
"parents" => [],
],
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "newsdetail",
"parents" => [
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "knowledge",
"parents" => [],
],
];
//recursively get all parents and the level the parent is at
function getParents($nodes,$level,&$parents)
{
foreach($nodes AS $key => $node)
{
$parents[ $node['name'] ] = array( "name" => $node['name'], "level" => $level);
if(isset($node['parents']) && !empty($node['parents']))
{
$level += 1;
getParents($node['parents'],$level,$parents);
}
}
}
//sort the parents by level
function sortParentsByLevel($a, $b)
{
if ($a['level'] == $b['level']) {
return 0;
}
return ($a['level'] > $b['level']) ? -1 : 1;
}
//find the output path based on parents array to add new value to
function setValueFromPath(&$paths, $parents, $value)
{
$dest = &$paths;
if(empty($parents))
{
if(!isset($dest[$value]))
$dest[$value] = array();
} else {
$finalNode = array_pop($parents);
foreach ($parents as $parent)
{
$dest = &$dest[$parent];
}
$dest[$finalNode][$value] = array();
}
}
//init new variable
$output = array();
//loop through each input node
foreach($input AS $key => $node)
{
//init a parent array
$parents = array();
//if we have parents use the getParents method to set them
if(isset($node['parents']) && is_array($node['parents']) && !empty($node['parents']))
{
getParents($node['parents'],1,$parents);
}
//sort the parents according to their level
uasort($parents, 'sortParentsByLevel');
//we're only interested in the associative key
$parentKeys = array();
foreach($parents AS $parent)
{
$parentKeys[] = $parent['name'];
}
//add the $node['name'] value in the appropriate parent array
setValueFromPath($output, $parentKeys, $node['name'] );
}
echo '<pre>';
print_r($output);
echo '</pre>';
die();
/*
Array
(
[home] => Array
(
[newslist] => Array
(
[newsdetail] => Array
(
)
)
)
[knowledge] => Array
(
)
)
*/

Categories