I am building a recursive function, which executes any function for any-deep-nested-array. For example, I want to STRIPSLASH all array values in this:
function RECURSER($array,$function_name){
return is_array($array) ? array_map('RECURSER', $array, $function_name) : $function_name($array);
}
but when I execute:
recursive_for_array_value( $MY_ARRAY, 'stripslashes')
the above function can't send second parameter to array_map.
Now I'll start off by saying that I'd probably never use this in any real projects, but this is an interesting challenge/question and T.Todua's answer works but using $GLOBALS can be avoided.
My position is that array_walk_recursive() is a better suited function versus recursively calling array_map() -- after all, array_walk_recursive() was specifically designed to visit leaf nodes and avoid the tedium of checking the current item's type as "array". use() is effective in passing the function string into the recursive function's scope.
*Note: You could only pass the function string as a string in a SUPER fringe case where the function prints to screen AND requires two arguments -- the first arg being the element value and the second arg being the element key.
Because you want to only process the element values AND modify them by reference, &$v is necessary.
Here is a relevant post to read regarding checking the dynamic function name: What exactly is the difference between the is_callable and function_exists in PHP?
Here is my working alternative:
Code: (Demo)
$multidim_array = ['a' => [' \one ', ['b' => 'two\\', [['c' => 'thr\ee']]]]];
$func = 'stripslashes';
if (function_exists($func)) {
array_walk_recursive($multidim_array, function(&$v)use($func){$v = $func($v);});
var_export($multidim_array);
} else {
echo "not callable";
}
If you wanted to go down this rabbit hole further, you could extend its potential utility by setting up the option to pass multiple arguments:
Code: (Demo)
$func = 'strpos';
if (function_exists($func)) {
$more = true;
$param2 = 'o';
array_walk_recursive($multidim_array, function(&$v)use($func, $more, $param2) {
if ($more) {
$v = $func($v, $param2);
} else {
$v = $func($v);
}
});
var_export($multidim_array);
} else {
echo "um... I'm not calling $func";
}
Finally, the approach that I whole-heartedly do NOT endorse is the use of eval() -- because you can see the tail, horns, and pitchfork a mile away.
Caution
The eval() language construct is very dangerous because it allows execution of arbitrary PHP code. Its use thus is discouraged. If you have carefully verified that there is no other option than to use this construct, pay special attention not to pass any user provided data into it without properly validating it beforehand.
This works, but really should not be entertained:
if (function_exists($func)) {
array_walk_recursive($multidim_array, function(&$v)use($func) {eval("\$v = $func(\$v);"); });
var_export($multidim_array);
} else {
echo "um... I'm not calling $func";
}
RECURSOR of any function:
$result= Recursiver_of_Array($array, 'stripslashes');
code:
function Recursiver_of_Array($array,$function_name=false){
if ($function_name) { $GLOBALS['current_func_name']= $function_name; }
return is_array($array) ? array_map('Recursiver_of_Array', $array) : $GLOBALS['current_func_name']($array);
}
array_map accepts one function and multiple arrays as arguments. Perhaps you need to recursively call recurser via an anonymous function instead.
function RECURSER($array,$function_name){
if (is_array($array))
return array_map(function ($element) use ($function_name) {
return RECURSER($element,$function_name);
},$array);
return $function_name($array);
}
The usecase of stripslashes as a one-line PHP function can be written as :
array_walk_recursive($array, function (&$value) { $value = stripslashes($value); });
Related
My problem is that I have lots of functions with VERY long lists of function parameters such as this one:
function select_items($con,$type,$id_item,$item_timestamp,$item_source_url,$item_type,$item_status,$item_blogged_status,$item_viewcount,$item_language,$item_difficulty,$item_sharecount,$item_pincount,$item_commentcount,$item_mainpage,$item_image_width,$item_image_height,$item_image_color,$item_modtime,$order,$start,$limit,$keyword,$language,$id_author,$id_sub_category,$id_category,$id_tag,$id_user){ ... }
As you can see its super long and (of course) very hard to maintain. Sometimes I need all of the variables to construct a super complex sql query, but sometimes I just use 1 or 2 of them. Is there a way to avoid this colossal list of parameters? For example with some strict / special naming convention ?
So basically I need something like this:
$strictly_the_same_param_name="It's working!";
echo hello($strictly_the_same_param_name);
function hello() //<- no, or flexible list of variables
{
return $strictly_the_same_param_name; // but still able to recognize the incoming value
}
// outputs: It's working!
I thought about using $_GLOBALs / global or $_SESSIONs to solve this problem but it doesn't seems really professional to me. Or is it?
For a first step, as you said, sometimes you need to call the function with only 2 args, you can set default values to your arguments in the declaration of your function. This will allow you to call your function with only 2 args out of 25.
For example:
function foo($mandatory_arg1, $optional_arg = null, $opt_arg2 = "blog_post") {
// do something
}
In a second step, you can use, and especially for that case, arrays, it will be way more simple:
function foo(Array $params) {
// then here test your keys / values
}
In a third step, you can also use Variable-length argument lists (search in the page "..."):
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
But ultimately, I think you should use objects to handle such things ;)
You can try use ... token:
$strictly_the_same_param_name= ["It's working!"];
echo hello($strictly_the_same_param_name);
function hello(...$args) //<- no, or flexible list of variables
{
if ( is_array( $args ) {
$key = array_search( 'What you need', $args );
if ( $key !== false ) {
return $args[$key];
}
}
return 'Default value or something else';
}
When dealing with arrays I am forced to add a bunch of repetitive code to handle arrays with one child versus multiple:
//If more than one step, process each step, elcse processs single
if(!array_key_exists('command',$pullcase['steps']['step'])) {
foreach($pullcase['steps']['step'] as $step) {
$command=$step['command'];
$parameter=$step['parameter'];
if(isset($step['value'])){
$value = $step['value'];
$this->runCommands($command,$parameter,$value);
} else {
$this->runCommands($command,$parameter);
}
}
} else {
$command = $pullcase['steps']['step']['command'];
$parameter = $pullcase['steps']['step']['parameter'];
if(isset($pullcase['steps']['step']['value'])){
$value = $pullcase['steps']['step']['value'];
$this->runCommands($command,$parameter,$value);
}
else { $this->runCommands($command,$parameter); }
}
As you can see, I'm having to duplicate my efforts depending on if there is a single item in an array versus multiple:
$pullcase['steps']['step'][0]['command']
vs
$pullcase['steps']['step']['command']
How can I simplify this code so that I can use a single variable for all instances?
If you control the creation of the array, make step an array of one even if there is only one so you always have an array. Is that possible?
You either have a step array [step][0][command] or you have a single step [step][command]. So when you create the array instead of [step][command] make it [step][0][command] etc. Standard way of doing it, problem solved as you only need the foreach.
If you can't do it at array creation then consider doing it before the loop:
if(is_array($pullcase['steps']['step'])) {
$steps = $pullcase['steps']['step'];
} else {
$steps[] = $pullcase['steps']['step'];
}
foreach($steps as $step) {
$value = isset($step['value']) ? $step['value'] : null;
$this->runCommands($step['command'], $step['parameter'], $value);
}
Also, if runCommands() can detect a empty argument, then an alternative to the if/else for the function call is used above.
The following may help. It doesn't do much but call the function "runcommands" on a value if the key is 'command'. I am using it to show how you can use array_walk_recursive to possibly solve your problem.
First, you need the function to use:
function runcommandswhencommand($value, $key)
{
if($key == 'command') runcommands($value);
}
Now, you can use the recursive walk on your array:
array_walk_recursive($pullcase, 'runcommandswhencommand');
With this, whenever the key is 'command', the value of that index will be used in the parameter of the function runcommands().
Say I have an array of tags
$all_tags = array('A', 'B', 'C');
And I want to create a set of URLs with $_GET variables.
I'd like the links to be:
'A' linking to "index.php?x[]=B&x[]=C"
'B' linking to "index.php?x[]=A&x[]=C"
etc. ($_GET is an array with all elements except for "current" element)
(I know there's an easier way to implement this: I'm actually simplifying a more complex situation)
I'd like to use array_filter() to solve this.
Here's my attempt:
function make_get ($tag) { return 'x[]=' . $tag; }
function tag_to_url ($tag_name) {
global $all_tags;
$filta = create_function('$x', 'global $all_tags; return ($x != $tag_name);');
return 'index.php?' . implode('&', array_map("make_get", array_filter($all_tags, "filta")));
}
print_r(array_map("", $all_tags));
But it doesn't work. I have a suspicion that maybe it has to do with how maps and filters in PHP actually mutate the data structure themselves, and return a boolean, instead of using a functional style, where they don't mutate and return a new list.
I am also interested in other ways to make this code more succinct.
Here's an alternative approach:
// The meat of the matter
function get_link($array, $tag) {
$parts = array_reduce($array, function($result, $item) use($tag)
{
if($item != $tag) $result[] = 'x[]='.$tag;
return $result;
});
return implode('&', $parts);
}
// Test driver
$all_tags = array('A', 'B', 'C');
echo get_link($all_tags, 'A');
echo "\n";
echo get_link($all_tags, 'B');
echo "\n";
echo get_link($all_tags, 'C');
echo "\n";
It's simply one call to array_reduce, and then an implode to pull the results together into a query string.
Something approaching real support for functional programming styles in PHP is very, very new--it was only in PHP 5.3 that functions became first class and anonymous functions were possible.
BTW, you should never use create_function(). What it really does is define a new function in the global namespace (which will never be garbage-collected!), and it uses eval() behind the scenes.
If you have PHP 5.3 or greater, you can do this:
$all_tags = array('A', 'B', 'C');
function is_not_equal($a, $b) {
return $a != $b;
}
function array_filter_tagname($alltags, $name) {
$isNotEqualName = function($item) use ($name){
return is_not_equal($item, $name);
};
// array_merge() is ONLY to rekey integer keys sequentially.
// array_filter() preserves keys.
return array_merge(array_filter($alltags, $isNotEqualName));
}
function make_url($arr) {
return 'input.php?'.http_build_query(array('x'=>$arr));
}
$res = array_filter_tagname($all_tags, 'B');
print_r($res);
print_r(make_url($res));
If you have a PHP < 5.3, you should use a class+object for your closures instead of create_function().
class NotEqualName {
protected $otheritem;
function __construct($otheritem) { // with PHP 4, use "function NotEqualName($otheritem) {"
$this->otheritem = $otheritem;
}
function compare($item) {
return $item != $this->otheritem;
}
}
function array_filter_tagname_objectcallback($alltags, $name) {
$isNotEqualName = new NotEqualName($name);
return array_merge(array_filter($alltags, array($isNotEqualName,'compare')));
}
In general, however, PHP is not very well suited to a functional style, and for your particular task using array_filter() is not very idiomatic PHP. array_diff() is a better approach.
Based on an answer I gave in the comments (shown here):
<?php
$all_tags = array('A', 'B', 'C');
function tag_to_url($tag_name)
{
global $all_tags;
$remaining_tags = array_diff($all_tags, array($tag_name));
return sprintf('index.php?%s',
http_build_query(array('x'=>array_values($remaining_tags))));
}
echo tag_to_url('B'); // index.php?x%5B0%5D=A&x%5B1%5D=C
// basically: index.php?x[0]=A&x[1]=C
Basically, use array_diff to remove the entry from the array (instead of filtering), then pass it off to the http_build_query to come up with a valid URL.
I'm just going to answer the "why it doesnt work" part.
$filta = create_function('$x', 'global $all_tags; return ($x != $tag_name);');
The tag_name variable is undefined in your lambda function's scope. Now, it is defined in the creating functions scope(tag_to_url). You can't exactly use the global keyword here either, because $tag_name isn't in the global scope, it is in the local tag_to_url scope. You could declare the variable in the global scope and then it could work, but considering you're fond of functional approaches, I doubt you like global variables :)
You could do trickery with string concatenation and var_export($tag_name) and pass that to create_function() if you wanted, but there's other better ways to achieve your goals.
Also, as an aside, I'm guessing you might benefit from turning up php's error reporting level while developing. php would have thrown undefined variable notices at you, which helps debugging and understanding.
// ideally set these in php.ini instead of in the script
error_reporting(E_ALL);
ini_set('display_errors', 1);
Is it possible to pass functions by reference?
Something like this:
function call($func){
$func();
}
function test(){
echo "hello world!";
}
call(test);
I know that you could do 'test', but I don't really want that, as I need to pass the function by reference.
Is the only way to do so via anonymous functions?
Clarification: If you recall from C++, you could pass a function via pointers:
void call(void (*func)(void)){
func();
}
Or in Python:
def call(func):
func()
That's what i'm trying to accomplish.
For what it's worth, how about giving something like this a shot? (Yes, I know it's an anonymous function which was mentioned in the post, but I was disgruntled at the abundance of replies that did not mention closures/function-objects at all so this is mostly a note for people running across this post.)
I don't use PHP, but using a closure appears to work in PHP 5.3 (but not PHP 5.2) as demonstrated here. I am not sure what the limitations, if any, there are. (For all I know the closure will eat your children. You have been warned.)
function doIt ($fn) {
echo "doIt\n";
return $fn();
}
function doMe () {
echo "doMe\n";
}
// I am using a closure here.
// There may be a more clever way to "get the function-object" representing a given
// named function, but I do not know what it is. Again, I *don't use PHP* :-)
echo doIt(function () { doMe(); });
Happy coding.
The problem with call_user_func() is that you're passing the return value of the function called, not the function itself.
I've run into this problem before too and here's the solution I came up with.
function funcRef($func){
return create_function('', "return call_user_func_array('{$func}', func_get_args());");
}
function foo($a, $b, $c){
return sprintf("A:%s B:%s C:%s", $a, $b, $c);
}
$b = funcRef("foo");
echo $b("hello", "world", 123);
//=> A:hello B:world C:123
ideone.com demo
No, functions are not first class values in PHP, they cannot be passed by their name literal (which is what you're asking for). Even anonymous functions or functions created via create_function are passed by an object or string reference.
You can pass a name of a function as string, the name of an object method as (object, string) array or an anonymous function as object. None of these pass pointers or references, they just pass on the name of the function. All of these methods are known as the callback pseudo-type: http://php.net/callback
function func1(){
echo 'echo1 ';
return 'return1';
}
function func2($func){
echo 'echo2 ' . $func();
}
func2('func1');
Result:
echo1 echo2 return1
In PHP 5.4.4 (haven't tested lower or other versions), you can do exactly as you suggested.
Take this as an example:
function test ($func) {
$func('moo');
}
function aFunctionToPass ($str) {
echo $str;
}
test('aFunctionToPass');
The script will echo "moo" as if you called "aFunctionToPass" directly.
A similar pattern of this Javascript first class function:
function add(first, second, callback){
console.log(first+second);
if (callback) callback();
}
function logDone(){
console.log('done');
}
function logDoneAgain(){
console.log('done Again');
}
add(2,3, logDone);
add(3,5, logDoneAgain);
Can be done in PHP (Tested with 5.5.9-1ubuntu on C9 IDE) in the following way:
// first class function
$add = function($first, $second, $callback) {
echo "\n\n". $first+$second . "\n\n";
if ($callback) $callback();
};
function logDone(){
echo "\n\n done \n\n";
}
call_user_func_array($add, array(2, 3, logDone));
call_user_func_array($add, array(3, 6, function(){
echo "\n\n done executing an anonymous function!";
}));
Result: 5 done 9 done executing an anonymous function!
Reference: https://github.com/zenithtekla/unitycloud/commit/873659c46c10c1fe5312f5cde55490490191e168
You can create a reference by assigning the function to a local variable when you declare it:
$test = function() {
echo "hello world!";
};
function call($func){
$func();
}
call($test);
You can say
$fun = 'test';
call($fun);
Instead of call(test);, use call_user_func('test');.
As of PHP 8.1, you can use First-class callables:
call(test(...));
You can even use methods:
call($obj->test(...));
As simple as it is.
It appears a bit unclear why do you want to pass functions by reference? Usually things are passed by reference only when the referenced data needs to be (potentially) modified by the function.
As PHP uses arrays or strings to refer functions, you could just pass an array or a string by reference and that would allow the function reference to be modified.
For example, you could do something like
<?php
$mysort = function($a, b) { return ($a < $b) ? 1 : -1; };
adjust_sort_from_config($mysort); // modifies $mysort
do_something_with_data($mysort);
where
<?php
function load_my_configuration(&$fun)
{
$sort_memory = new ...;
...
$fun = [$sort_memory, "customSort"];
// or simply
$fun = function($a, b) { return (rand(1,10) < 4 ? 1 : -1; };
}
This works because there are three ways to refer to function in PHP via a variable:
$name – the string $name contains the name of the function in global namespace that should be called
array($object, $name) – refers to method called string $name of object $object.
array($class, $name) – refers to static function string $name of class $class.
If I remember correctly, the methods and static functions pointed by these constructs must be public. The "First-class callable syntax" should improve this restriction given recent enough PHP version but it seems to be just some syntactic sugar around Closure::fromCallable().
Anonymous functions work the same behind the scenes. You just don't see the literal random names of those functions anywhere but the reference to an anonymous function is just a value of a variable, too.
I'd like to check if there is a value on an array like this:
function check_value_new ($list, $message) {
foreach ($list as $current) {
if ($current == $message) return true;
}
return false;
}
function check_value_old ($list, $message) {
for ($i = 0; $i < count ($status_list); $i ++) {
if ($status_list[$i] == $$message) return true;
}
return false;
}
$arr = array ("hello", "good bye", "ciao", "buenas dias", "bon jour");
check_value_old ($arr, "buenas dias"); // works but it isn't the best
check_value_new ($arr, "buenas dias"); // argument error, where I'm wrong?
I've read the check_value_new method is a better way to work with arrays, but I'm not used to work with it, how I should fix it?
PHP offers a function called in_array that checks if a value exists in a given array.
You can change your check_value_new function to include this:
function check_value_new ($list, $message) {
foreach ($list as $current) {
if (in_array($message, $current)) {
return true;
}
return false;
}
If you'd like to, you could also make the function work without the foreach loop, like so
function check_value_new ($list, $message) {
// returns true if found, else returns false.
return in_array($message, $list);
}
The reasons that your check_value_old() function breaks is because you have a typo in your loop's second parameter. $status_list is not one of the variables passed into your function and it is not declared anywhere before it is used. You merely need to use $list instead. It is also not necessary to call count() on every iteration -- since that value never changes in the loop, just call it once before the loop and use that value in the second parameter.
As for deciding how to loop over array values, there is no reason to reinvent a native function call. in_array() was already designed and optimized to search linear values in an array and return true as soon as it finds a match; otherwise it returns false.
I don't know what #AnthonyForloney is doing by writing in_array() inside of a foreach() loop. Not only will the snippet break because there aren't enough curly braces, the input array does not have two levels of data, so in_array() will choke when it receives $current which is a string (not the expected array).
Ultimately, my advice is: Only write your own custom function when there isn't a native function to suit your needs. Throw away both of the manual looping ideas and only call:
$arr = ["hello", "good bye", "ciao", "buenas dias", "bon jour"];
var_export(in_array("buenas dias", $arr));
One final insight that I'd like to provide is regarding microoptimization. If you are planning to check the existence of a unique value in a "very large" array, or if you are going to be making thousands of searches on the same array, then it would be more efficient to set up your array in a "flipped" orientation and call isset() or array_key_exists(). isset()/array_key_exists() will always outperform in_array() or array_search() because of the way that php handles arrays as "hash maps". More reading here.