Remove deeply nested element from multi-dimensional array? - php

I need to remove an element form a deeply nested array of unknown structure (i.e. I do not know what the key sequence would be to address the element in order to unset it). The element I am removing however does have a consistent structure (stdObject), so I can search the entire multidimensional array to find it, but then it must be removed. Thoughts on how to accomplish this?
EDIT: This is the function I have right now trying to achieve this.
function _subqueue_filter_reference(&$where)
{
foreach ($where as $key => $value) {
if (is_array($value))
{
foreach ($value as $filter_key => $filter)
{
if (isset($filter['field']) && is_string($filter['field']) && $filter['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
unset($value[$filter_key]);
return TRUE;
}
}
return _subqueue_filter_reference($value);
}
}
return FALSE;
}
EDIT #2: Snipped of array structure from var_dump.
array (size=1)
1 =>
array (size=3)
'conditions' =>
array (size=5)
0 =>
array (size=3)
...
1 =>
array (size=3)
...
2 =>
array (size=3)
...
3 =>
array (size=3)
...
4 =>
array (size=3)
...
'args' =>
array (size=0)
empty
'type' => string 'AND' (length=3)
...so assuming that this entire structure is assigned to $array, the element I need to remove is $array[1]['conditions'][4] where that target is an array with three fields:
field
value
operator
...all of which are string values.

This is just a cursor problem.
function recursive_unset(&$array)
{
foreach ($array as $key => &$value) # See the added & here.
{
if(is_array($value))
{
if(isset($value['field']) && $value['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
unset($array[$key]);
}
recursive_unset($value);
}
}
}
Notes : you don't need to use is_string here, you can just make the comparison as you're comparing to a string and the value exists.
Don't use return unless you're sure there is only one occurrence of your value.
Edit :
Here is a complete example with an array similar to what you showed :
$test = array (
1 => array (
'conditions' =>
array (
0 => array ('field' => 'dont_care1', 'value' => 'test', 'operator' => 'whatever'),
1 => array ('field' => 'dont_care2', 'value' => 'test', 'operator' => 'whatever'),
2 => array ('field' => 'nodequeue_nodes_node__nodequeue_subqueue.reference', 'value' => 'test', 'operator' => 'whatever'),
3 => array ('field' => 'dont_care3', 'value' => 'test', 'operator' => 'whatever')
),
'args' => array (),
'type' => 'AND'
));
var_dump($test);
function recursive_unset(&$array)
{
foreach ($array as $key => &$value)
{
if(is_array($value))
{
if(isset($value['field']) && $value['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
unset($array[$key]);
}
recursive_unset($value);
}
}
}
recursive_unset($test);
var_dump($test);

One way to solve this was to extend your recursive function with a second parameter:
function _subqueue_filter_reference(&$where, $keyPath = array())
You'd still do the initial call the same way, but the internal call to itself would be this:
return _subqueue_filter_reference($value, array_merge($keyPath, array($key)));
This would provide you with the full path of keys to reach the current part of the array in the $keyPath variable. You can then use this in your unset. If you're feeling really dirty, you might even use eval for this as a valid shortcut, since the source of the input you'd give it would be fully within your control.
Edit: On another note, it may not be a good idea to delete items from the array while you're looping over it. I'm not sure how a foreach compiles but if you get weird errors you may want to separate your finding logic from the deleting logic.

I have arrived at a solution that is a spin-off of the function found at http://www.php.net/manual/en/function.array-search.php#79535 (array_search documentation).
Code:
function _subqueue_filter_reference($haystack,&$tree=array(),$index="")
{
// dpm($haystack);
if (is_array($haystack))
{
$result = array();
if (count($tree)==0)
{
$tree = array() + $haystack;
}
foreach($haystack as $k=>$current)
{
if (is_array($current))
{
if (isset($current['field']) && is_string($current['field']) && $current['field'] == 'nodequeue_nodes_node__nodequeue_subqueue.reference')
{
eval("unset(\$tree{$index}[{$k}]);"); // unset all elements = empty array
}
_subqueue_filter_reference($current,$tree,$index."[$k]");
}
}
}
return $tree;
}
I hate having to use eval as it SCREAMS of a giant, gaping security hole, but it's pretty secure and the values being called in eval are generated explicitly by Drupal core and Views. I'm okay with using it for now.
Anyway, when I return the tree I simply replace the old array with the newly returned tree array. Works like a charm.

Related

How to see if an array of associative arrays is empty in php

I have a fairly easy issue where I need to see if an associative array of arrays is empty in php. My array looks like this:
array (
'person1' =>
array (
),
'person2' =>
array (
),
'person3' =>
array (
),
)
In my case, the three array's for the three people holds nothing so I need a test whether this is empty. I have done this which works:
if ( empty($form_values['person1']) && empty($form_values['person2']) && empty($form_values['person3'] ) ){
echo 'values empty!!';
}
But I was hoping something a bit more cleaner with using empty like the following:
if (empty( $form_values )) {
echo 'HI!';
}
You can use array_filter() to filter all of the empty array elements. You can then use empty to check if the result is empty then.
I've shorthanded the arrays so it's a easier to read since the arrays are empty. array() will work the same.
$form_values = [
'person1' => [],
'person2' => [],
'person3' => []
];
if (empty(array_filter($form_values))) {
// empty
} else {
// not empty
}
If you're looking for a one-liner then you could do something like:
$form_values = array (
'person1' =>
array (
),
'person2' =>
array (
),
'person3' =>
array (
),
);
if(array_sum(array_map(function($v){return !empty($v);}, $form_values)) === 0)
{
// empty
}
else
{
// not empty
}
Use a loop that tests each nested array. If none of them are non-empty, the whole array is empty.
$is_empty = true;
foreach ($form_values as $val) {
if (!empty($val)) {
$is_empty = false;
break;
}
}
<?php
$data =
[
'pig' => [],
'hog' => [],
'sow' => []
];
$all_empty = array_filter($data) === [];
var_dump($all_empty);
Output:
bool(true)
From the manual for array_filter:
If no callback is supplied, all empty entries of array will be
removed. See empty() for how PHP defines empty in this case.
Note that if an item was deemed as empty, like an empty string, it would still return true. This test may not be strict enough.
More explicitly:
if (array_filter($data, function($v) {return $v !== []; }) === []) {}
Filter out all items that aren't the empty array. What we'll be left with is an empty array if all items are an empty array.
Or search and compare:
if (array_keys($data, []) == array_keys($data)) {}
Check keys belonging to items containing the empty array match the keys of the array. Or rather all items (if they exist) are the empty array.
Note that an empty array will also satisfy the three solutions above.

Replace value in multidimensional php array

First, I have to say I already checked some answers but neither it wasn't exactly what I was looking for neither I couldn't fully understand the answer and how to use it.
I have this MultiDimensional array:
Array
(
[field_5abcb693a68bc] => Array
(
[0] => Array
(
[field_5abcbb1b51ddf] => mortgage
[field_5ae58a0b58b58] =>
[field_5abcbb1e51de0] => 10
[field_5abcbb2051de1] => הידגלה
[field_5abcbb2351de2] => 45,654,456
[field_5abcbb6251de3] =>
[field_5abcbb6651de4] => 04/2017
[field_5abcbb6851de5] => 4,454,656
[field_5abcbb6b51de6] => 24/07/2018
[field_5abcbbb351de7] => 657
[field_5abcbbb651de8] => 24/07/2018
[field_5abcbbb851de9] => 15
[field_5abcbbbb51dea] => yes
)
)
)
And I want to find values that much the pattern of mm/yyyy. Regex is a good option, I know how to write the condition:
if ( preg_match('/^\d[1-9]\/[1-9][0-9][0-9][0-9]$/',$v) ) {
//do stuff
}
I want to look in the inner part of the array for this pattern, and if it is a match, to change the value in the array to
$value = '01/' . $value;
means the value '04/2017' changes to '01/04/2017'.
note: there can be more than one value to change.
note: the name of the first array within the array [field_5abcb693a68bc] can vary and won't stay the same in other cases.
Thanks.
Use array_walk_recursive with reference to value as argument instead of regular value argument of callback.
It would work like that:
array_walk_recursive(
$array,
function (&$value) {
if (preg_match('/^\d[1-9]\/[1-9][0-9][0-9][0-9]$/',$value)) {
$value = '01/' . $value;
}
}
);
Check result on 3v4l.org
You want to perform a conditional replacement using a regex pattern? preg_replace() seems sensible. preg_replace() is happy to iterate a one-dimensional array, so the lowest level can be directly served to it.
Notice that I've changed the pattern delimiter to avoid escaping the forward slash. I've also made the pattern more brief by using \d and the {3} quantifier. $0 means the "fullstring match". You don't have to write the unset() call to purge those temporary variables, but some developers consider it to be best practice because it avoids potential variable conflict down-script.
Code: (Demo)
$array = [
'field_5abcb693a68bc' => [
0 => [
'field_5abcbb1b51ddf' => 'mortgage',
'field_5ae58a0b58b58' => '',
'field_5abcbb1e51de0' => '10',
'field_5abcbb2051de1' => 'הידגלה',
'field_5abcbb2351de2' => '45,654,456',
'field_5abcbb6251de3' => '',
'field_5abcbb6651de4' => '04/2017',
'field_5abcbb6851de5' => '4,454,656',
'field_5abcbb6b51de6' => '24/07/2018',
'field_5abcbbb351de7' => '657',
'field_5abcbbb651de8' => '24/07/2018',
'field_5abcbbb851de9' => '15',
'field_5abcbbbb51dea' => 'yes'
]
]
];
foreach ($array as &$set) {
foreach ($set as &$subset) {
$subset = preg_replace('~^\d[1-9]/[1-9]\d{3}$~', '01/$0', $subset);
}
}
unset($set, $subset); // avoid future variable interferences
var_export($array);
Output:
array (
'field_5abcb693a68bc' =>
array (
0 =>
array (
'field_5abcbb1b51ddf' => 'mortgage',
'field_5ae58a0b58b58' => '',
'field_5abcbb1e51de0' => '10',
'field_5abcbb2051de1' => 'הידגלה',
'field_5abcbb2351de2' => '45,654,456',
'field_5abcbb6251de3' => '',
'field_5abcbb6651de4' => '01/04/2017',
'field_5abcbb6851de5' => '4,454,656',
'field_5abcbb6b51de6' => '24/07/2018',
'field_5abcbbb351de7' => '657',
'field_5abcbbb651de8' => '24/07/2018',
'field_5abcbbb851de9' => '15',
'field_5abcbbbb51dea' => 'yes',
),
),
)
You can anonymously process down the levels of the array without actually knowing any of the names of the keys like this for example
foreach ($arr as $key => &$outer) {
foreach ($outer as &$inner) {
foreach ($inner as $k => $v) {
if ( preg_match('/^\d[1-9]\/[1-9][0-9][0-9][0-9]$/',$v) ) {
$inner[$k] = '01/' . $v;
}
}
}
}

array_diff_assoc() or foreach()? Which is faster?

I have two arrays, for example $session and $post with 100+ values. I will compare the $post array values with $session array. If post is different then it will be taken to result array else not.
We can try this using array_diff_assoc($post, $session) and foreach(). Which one is faster?
For profiling, Phil has suggested a great way in his reply, but I will link it here too, just in case:
Simplest way to profile a PHP script
Practically, you need to know what each approach does. in array_diff_assoc, you are returning the difference between 2 collections, after comparing the key/value couples for each element. It will then return an array that contains the entries from array1 that are not present in array2 or array3, etc.
In a for each loop, you will need to hard code the same function (assuming that's what you need). You will need to take the first element, then look for the combination in your other arrays. If it matches your requirements, you will save it into your output array, or even print it directly.
Same principles apply, but then again, it will be up to profiling to determine the faster approach. Try doing so on a large number of big arrays, as the difference isn't noticeable at smaller scales.
I'll leave this as a stub/example, please edit, or use for profiling.
<?php
$before = [
'name' => 'Bertie',
'age' => '23'
];
$after = [
'name' => 'Harold',
'age' => '23',
'occupation' => 'Bus driver'
];
function changed_1($after, $before) {
return array_diff_assoc($after, $before);
}
function changed_2($after, $before) {
$changed = [];
foreach($after as $k => $v) {
if(isset($before[$k]) && $before[$k] !== $v)
$changed[$k] = $v;
if(!isset($before[$k]))
$changed[$k] = $v;
}
return $changed;
}
var_export(changed_1($after, $before));
var_export(changed_2($after, $before));
Output:
array (
'name' => 'Harold',
'occupation' => 'Bus driver',
)array (
'name' => 'Harold',
'occupation' => 'Bus driver',
)

Ignore # array keys in php foreach loop

I am trying to loop over certain array keys in drupal, but this is more of a generic php array question.
The array looks something like this...
$form['items'] = array(
#title => 'hello',
0 => array(
#subtitle => 'hello2';
),
1 => array(
#subtitle => 'hello2';
),
#prefix => '<div>hello</div>',
);
As you can see, the keys are a mix of numeric keys and #meta keys.
I am using this...
foreach($form['items'] as $x) {
unset($form['items'][$x]['column1']);
}
But i only want to target the numeric keys, I have tried is_numeric but it returned false.
Can someone tell me how to ignore the other keys? (Ignore #title and #prefix etc)
You want to check the keys, but you are using the value in your foreach. Do the following:
foreach($form['items'] as $key => $value) {
if (is_numeric($key))
unset($form['items'][$key]);
}
Hope I was helpful
Use is_int() rather than is_numberic()
foreach ($input_array as $key => $val) {
if (is_int($key)) {
// do stuff
}
}
Important to note that is_int only works on things that are type integer, meaning string representations are not allowed.

confusing multidimensional array in php with foreach getting error

what is use of multidimensional array(2D,3D or what is the limit in multidimensional array) and foreach()?
foreach() is use for printing values inside array?
In case of multidimensional array why nested loop is important?
Errors:
Notice: Array to string conversion in C:\xampp\htdocs\example\basic\foreach2.php on line 9
Arrayarray(3) { [0]=> int(4) [1]=> int(5) [2]=> int(7) }
Notice: Array to string conversion in C:\xampp\htdocs\example\basic\foreach2.php on line 11
$items = array(1,2,3,
array(4,5,7
),
8,54,4,5,5);
foreach($items as $key => $value)
{
echo $value;
var_dump($value);
echo $key ."pair match".$value . "<br>";
}
HOW do I access this array?
$a_services = array(
'user-login' => array(
'operations' => array(
'retrieve' => array(
'help' => 'Retrieves a user',
'callback' => 'android',
'file' => array('type' => 'inc', 'module' => 'android_services'),
'access callback' => 'services',
'args' => array(
array(
'name' => 'phone_no',
'type' => 'string',
'description' => 'The uid ',
'source' => array('path' => 0),
'optional' => FALSE,
),
),
),
),
),
);
print_r($a_services['$android_services ']['user-login']['operations']['retrieve']['callback']);
print_r($a_services['$android_services ']['user-login']['operations']['retrieve']['callback']['args']['name']);
Error 1. Notice: Array to string conversion
2. Undefined index: $android_services
3. How to print with help of foreach
4. above array is 4 dimensional??????
To loop through this kind of array you need some sort of recursiveness.
I usually call a function inside the for each. the function tests whether the current element is an array. If so the functions call itself. If not the function does whatever (echo the value in your example).
Something like:
foreach($a_services as $key => $value) {
do_your_thing($value);
}
function do_your_thing($recvalue)
{
if (is_array($recvalue))
{
foreach($recvalue as $key => $value)
{
do_your_thing($value);
}
}
else
{
echo $recvalue;
}
return $recvalue;
}
You can define multidimension arrays (arrays including oher arrays) in PHP.
For example you have 2 different lists, one for grocery shopping, one for daily task.
$lists = array(
'grocery' => array('banana', 'apple'),
'tasks' => array('go to work', 'wash dishes'),
);
If you want to check all levels of arrays, you should use nested loops. For example you can use foreach, it will iterate over arrays first level.
foreach($lists as $list)
return $list; // returns grocery[] and tasks[]
As you see this loop returning other loop. So if you need to list all grocery[] array items, you need iterate again in this array.
foreach($lists as $list)
foreach($list as $k => $l)
if($k == 'grocery')
echo $l; // echos "banana","apple"
Nested loops are important (and necessary) to reach multidimensional arrays contents especially if you don't know structure of the array. These loops will find all array items for you. But if you know structure, you can directly use $lists['grocery'] to reach grocery array or $lists['grocery'][0] to reach first element of your grocery list.

Categories