I have the following php array structure:
$r = [
[
'id' => 'abc',
'children' => [
[
'id' => 'def',
'children' => []
],
[
'id' => 'ghi',
'children' => [
[
'id' => 'jkl',
'children' => []
],
[
'id' => 'mno',
'children' => []
]
]
]
]
]
]
and a function to search for a parent like:
function &getElementByUuid($element, $uuid){
foreach($element as $child){
if($child['id'] == $uuid){
return $child;
}
if(isset($child['children'])){
if($childFound = $this->getElementByUuid($child['children'], $uuid)){
return $childFound;
}
}
}
return false;
}
calling this by
getElementByUuid($r, 'ghi');
Searching already works perfectly, since it returns the parent of the element, I want to add childs to.
But I need to get the found parent array element as reference so I can add array elements to it.
Like:
$parent = getElementByUuid($r, 'ghi');
$parent['children'][] = [
'id' => 'xyz',
'children' => []
];
But I cannot get the parent element as reference, though I marked the method with & to return the reference, not the value.
Any help on this would be great.
Thanks in advance :)
You need to walk array by reference too and add ampersand before calling the function too. Here is small example, how to return by reference: https://3v4l.org/7seON
<?php
$ar = [1,2,3,4];
function &refS(&$ar, $v) {
foreach ($ar as &$i) {
if ($i === $v) {
return $i;
}
}
}
$x = &refS($ar, 2);
var_dump($x);
$x = 22;
var_dump($ar);
I am just plain stupid...
Call:
$parent =& $this->getElementByUuid($tree, $parentId);
and method should look like this:
function &getElementByUuid(&$element, $uuid){
foreach($element as &$child){
if($child['id'] == $uuid){
return $child;
}
if(isset($child['children'])){
if($childFound =& $this->getElementByUuid($child['children'], $uuid)){
return $childFound;
}
}
}
return false;
}
Else, php creates copy of the values and iterates through the values, return a reference to the copy, not the reference to the refence.
I hope this might help someone else ;)
Related
Given the below code, how do I add an if statement inside obj so that the final PHP result does not display a field if it has a NULL value? For example, if id_info is NULL, then do not display "id":[id_info] in the actual PHP page? I couldn't find this info anywhere in the documentation.
<?php
$id_info = ($db->query("SomeSQL query")->fetch_assoc())['id'];
$name_info = ....;
//some more queries
$obj = (object) ["id" => strval($id_info),
"Name" => (object) [
"eng_name" => strval($name_info)
]];
echo json_encode($obj);
?>
As mentioned by knittl you could check if a specific value is null and not add it to your object.
If it is necessary though to dynamically create objects withouth the hustle of checking. You have to use array_filter or any other custom filtering function for that.
I wrote a custom filtering function that accepts a deeply nested array and returns it back filtered.
function arrayFilter($inputArr){
$output = null;
if (is_array($inputArr)){
foreach ($inputArr as $key=>$val){
if(!$inputArr[$key]) continue;
if (is_array($val)) {
$tmpArr = arrayFilter($val);
if($tmpArr) $output[$key] = array_filter($tmpArr);
}
else $output[$key] = $val;
}
} else {
$output[$key] = $val;
}
return $output;
}
So, lets say you have a deeply nested object similar to the one you provided
$obj = (object) [
"id" => null,
"Name" => (object) [
"eng_name" => strval('some name2'),
"de_name" => null,
"more" => (object) [
"fr_name" => strval('some name3'),
"ru_name" => null,
]
]
];
Below i convert the stdClass object you have to an array with json_encode and json_decode and if you pass it as a parameter into the function like:
$filtered = arrayFilter(json_decode(json_encode($obj), true));
Your output will be something like the following:
{
"Name":{
"eng_name":"some name2",
"more":{
"fr_name":"some name3"
}
}
}
Simply don't add it to the object in the first place:
$obj = [ "Name" => [ "eng_name" => strval($name_info) ] ];
if ($id_info != null) {
$obj["id"] = strval($id_info);
}
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 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.
How do I recursively get value from array where I need to explode a key?
I know, it's not good the question, let me explain.
I got an array
[
"abc" => "def",
"hij" => [
"klm" => "nop",
"qrs" => [
"tuv" => "wxy"
]
]
]
So, inside a function, I pass:
function xget($section) {
return $this->yarray["hij"][$section];
}
But when I want to get tuv value with this function, I want to make section as array, example:
To get hij.klm value (nop), I would do xget('klm'), but to get hij.klm.qrs.tuv, I can't do xget(['qrs', 'tuv']), because PHP consider $section as key, and does not recursively explode it. There's any way to do it without using some ifs and $section[$i] ?
function xget($section) {
return $this->yarray["hij"][$section];
}
that one is static function right?
you can do that also for this
function xget($section) {
if(isset($this->yarray["hij"][$section])){
return $this->yarray["hij"][$section];
}elseif(isset($this->yarray["hij"]["klm"]["qrs"][$section])){
return $this->yarray["hij"]["klm"]["qrs"][$section];
}
}
as long as the key name between two of them are not the same.
You could use array_walk_recursive to find tuv's value regardless of the nested structure:
$tuv_val='';
function find_tuv($k,$v)
{
global $tuv_val;
if ($k=='tuv')
$tuv_val=$v;
}
array_walk_recursive($this->yarray,"find_tuv");
echo "the value of 'tuv' is $tuv_val";
try my code
<?php
$array = array(
'aaa' => 'zxc',
'bbb' => 'asd',
'ccc' => array(
'ddd' => 'qwe',
'eee' => 'tyu',
'fff' => array(
'ggg' => 'uio',
'hhh' => 'hjk',
'iii' => 'bnm',
),
),
);
$find = '';
function xget($key){
$GLOBALS['find'] = $key;
$find = $key;
array_walk_recursive($GLOBALS['array'],'walkingRecursive');
}
function walkingRecursive($value, $key)
{
if ($key==$GLOBALS['find']){
echo $value;
}
}
xget('ggg');
?>
Ok, I'm really stucked with this. I hope you can help me.
I have my class, used to manage hierarchical data. The input is a plain array with the following structure (just an example):
$list = array(
(object) array('id' => 1, 'nombre' => 'Cámaras de fotos', 'parentId' => null),
(object) array('id' => 2, 'nombre' => 'Lentes', 'parentId' => null),
(object) array('id' => 3, 'nombre' => 'Zoom', 'parentId' => 2),
(object) array('id' => 4, 'nombre' => 'SLR', 'parentId' => 1),
(object) array('id' => 5, 'nombre' => 'Primarios', 'parentId' => 2),
(object) array('id' => 6, 'nombre' => 'Sensor APS-C', 'parentId' => 4),
(object) array('id' => 7, 'nombre' => 'Full-frame', 'parentId' => 4),
(object) array('id' => 8, 'nombre' => 'Flashes', 'parentId' => null),
(object) array('id' => 9, 'nombre' => 'Compactas', 'parentId' => 1)
);
I input the data to the class this way:
$Hierarchical = new Hierarchical;
$Hierarchical->plain = $list;
Then I have a public function (createTree) to create a multidimensional array representation of the list. It works perfectly. It can return the result or store it inside $this->tree.
As you can see, this is very simple. It calls private function iterateTree, which is the recursive function.
class Hierarchical {
public $plain = array();
public $tree = array();
public function createTree($parentId=0, $return=false) {
$tree = $this->iterateTree($parentId);
if(!$return) {
$this->tree = $tree;
} else {
return $tree;
}
}
private function iterateTree($parentId) {
$resArray = array();
foreach($this->plain as $item) {
if($item->parentId == $parentId) {
$children = $this->iterateTree($item->id);
if( count($children) > 0 ) {
$item->children = $children;
}
$resArray[] = $item;
}
}
return $resArray;
}
}
So far so good. It works fine.
BUT... The problem appears when I want to use $this->plain after calling createTree(). Instead of returning the original dataset, it returns some kind of mix between the original input, with all their children appended (similar to $this->tree).
I can't figure out why the content of $this->plain is being changed, neither in the both functions used I'm changing it's content.
I've tried unseting the variables inside the foreach, after the foreach, even passing the original array as an argument and not using $this->plain at all inside the recursive function. Nothing worked.
I'm also not using any other function inside the class that could change it's value.
It's a total mistery!
In your foreach loop $item will be a reference to the object in the array, so you are changing that same object in the line
$item->children = $children;
This will affect the object referred to in the original arrays $list and $this->plain.
One solution may be to clone $item within your foreach loop.
According to Doug's answer, the correct function is: (added $itemAux = clone $item)
private function iterateTree($parentId) {
$resArray = array();
foreach($this->plain as $item) {
$itemAux = clone $item;
if($itemAux->parentId == $parentId) {
$children = $this->iterateTree($itemAux->id);
if( count($children) > 0 ) {
$itemAux->children = $children;
}
$resArray[] = $itemAux;
}
}
return $resArray;
}
To add to Doug's answer, although the manual says that "objects are not passed by reference" (http://www.php.net/manual/en/language.oop5.references.php), it may instead help to think of objects as a completely separate entity from any variables that may "contain" them, and that they are actually passed everywhere by reference...
class testClass
{
public $var1 = 1;
}
function testFunc($obj)
{
$obj->var1 = 2;
}
$t = new testClass;
testFunc($t);
echo $t->var1; // 2
So when you do $item->children = $children;, you are in fact affecting each original object in that $plain array.