I am writing a class to sanitize strings passed to PHP through an ajax call, when I pass a string into this class it works fine but passing the array as a reference and it won't work.
class Sanitize {
public static function clean (&$str) {
self::start($str);
}
public static function cleanArray (&$array) {
if (self::arrayCheck($array)) {
foreach ($array as $key => $value) {
if (self::arrayCheck($value)) {
self::cleanArray($value);
} else {
self::clean($value);
}
}
} else {
throw new Exception ('An array was not provided. Please try using clean() instead of cleanArray()');
}
}
private static function start (&$str) {
$str .= '_cleaned';
}
private static function arrayCheck ($array) {
return (is_array($array) && !empty($array));
}
}
Test Code:
$array = array(
'one' => 'one',
'two' => 'two',
'three' => 'three',
'four' => 'four'
);
echo print_r($array, true) . PHP_EOL;
Sanitize::cleanArray($array);
echo print_r($array, true) . PHP_EOL;
Output:
Array
(
[one] => one
[two] => two
[three] => three
[four] => four
)
Array
(
[one] => one
[two] => two
[three] => three
[four] => four
)
Is there something I am missing, or is it not possible to nest reference passes in PHP?
You lose the reference inside the foreach. Change it to this and it'll work:
foreach( $array as $key => &$value ) {
Your code does not modify the $array, it modifies $value.
There're couple of ways to get around that, one is foreach ($array as &$value), the other is modify $array[$key] inside the loop.
Related
I have a deep multidimensional array that I am needing to extract the value of a specific key. I have found that the array_walk_recursive function will be my best option. I only need the first occurrence.
My array looks like this - (except much more complicated)
Array (
[vehicle info] => Array (
[one] => Array (
[submodel] => LX
[engine] => 2.3
)
[two] => Array (
[color] => blue
[year] => 2007
[wheels] => 4
)
[three] => Array (
[submodel] => LX
[make] => Ford
[model] => F-150
[offroad] => No
)
)
)
The issue here is, submodel is in both one and three. Additionally, the array is not consistent, so I must use array_walk_recursive to search through it for the matching key, then return the value for that key.
Here is my current code -
array_walk_recursive ($array, (function ($item, $key) {
$wanted = "submodel";
if ($key === $wanted) {
echo ("$key is $item");
}
}));
The above returns submodel is LXsubmodel is LX.
Bonus Question!!
How can I search for multiple keys and return the first corresponding value for each of those? I was thinking putting all wanted keys in an array, then do a foreach loop, but don't quite know how to structure this. I am new to php.
array_walk_recursive() is the appropriate native function to call for this task. Keep track of which keys have already been declared in the result array and ensure that they are never overwritten.
Code: (Demo)
$needles = ['submodel', 'offroad'];
$result = [];
array_walk_recursive(
$array,
function($value, $key) use ($needles, &$result) {
if (
in_array($key, $needles)
&& !isset($result[$key])
) {
$result[$key] = "$key is $value";
}
}
);
var_export($result);
Output:
array (
'submodel' => 'submodel is LX',
'offroad' => 'offroad is No',
)
If your application has performance concerns, then the native function becomes less attractive because it will always iterate the entire input array's structure -- even after all sought keys are encountered. If you want to "break early" (short circuit), then you will need to design your own recursive function which will return when all sought keys are found.
Code: (Demo)
$soughtKeys = array_flip(['submodel', 'offroad']);
function earlyReturningRecursion(array $array, array $soughtKeys, array &$result = []): array
{
foreach ($array as $key => $value) {
if (!array_diff_key($soughtKeys, $result)) { // check if result is complete
return $result;
} elseif (is_array($value)) {
earlyReturningRecursion($value, $soughtKeys, $result);
} elseif (isset($soughtKeys[$key]) && !isset($result[$key])) {
$result[$key] = "$key is $value";
}
}
return $result;
}
var_export(earlyReturningRecursion($array, $soughtKeys));
// same output as the first snippet
I would start by setting the values you want to null, and then only saving them if they haven't been found yet, by checking is_null(). I haven't tested this code, but it should look something like this:
$submodel = null;
array_walk_recursive ($array, (function ($item, $key) {
$wanted = "submodel";
if ($key === $wanted && is_null($submodel)) {
echo ("$key is $item");
$submodel = $item;
}
}));
array_walk_recursive() has the defect of not allowing to return matching results however in PHP 7 you could use an anonymous function and a variable to store the matching value.
$matching = null;
$wanted = "submodel";
array_walk_recursive ($array, function ($item, $key) use ($wanted, $matching) {
if (($key === $wanted) && is_null($matching)) {
$matching = $item;
}
});
As far as there is no way to return early from array_walk_recursive(), I'd suggest to create a function to find the first occurrence of $wanted:
$arr = [
'vehicle info' => [
'one' => ['submodel' => 'LX', 'engine' => '2.3'],
'two' => ['color' => 'blue', 'year' => '2007', 'wheels' => '4'],
'three' => ['submodel' => 'LX', 'make' => 'Ford', 'model' => 'F-150', 'offroad' => 'No'],
],
];
function find($needle, $haystack, $found = '')
{
foreach ($haystack as $key => $value) {
if ($found) {
break;
}
if ($key === $needle) {
$found = "{$needle} is {$value}";
break;
}
if (is_array($value)) {
$found = find($needle, $value, $found);
}
}
return $found;
}
$wanted = 'submodel';
$result = find($wanted, $arr);
var_dump($result); // string(14) "submodel is LX"
Live demo
Update: to search for multiple keys you'll need to do it in a loop:
$multiple_keys = array('submodel', 'year');
foreach ($multiple_keys as $wanted) {
var_dump(find($wanted, $arr));
}
// Output:
// string(14) "submodel is LX"
// string(12) "year is 2007"
Live demo
I have a large data-set that I'm checking the contents of; I do this validation while creating an internal array of the data; to avoid looping over the array again later, I would like the validation to change the contents of the array.
Now the problem is that I'm calling the validation routines through call_user_func and this seems to pose some problems with passing by reference. Or maybe I'm doing something else wrong.
Here's a stripped down example:
public function index( )
{
$arr = array(
array('a' => 'aap', 'n' => 'noot', 'm' => 'mies'),
array('a' => 'ding', 'b' => 'flof', 'c' => 'bips'),
array( 'd' => 'do', 'e' => 're', 'c' => 'mi')
);
$func = array( $this, '_user_func' );
$errors = 0;
$new_arr = array();
foreach ($arr as $key => &$value) {
$new_arr[$key] = &$value; // Simulate production-code manipulation
//if ( !$this->_do_callback($func, $new_arr[$key], $key) ) $errors++; // No exception but array not modified afterwards
if ( !call_user_func( $func, $new_arr[$key], $key ) ) $errors++; // Exception: Parameter 1 to TestRef::user_func() expected to be a reference, value given
}
unset($value);
var_dump($new_arr);
print_r('Errors: '.$errors);
}
private function _do_callback( $func, array &$row, $row_id )
{
if ( is_callable( $func ) )
{
return call_user_func( $func, $row, $row_id );
}
else
{
throw new Exception( "Error doing callback. Callback empty or not a callable function." );
}
}
private function _user_func( &$arr, $index = 0 )
{
// "Validation" routine
foreach ($arr as $key => &$value) {
if ($key == 'b') return FALSE; // Simulate validation error for error count
$arr[$key] = 'replaced';
}
unset($value);
//var_dump($arr); // Works!
return TRUE;
}
i think you're trying to redefine an existing php function which is array_walk.
And especially in your case, you'll need array_walk_recursive.
Here's a rewritten (simplified ?) version of your code.
public function index( )
{
$arr = array(
array('a' => 'aap', 'n' => 'noot', 'm' => 'mies'),
array('a' => 'ding', 'b' => 'flof', 'c' => 'bips'),
array( 'd' => 'do', 'e' => 're', 'c' => 'mi')
);
$func = array( $this, '_user_func' );
var_dump($arr); // BEFORE WALK
array_walk_recursive($arr, $func);
var_dump($arr); // AFTER WALK
}
/**
* Does something to a row from an array (notice the reference)
*/
private function _user_func( &$rowValue, $rowIndex )
{
$rowValue = 'replaced';
}
You can see this code in action here --> http://ideone.com/LcZKo
Either:
Try changing your foreach loop into this:
foreach ($arr as $key => &$value) {
$this->_do_callback($func, $value, $key); // No exception but array not modified afterwards
//call_user_func( $func, $value, $key ); // Exception: Parameter 1 to TestRef::user_func() expected to be a reference, value given
}
unset($value); // avoid memory leak
Or:
Wrap the variable in an array prior to invoking call_user_func.
Have you tried it like this:?
foreach ($arr as $key => &$value) {
$this->_do_callback($func, $value, $key); // No exception but array not modified afterwards
//call_user_func( $func, $value, $key ); // Exception: Parameter 1 to TestRef::user_func() expected to be a reference, value given
}
I need to unset elements from arrays that are nested into another array, in a way that only the first N elements would be kept (N being predefined). Only elements that have a numerical index should be affected.
Input array:
Array
(
[0] => Array (
[a] => 'w'
[b] => Array (
[0]=> 'x'
[1]=> 'x'
[2]=> 'x'
)
)
[1] => Array (
[a] => 'y'
)
[2] => Array (
[0] => 'z'
[1] => 'z'
[2] => 'z'
)
)
Desired output (with N=2):
Array
(
[0] => Array (
[a] => 'w'
[b] => Array (
[0]=> 'x'
[1]=> 'x'
)
)
[1] => Array (
[a] => 'y'
)
)
Based on the above definition, only [0][b][2] and [2] got unset because they had a numerical index and because they both represnted the 3rd element of their respective array.
Haven't tested but something like this might work.
function myFunc(&$array){
foreach($array as $key=>&$value){
if(is_array($value)){
myFunc($value);
}
if(is_numeric($key) && $key > 1){
unset($array[$key]);
}
}
}
About array_walk. php.net says:
the programmer cannot add, unset or
reorder elements. If the callback does
not respect this requirement, the
behavior of this function is
undefined, and unpredictable.
Write yourself a function that does exactly what you want. Then document the function so if you need to use it in about two weeks, you might want to know what exactly that function is doing.
I say this because the data structure you want to handle seems to be very specific. So it's worth to encapsulate it in a function on it's own to hide away the complexity. Name the function properly.
Inside the function you can process the data in the various ways and with the various conditions you need to formulate. Parameters from the outside can be passed as function parameters. The return value is the result you aim to achieve.
This worked for me, perhaps not the cleanest code.
$array = array
(
array(
'a' => 'w',
'b' => array('x','x','x')
),
array(
'a' => 'y'
),
array(
'z','z','z'
)
);
function recurse_and_strip ($array, &$size=2)
{
foreach ($array as $key => &$element)
{
if (is_hash($element))
{
$element = recurse_and_strip($element,$size);
} else if (is_array($element))
{
$deletefrom = false;
// now the tricky part.. see how many must be deleted
for ($i=0; $i < count($element); $i++ )
{
if ($size == 0)
{
echo "Delete from " . $i;
$deletefrom = $i;
break 1;
}
$size--;
}
if ($deletefrom !== false)
{
if ($deletefrom == 0)
{
unset($array[$key]);
} else {
array_splice($element,$deletefrom);
}
}
}
}
return $array;
}
// source http://www.benjaminkeen.com/?p=23
function is_hash($var)
{
if (!is_array($var))
return false;
return array_keys($var) !== range(0,sizeof($var)-1);
}
var_dump(recurse_and_strip($array,2));
array_walk_recursive itself cannot achieve what you want. Even though you can pass array by reference, unsetting the variable in the callback will only unset it in that scope.
However, you can use walk_recursive_remove function:
/**
* http://uk1.php.net/array_walk_recursive implementation that is used to remove nodes from the array.
*
* #param array The input array.
* #param callable $callback Function must return boolean value indicating whether to remove the node.
* #return array
*/
function walk_recursive_remove (array $array, callable $callback) {
foreach ($array as $k => $v) {
if (is_array($v)) {
$array[$k] = walk_recursive_remove($v, $callback);
} else {
if ($callback($v, $k)) {
unset($array[$k]);
}
}
}
return $array;
}
You will need to implement your own logic using the $callback to unset the specific values.
Here's a more general solution to modifying the array to which the leaf belongs. You can unset the current key, or add siblings, etc.
/**
* Modified version of array_walk_recursive that passes in the array to the callback
* The callback can modify the array or value by specifying a reference for the parameter.
*
* #param array The input array.
* #param callable $callback($value, $key, $array)
*/
function array_walk_recursive_array(array &$array, callable $callback) {
foreach ($array as $k => &$v) {
if (is_array($v)) {
array_walk_recursive_array($v, $callback);
} else {
$callback($v, $k, $array);
}
}
}
I have a result set as an array from a database that looks like:
array (
0 => array (
"a" => "something"
"b" => "something"
"c" => "something"
)
1 => array (
"a" => "something"
"b" => "something"
"c" => "something"
)
2 => array (
"a" => "something"
"b" => "something"
"c" => "something"
)
)
How would I apply a function to replace the values of an array only on the array key with b? Normally I would just rebuild a new array with a foreach loop and apply the function if the array key is b, but I'm not sure if it's the best way. I've tried taking a look at many array functions and it seemed like array_walk_recursive is something I might use, but I didn't have luck in getting it to do what I want. If I'm not describing it well enough, basically I want to be able to do as the code below does:
$arr = array();
foreach ($result as $key => $value)
{
foreach ($value as $key2 => $value2)
{
$arr[$key][$key2] = ($key2 == 'b' ? $this->_my_method($value2) : $value2);
}
}
Should I stick with that, or is there a better way?
Using array_walk_recursive:
If you have PHP >= 5.3.0 (for anonymous functions):
array_walk_recursive($result, function (&$item, $key) {
if ($key == 'b') {
$item = 'the key is b!';
}
});
Otherwise something like:
function _my_method(&$item, $key) {
if ($key == 'b') {
$item = 'the key is b!';
}
}
array_walk_recursive($result, '_my_method');
Untested but I think this will work.
function replace_b (&$arr)
{
foreach ($arr as $k => $v)
{
if ($k == 'b')
{
/* Do something */
}
if (is_array($v)
{
replace_b($arr[$k]);
}
}
}
The function will move through an array checking the keys for b. If the key points to an array it recursively follows it down.
use array_walk_recursive documented here
$replacer = function($x) {return "I used to be called $x";}; //put what you need here
$replaceB = function(&$v, $k) use ($replacer) {if ($k === 'b') $v = $replacer($v);};
array_walk_recursive($arr, $replaceB);
The replacer function might be overkill. You can replace it with a literal or anything you like.
I have a nested array in which I want to display a subset of results. For example, on the array below I want to loop through all the values in nested array[1].
Array
(
[0] => Array
(
[0] => one
[1] => Array
(
[0] => 1
[1] => 2
[2] => 3
)
)
[1] => Array
(
[0] => two
[1] => Array
(
[0] => 4
[1] => 5
[2] => 6
)
)
[2] => Array
(
[0] => three
[1] => Array
(
[0] => 7
[1] => 8
[2] => 9
)
)
)
I was trying to use the foreach function but I cannot seem to get this to work. This was my original syntax (though I realise it is wrong).
$tmpArray = array(array("one",array(1,2,3)),array("two",array(4,5,6)),array("three",array(7,8,9)));
foreach ($tmpArray[1] as $value) {
echo $value;
}
I was trying to avoid a variable compare on whether the key is the same as the key I want to search, i.e.
foreach ($tmpArray as $key => $value) {
if ($key == 1) {
echo $value;
}
}
Any ideas?
If you know the number of levels in nested arrays you can simply do nested loops. Like so:
// Scan through outer loop
foreach ($tmpArray as $innerArray) {
// Check type
if (is_array($innerArray)){
// Scan through inner loop
foreach ($innerArray as $value) {
echo $value;
}
}else{
// one, two, three
echo $innerArray;
}
}
if you do not know the depth of array you need to use recursion. See example below:
// Multi-dementional Source Array
$tmpArray = array(
array("one", array(1, 2, 3)),
array("two", array(4, 5, 6)),
array("three", array(
7,
8,
array("four", 9, 10)
))
);
// Output array
displayArrayRecursively($tmpArray);
/**
* Recursive function to display members of array with indentation
*
* #param array $arr Array to process
* #param string $indent indentation string
*/
function displayArrayRecursively($arr, $indent='') {
if ($arr) {
foreach ($arr as $value) {
if (is_array($value)) {
//
displayArrayRecursively($value, $indent . '--');
} else {
// Output
echo "$indent $value \n";
}
}
}
}
The code below with display only nested array with values for your specific case (3rd level only)
$tmpArray = array(
array("one", array(1, 2, 3)),
array("two", array(4, 5, 6)),
array("three", array(7, 8, 9))
);
// Scan through outer loop
foreach ($tmpArray as $inner) {
// Check type
if (is_array($inner)) {
// Scan through inner loop
foreach ($inner[1] as $value) {
echo "$value \n";
}
}
}
foreach ($tmpArray as $innerArray) {
// Check type
if (is_array($innerArray)){
// Scan through inner loop
foreach ($innerArray as $value) {
echo $value;
}
}else{
// one, two, three
echo $innerArray;
}
}
Both syntaxes are correct. But the result would be Array. You probably want to do something like this:
foreach ($tmpArray[1] as $value) {
echo $value[0];
foreach($value[1] as $val){
echo $val;
}
}
This will print out the string "two" ($value[0]) and the integers 4, 5 and 6 from the array ($value[1]).
As I understand , all of previous answers , does not make an Array output,
In my case :
I have a model with parent-children structure (simplified code here):
public function parent(){
return $this->belongsTo('App\Models\Accounting\accounting_coding', 'parent_id');
}
public function children()
{
return $this->hasMany('App\Models\Accounting\accounting_coding', 'parent_id');
}
and if you want to have all of children IDs as an Array , This approach is fine and working for me :
public function allChildren()
{
$allChildren = [];
if ($this->has_branch) {
foreach ($this->children as $child) {
$subChildren = $child->allChildren();
if (count($subChildren) == 1) {
$allChildren [] = $subChildren[0];
} else if (count($subChildren) > 1) {
$allChildren += $subChildren;
}
}
}
$allChildren [] = $this->id;//adds self Id to children Id list
return $allChildren;
}
the allChildren() returns , all of childrens as a simple Array .
I had a nested array of values and needed to make sure that none of those values contained &, so I created a recursive function.
function escape($value)
{
// return result for non-arrays
if (!is_array($value)) {
return str_replace('&', '&', $value);
}
// here we handle arrays
foreach ($value as $key => $item) {
$value[$key] = escape($item);
}
return $value;
}
// example usage
$array = ['A' => '&', 'B' => 'Test'];
$result = escape($array);
print_r($result);
// $result: ['A' => '&', 'B' => 'Test'];
array(
"IT"=>
array(
array('id'=>888,'First_name'=>'Raahul','Last_name'=>'Pandey'),
array('id'=>656,'First_name'=>'Ravi','Last_name'=>'Teja'),
array('id'=>998,'First_name'=>'HRX','Last_name'=>'HRITHIK')
),
// array(
"DS"=>
array(
array('id'=>87,'First_name'=>'kalia','Last_name'=>'Pandey'),
array('id'=>6576,'First_name'=>'Raunk','Last_name'=>'Teja'),
array('id'=>9987,'First_name'=>'Krish','Last_name'=>'HRITHIK')
)
// )
)
);
// echo "";
// print_r($a);
echo "";
print_r($a);
?>
////how to get id in place of index value....