PHP seems very inconsistent and (compiler) fails to generate the logic. Let's start our investigation, first with a simple set of JSON data.
[
{
"customer": "cust01",
"assembly": "assem01",
"date_received": "02-08-2015",
"time_received": "09:15",
"date_completed": "02-23-2015",
"time_completed": "10:27"
},
{
"customer": "lov_01",
"assembly": "lov_02",
"date_received": "lov_03",
"time_received": "lov_04",
"date_completed": "lov05",
"time_completed": "lov_06"
}
]
Then in the PHP, we retrieve an array of that data
$t_json_string = file_get_contents($t_json_file_path);
$t_json_arr = json_decode($t_json_string, true);
Assume we retrieve post values in an array like this
$t_new_entry = [
"customer" => "lov_01",
"assembly" => "lov_02",
"date_received" => "lov_03",
"time_received" => "lov_04",
"time_completed"=> "lov_05",
"time_completed"=> "lov_06"
];
and the goal is to verify as if new entry exists in the json array yet, by a condition whether both arrays have more than 2 similar values, for that I'm using $t_count to count the number of similar occurrences.
I wrote up 2 methods for checking that while passing the same data into the data pool.
// $t_boo = $db_entry_check($t_new_entry, $t_json_arr); echo $t_boo;
// true, $t_count shows 3.
$t_bool = $db_entry_exist($t_new_entry, $t_json_arr); echo $t_bool;
// False. It has to be true with the $t_count printed out at 3.
The first one employs call_user_function_array, which I tested and it works so I commented it out. Code for it here:
$db_entry_check = function($needle, $haystack){
$t_exist = 'false';
$t_count = 0;
function h_loop (&$t_count, $value, $array){
foreach ($array as $key => $val){
if (is_array($val)){
h_loop($t_count, $value, $val);
} else {
echo "<br/> --- value: ". $value. "<br/> --- val: ". $val . "<br/><br/>";
if ($val === $value){
$t_count += 1;
echo "<br/>" . $t_count . "<br/>";
continue;
}
}
}
}
function n_loop (&$t_count, $arr, $array){
foreach ($arr as $key => $value){
if (is_array($value)){
n_loop($t_count, $value, $array);
} else {
if ($t_count > 2) continue;
call_user_func_array('h_loop', [&$t_count, $value, $array]);
}
}
}
n_loop($t_count, $needle, $haystack);
echo "<br/>" . $t_count . "<br/>";
if ($t_count > 2) $t_exist = 'true';
return $t_exist;
};
The second one is my attempt to use lambdas on every component functions. I tried playing around putting $value, $array, and $t_count into use() part as those variables exist within the scope of $db_entry_exist for data binding & dependencies injection. When it comes to considering parameters (for the function) vs dependencies (for the use) Of the h_loop, I find it confusing, what an entire mess in PHP efforts to implement concepts of Javascript.
No matter what parameters I am passing onto the function part and no matter what variables got injected in the use() part. Many variations have been tested but none of them work. I usually get an error of 'Function name must be a string'. Invoking a closure within another closure in PHP seems not working as the logic in Javascript. It fails me whenever I tries to pass $h_loop($t_count, $value, $array); or echo $factorial(5); in the else part of the n_loop function. What I don't understand is that $db_entry_exist itself is a lambda (Closure as what PHP calls it) and n_loop function can be called inside without any error but calling/invoking a grandchild (h_loop) function by the same approach does not work, often resulting in the same error above.
$db_entry_exist = function($needle, $haystack){
$t_exist = 'false';
$t_count = 0;
// n_loop($t_count, $needle, $haystack);
$h_loop = function (&$t_count, $value, $array) use (&$h_loop) {
foreach ($array as $key => $val){
if (is_array($val)){
h_loop($t_count, $value, $val);
} else {
echo "<br/> --- value: ". $value. "<br/> --- val: ". $val . "<br/><br/>";
if ($val === $value){
$t_count += 1;
echo "<br/>" . $t_count . "<br/>";
continue;
}
}
}
};
$factoral = function($n) use (&$factoral) {
if ($n <= 1)
return 1;
else
return $n * $factoral($n - 1);
}; // source: https://gist.github.com/superic/8290704
$n_loop = function (&$t_count, $arr, $array) use (&$n_loop) {
foreach ($arr as $key => $value){
if (is_array($value)){
$n_loop($t_count, $value, $array);
} else {
if ($t_count > 2) continue;
$h_loop($t_count, $value, $array);
}
}
};
/*$n_loop = function ($arr, $array) use (&$n_loop, &$t_count){
// echo "<br/> --- nloop.t_count: " . $t_count . "<br/>";
foreach ($arr as $key => $value){
if (is_array($value)){
$n_loop($value);
} else {
if ($t_count > 2) continue;
// $h_loop($value, $array);
}
}
};*/
$n_loop($t_count, $needle, $haystack);
echo "<br/>" . $t_count . "<br/>";
if ($t_count > 2) $t_exist = 'true';
return $t_exist;
};
and here is the link to view my entire code:
<script src="http://ideone.com/e.js/YjLkZF" type="text/javascript" ></script>
To sum up, there are primarily 2 issues I don't understand and can't figure:
$n_loop is invoked fine within $db_entry_exist method but $h_loop isn't.
In the context of $db_entry_exist, how to pass and pass what variables to the function() and pass what as dependencies to the use() part.
$n_loop = function (&$t_count, $arr, $array) use (&$n_loop){}
// ------ OR ------- many other variations are there too.
$n_loop = function ($arr, $array) use (&$n_loop, &$t_count){}
Please investigate the code and let me know your thoughts. Thank you.
You have two misconceptions in your code that are affecting your understanding.
First: PHP does not actually have nested functions. When you say:
function outer()
{
function foo() {}
function bar() {}
}
what you are really saying is, when outer() is called, define foo() and bar() in the global scope. This means that once you call outer() once, anyone (not just outer()) can call foo() and bar(). This also means that calling outer() a second time results in a Cannot redeclare foo() error.
Second: Closures in PHP do not automatically close over any variables in their parent scope. Any variables intended to be part of the closure must be explicitly included in the use() list. This means that when you write:
$n_loop = function (&$t_count, $arr, $array) use (&$n_loop) {
//...
$h_loop($t_count, $value, $array);
//...
};
the call to $h_loop will always fail, because in the scope of that function, there is no variable named $h_loop. If you add $h_loop to your use() list, then you will be able to call it as expected.
Related
I'm working with a JSON string. I'm converting it to an associative array to find specific values and change those values when a certain key is found (['content']). The depth of the array is always unknown and will always vary.
Here is the function I wrote. It takes an array as an argument and passes it by reference so that the variable itself is modified rather than a copy of it scoped locally to that function.
$json_array = json_decode($json_string, true);
function replace_data(&$json_array, $data='REPLACE TEST')
{
foreach($json_array as $key => $value) {
if ($key == 'content' && !is_array($value)) {
$json_array[$key] = $data;
} else {
if (is_array($value)) {
replace_data($value, $data);
}
}
}
}
replace_data($json_array, "test test test");
var_dump($json_array);
What I'm expecting to happen is every time a key ['content'] is found at no matter what depth, it replaces with that value specified in the $data argument.
But, when I var_dump($json_array) Those values are unchanged.
What am I missing?
With array_walk_recursive:
function replace_data($json_array, $data = 'REPLACE TEST') {
array_walk_recursive($json_array, function (&$value, $key) use ($data) {
if (!is_array($value) && $key === 'content') {
// $value passed by reference
$value = $data;
}
});
return $json_array;
}
And without references:
function replace_data($json_array, $data = 'REPLACE TEST') {
foreach ($json_array as $key => $value) {
if (is_array($value)) {
$json_array[$key] = replace_data($value, $data);
} elseif ($key === 'content') {
$json_array[$key] = $data;
}
}
return $json_array;
}
To expand on my comment, you need another reference here:
foreach($json_array as $key => &$value) {
That way, a reference to the original value is passed when you make the recursive call, rather than the local copy created with the foreach loop.
From the PHP manual entry for foreach:
In order to be able to directly modify array elements within the loop precede $value with &. In that case the value will be assigned by reference.
You'll also see in the manual that it recommends unsetting the reference to $value after the loop. Even though it probably won't cause any problems if you don't do that in your function, it's best to be in the habit of always unsetting references created in foreach loops like that. It can cause some strange looking problems if you don't.
From PHP7.4, "arrow function" syntax offers a clean and short approach.
As leafnodes are iterated, if the key is content, then replace the text, otherwise do not change the value.
There are no returns being used. All mutations are applied directly to the passed in variables prefixed with &.
function replace_data(&$array, $replaceWith = 'REPLACE TEST')
{
array_walk_recursive(
$array,
fn(&$v, $k) => $v = ($k === 'content' ? $replaceWith : $v)
);
}
replace_data($json_array, "test test test");
var_export($json_array);
I'm curious, how does the PHP's function extract do it's work? I would like to make a slightly modified version. I want my function to make the variable names when extracting from the keys of the array from snake notation to camelCase e.g:
Now extract does this:
$array = ['foo_bar' => 'baz'];
extract($array);
// $foo_bar = 'baz';
What I would like is:
camelExtract($array);
// $fooBar = 'baz';
Now I could of course camelCase the array first, but it would be nice if this could be done in a single function.
edit:
It seems some people misread my question. Yes I could do this:
function camelExtract($array)
{
$array = ['foo_bar' => 'baz'];
$camelCased = [];
foreach($array as $key => $val)
{
$camelCased[camelcase($key)] = $val;
}
extract($camelCased);
// $fooBar = 'baz';
// I can't "return" the extracted variables here
// .. now $fooBar is only available in this scope
}
camelExtract($array);
// Not here
But as I've stated, then the $fooBar is only visible within that scope.
I guess I could do something as extract(camelCaseArray($array)); and that would work.
This should work:-
function camel(array $arr)
{
foreach($arr as $a => $b)
{
$a = lcfirst(str_replace(" ", "", ucwords(str_replace("_", " ", $a))));
$GLOBALS[$a] = $b;
}
}
You can (cautiously) use variable variables:
function camelExtract($vals = array()) {
foreach ($vals as $key => $v) {
$splitVar = explode('_', $key);
$first = true;
foreach ($splitVar as &$word) {
if (!$first) {
$word = ucfirst($word);
}
$first = false;
}
$key = implode('', $splitVar);
global ${$key};
${$key} = $v;
}
}
This has now been tested and functions as expected. This condensed answer (after it addressed the lowercase first word) also works great and is much more condensed - mine is just a little more of a "step by step" to work through how the camel is done.
extract, and modification to the callees local symbol table from within a called function is magic. There is no way to perform the equivalent in plain-PHP without using it.
The final task can be solved using John Conde's suggesting of using extra after performing a transformation to the supplied array keys; although my recommendation is to avoid extract-like behavior entirely. The approach would then look similar to
extract(camelcase_keys($arr));
where such code is not wrapped in a function so that extract is executed from the scope of the symbol table in which to import the variables.
This extract behavior is is unlike variable-variables (in a called function) and is unlike using $GLOBALS as it mutates the callees (and only the callees) symbol table as see seen in this demo:
function extract_container () {
extract(array("foo" => "bar"));
return $foo;
}
echo "Extract: " . extract_container() . "\n"; // "bar" =>
echo "Current: " . $foo . "\n"; // => {no $foo in scope}
echo "Global: " . $GLOBALS['foo'] . "\n"; // => {no 'foo' in GLOBALS}
The C implementation for extract can be found in ext/standard/array.c. This behavior is allowed because the native function does not create a new/local PHP symbol table for itself; as such it is allowed to (trivially) modify the symbol table of the calling PHP context.
<?php
$arr = array('foo_bar'=>'smth');
function camelExtract($arr) {
foreach($arr as $k=>$v) {
$newName = lcfirst(str_replace(" ","",ucwords(str_replace("_"," ",$k))));
global $$newName;
$$newName = $v;
//var_dump($newName,$$newName);
}
}
camelExtract($arr);
?>
or just like (t's what you suggest, and better to mimic the original extract)
$camelArray[lcfirst(str_replace(" ","",ucwords(str_replace("_"," ",$k))))] = $v;
and extract on the resulting camelArray
Hi I have a PHP array with a variable number of keys (keys are 0,1,2,3,4.. etc)
I want to process the first value differently, and then the rest of the values the same.
What's the best way to do this?
$first = array_shift($array);
// do something with $first
foreach ($array as $key => $value) {
// do something with $key and $value
}
I would do this:
$firstDone = FALSE;
foreach ($array as $value) {
if (!$firstDone) {
// Process first value here
$firstDone = TRUE;
} else {
// Process other values here
}
}
...but whether that is the best way is debatable. I would use foreach over any other method, because then it does not matter what the keys are.
Here is one way:
$first = true;
foreach($array as $key => $value) {
if ($first) {
// something different
$first = false;
}
else {
// regular logic
}
}
$i = 0;
foreach($ur_array as $key => $val) {
if($i == 0) {
//first index
}
else {
//do something else
}
$i++;
}
I would do it like this if you're sure the array contains at least one entry:
processFirst($myArray[0]);
for ($i=1; $i<count($myArray); $1++)
{
processRest($myArray[$i]);
}
Otherwise you'll need to test this before processing the first element
I've made you a function!
function arrayCallback(&$array) {
$callbacks = func_get_args(); // get all arguments
array_shift($callbacks); // remove first element, we only want the callbacks
$callbackindex = 0;
foreach($array as $value) {
// call callback
$callbacks[$callbackindex]($value);
// make sure it keeps using last callback in case the array is bigger than the amount of callbacks
if(count($callbacks) > $callbackindex + 1) {
$callbackindex++;
}
}
}
If you call this function, it accepts an array and infinite callback arguments. When the array is bigger than the amount of supplied functions, it stays at the last function.
You can simply call it like this:
arrayCallback($array, function($value) {
print 'callback one: ' . $value;
}, function($value) {
print 'callback two: ' . $value;
});
EDIT
If you wish to avoid using a function like this, feel free to pick any of the other correct answers. It's just what you prefer really. If you're repeatedly are planning to loop through one or multiple arrays with different callbacks I suggest to use a function to re-use code. (I'm an optimisation freak)
I want to apply a function to each element/prop of an object but it seems array_walk_recursive() does not work on object. i.e:
if( $re = $con->query("SELECT id, created_date, contents FROM " .
POST_DATA . " WHERE type = 'news' ORDER BY ".
"created_date DESC LIMIT $amount") ) {
if( $re->num_rows != 0 ) {
while( $ob = $re->fetch_object() ) {
$ob = array_walk_recursive( $ob, "_output" );
print_r($ob);
die();
}
}
}
would simply return '1'.
How might I resolve this?
It's actually returning a value of True for array_walk_recursive. If you look at the function's documentation, you'll see that what this method is doing is calling the function _output for each item and key in the object.
You should also have some code that looks similar to this, I would imagine, to get it to work correctly:
function _output($data, $key) {
echo "For the key $key, I got the data: ";
print_r($data);
}
Where _output is called because that is the stringified name that you gave in the array_walk_recursive function. That should print your values to the screen.
Edit:
It seems that I'm not actually answering what you were originally wanting to do, though. If you're wanting to apply a function to every element of an array, I would suggest that you look at array_map. You can use array_map like this:
function double($item) {
return 2 * $item;
}
array_map('double', $item);
Ultimately, if the recursion is something that you desire, you could probably do something like this:
function callback($key, $value) {
// do some stuff
}
function array_map_recursive($callback, $array) {
$new_array = array()
foreach($array as $key => $value) {
if (is_array($value)) {
$new_array[$key] = array_map_recursive($callback, $value);
} else {
$new_array[$key] = call_user_func($callback, $key, $value);
}
}
return $new_array;
}
array_map_recursive('callback', $obj);
That would return another array like $obj, but with whatever the callback was supposed to do.
I have a function
function me($a, $b, $c, $d) {
}
I want to add all this in array by array_push is there a variable enable me do this in one step.
I want echo all this on ($a,$b,$c,$d) by foreach
I don't know it i will assume any variable in () will equal $anything
function me($a,$b,$c,$d){
foreach ($anything as $key => $value){
echo $value; // i want return $a,$b,$c,$d values
}
}
Any one understand what I want? I want foreach the function and I cant explain because I don't understand
function(void){
foreach(void) { }
}
I want foreach all variables between () OK in function**(void)**{
I guess that you are talking about variable number of arguments. This example below is from the php site and uses func_num_args to get the actual number of arguments, and func_get_args which returns an array with the actual arguments:
function me()
{
$numargs = func_num_args();
echo "Number of arguments: $numargs<br />\n";
if ($numargs >= 2) {
echo "Second argument is: " . func_get_arg(1) . "<br />\n";
}
$arg_list = func_get_args();
for ($i = 0; $i < $numargs; $i++) {
echo "Argument $i is: " . $arg_list[$i] . "<br />\n";
}
}
me (1, 2, 3);
You could try func_get_args(). Calling that will give you an array (numerically indexed) containing all of the parameter values.
I don't really understand what you are asking, though.
func_get_args() returns all arguments passed to the function.
You can use func_get_args() like Jasonbar says:
function a() {
foreach (func_get_args() as $param) {
echo $param;
}
}
a(1,2,3,4); // prints 1234
a(1,2,3,4,5,6,7); // prints 1234567