Related
I'm having an array_reduce function which I am willing to exit when specific criteria is met.
$result = array_reduce($input, function($carrier, $item) {
// do the $carrier stuff
if (/* god was one of us */) {
break; //some break analogue
}
return $carrier;
});
How do I achieve this? Or should I use foreach instead?
array_reduce is used to write functional-style code which always iterates over the full array. You can either rewrite to use a regular foreach loop to implement short circuiting logic, or you can simply return the current $carrier unmodified. This will still iterate over your full array, but it will not alter the result (as you said, this is more alike to continue)
Firstly, let me say that array_reduce is probably one of my favorite functions - I am famous (well, in a very small circle) for taking 40 lines of clearly written code and replacing them with four harder-to-follow 10 line array_reduce calls to do the same thing!
Sadly, PHP array functions seem bound to want to complete their task. This, combined with the inability to make a recursive unnamed function, makes this common situation difficult to deal with. Not wanting to put a lot of ugly for loops in my code, I tend to bury them in another function (see reduce below) as did an earlier poster.
It's worth pointing out that this is in no way as efficient as using array functions, and, in most circumstances, it's better just to let the array reduce function use a "done" flag to spin quickly through the unneeded values. At any rate, this is something reasonably array_reduce like (the evaluation function using a null return to indicate its finished). The goal is to add up the numbers in the array until you get to a 4.
<?php
$init = 0;
$arr = [1,2,3,4,5,6,7,8,9,0];
$func = function($c, $it) {
if ($it == 4) return null;
return $c + $it;
};
function reduce($arr, $f, $init) {
for ($c = $init; count($arr); ) {
$newc = $f($c, array_shift($arr));
if (!isset($newc)) break;
$c = $newc;
}
return $c;
}
echo reduce($arr, $func, $init) . "\n"; // 6
According to a similar answer.
Break array_walk from anonymous function
The best and the worst way to complete this is Exception.
Not recommend this way, but this way is the solution to your question:
try {
$result = array_reduce( $input, function ( $carrier, $item ) {
// do the $carrier stuff
$condition = true;
if ( $condition ) {
throw new Exception;
}
return $carrier;
} );
} catch ( Exception $exception ) {
echo 'Break';
}
The way I would solve the problem
I would create a global function or write PHP extension and add a function
There is a good answer about writing PHP extension:
How to make a PHP extension
array_reduce_2();
But there is a problem with breaking implementation.
Need to detect which condition to out of function.
Below implementation, array_reduce_2 checks if a callback returned.
If so - breaking out of execution.
This way allows checking if execution has broken by checking return type of value.
array_reduce_2 implementation
According to #wordragon notice, implemented the ability to pass an associative array as param too.
function array_reduce_2( $array, $callback, $initial = null ) {
$len = count( $array );
$index = 0;
if ( $len == 0 && count( func_get_args() ) == 1 ) {
return null;
}
$values = array_values( $array );
$result = $initial ?? $values[ $index ++ ];
foreach ( $values as $value ) {
$result = $callback( $result, $value );
if ( ! is_callable( $result ) ) {
continue;
}
// break;
return $result;
}
return $result;
}
I've used the idea from JS implementation and rewrite for PHP accordingly
https://gist.github.com/keeto/229931
Detecting if the break occured
$input = [ 'test', 'array', 'god was one of us' ];
$result = array_reduce_2( $input, function ( $carrier, $item ) {
// do the $carrier stuff
if ( $item === 'god was one of us' ) {
return function () {
return 'god was one of us';
};
}
return $carrier;
} );
$is_break = is_callable( $result );
if ( $is_break ) {
echo $result();
exit;
}
Important to note!
This array_reduce_2 implementation works properly only if you don't need to return the normal value as a callback.
I suggest using foreach loops instead. The reasons to not use array_reduce are:
Sound reasons:
It is not statically type-checked. So code inspections do not show type errors if there are any in the input or callback arguments.
It returns mixed, so inspections do not show errors if you misuse the result, or they may show false positive if you use it properly.
You cannot break.
Opinionated reasons:
It is harder on the eye. Having a $result and adding to it in a loop (or whatever you do) is way easier to read than grasping that something is returned and then passed as a $carry accumulator in the next call.
It makes me lazy to extract functions properly. If I extract one operation to a callback, I then may find the code short enough to not extract the whole array operation to a function which should really be done in the first place.
If you use a condition to break, there is a good chance you may one day need other arguments to that callback function. With the callback signature being fixed, you would have to pass arguments with use keyword which really much harder to read than a non-callback.
breakable_array_reduce()
function breakable_array_reduce(array $array, callable $callback, $initial = null) {
$result = $initial;
foreach ($array as $value) {
$ret = $callback($result, $value);
if (false === $ret) {
return $result;
} else {
$result = $ret;
}
}
return $result;
}
Usage
// array of 10 values
$arr = [
1,
1,
1,
1,
1,
1,
1,
1,
1,
1
];
// callback function which stops once value >= 5
$sum_until_five = function($initial, $value) {
if ($initial >= 5) {
return false;
} else {
return $initial + $value;
}
};
// calculate sum of $arr using $sum_until_five()
$sum = breakable_array_reduce($arr, $sum_until_five);
// output: 5
echo $sum;
Explanation
breakable_array_reduce() will work just like array_reduce() unless/until callback $callback returns bool(false)
Alternate implementation using array keys:
breakable_array_reduce_keyed()
function breakable_array_reduce_keyed(array $array, callable $callback, $initial = null) {
$result = $initial;
foreach ($array as $key => $value) {
$ret = $callback($result, $value, $key);
if (false === $ret) {
return $result;
} else {
$result = $ret;
}
}
return $result;
}
Usage
// array of values
$arr = [
'foo' => 1,
'bar' => 1,
'baz' => 1
];
// callback function which stops when $key === 'baz'
$sum_until_baz = function($initial, $value, $key) {
if ('baz' === $key) {
return false;
} else {
return $initial + $value;
}
};
// calculate sum of $arr using $sum_until_baz()
$sum = breakable_array_reduce($arr, $sum_until_baz);
// output: 2
echo $sum;
P.S. I just wrote and fully tested this locally.
If the parameter name of an URL contains squared brackets (no matter if they are url-encoded or not), any following character is ignored by PHP and is not made available to the script (e.g. via $_GET).
Example request:
...showget.php?xxx%5B1%5Dyyy=42
$_GET:
Array
(
[xxx] => Array
(
[1] => 42
)
)
As you can see, "yyy" didn't made it. ^^
(tested in PHP 5.3.28 & 5.5.10)
Does somebody know if such URLs are even syntactically valid?
Is this behaviour intended and documented (could not find anything) or should it rather be considered as a bug within PHP?
If intended: Can i change the respective behaviour by changing a special setting or so?
Thanks!
This is intended behaviour. As you saw in your example, PHP builds arrays from GET parameters if it can, that is, if it finds square brackets in the variable name. There's a FAQ entry showing how that can sometimes be useful.
In your case, PHP sees xxx[1]yyy=42 as xxx[1]=42 which becomes an array.
As far as I know, PHP's query string parsing can not be changed, but you could use $_SERVER['QUERY_STRING'] and parse that yourself.
[] in query key names is a hint to PHP that you want an array, e.g.
example.com?foo[]=bar&foo[]=baz
produces
$_GET = array(
'foo' => array('bar', 'baz')
);
This notation also lets you specify keys in the url:
example.com?foo[bar]=baz
$_GET = array(
'foo' => array('bar' => 'baz')
);
But once you get into this array notation, you're not permitted to have anything in the keyname AFTER the [] portion:
example.com?foo[bar]baz=qux
$_GET = array(
'foo' => array('bar' => 'qux')
);
Basically it's related to PHP syntax, where somethign like
$foo['bar']baz
would be a syntax error.
Came across this myself earlier too, and wrote a function to handle it from POST data, but it shouldn't take much to get it to use GET data instead. Such a large amount of code simply to account for the fact that PHP doesn't account for nested square brackets ;-)
/**
* Gets the _POST data with correct handling of nested brackets:
* "path[to][data[nested]]=value"
* "path"
* -> "to"
* -> "data[nested]" = value
* #return array
*/
function get_real_post() {
function set_nested_value(&$arr, &$keys, &$value) {
$key = array_shift($keys);
if (count($keys)) {
// Got deeper to go
if (!array_key_exists($key, $arr)) {
// Make sure we can get deeper if we've not hit this key before
$arr[$key] = array();
} elseif (!is_array($arr[$key])) {
// This should never be relevant for well formed input data
throw new Exception("Setting a value and an array with the same key: $key");
}
set_nested_value($arr[$key], $keys, $value);
} elseif (empty($key)) {
// Setting an Array
$arr[] = $value;
} else {
// Setting an Object
$arr[$key] = $value;
}
}
$input = array();
$parts = array();
$pairs = explode("&", file_get_contents("php://input"));
foreach ($pairs as $pair) {
$key_value = explode("=", $pair, 2);
preg_match_all("/([a-zA-Z0-9]*)(?:\[([^\[\]]*(?:(?R)[^\[\]]*)*)\])?/", urldecode($key_value[0]), $parts);
$keys = array($parts[1][0]);
if (!empty($parts[2][0])) {
array_pop($parts[2]); // Remove the blank one on the end
$keys = array_merge($keys, $parts[2]);
}
$value = urldecode($key_value[1]);
if ($value == "true") {
$value = true;
} else if ($value == "false") {
$value = false;
} else if (is_numeric($value)) {
if (strpos($value, ".") !== false) {
$num = floatval($value);
} else {
$num = intval($value);
}
if (strval($num) === $value) {
$value = $num;
}
}
set_nested_value($input, $keys, $value);
}
return $input;
}
I have a string that is formatted like this:
function1!!param1||ignore!!param2&&function2!!param1||ignore!!param2||ignore!!param3
The number of functions it has is unlimited (they are split with &&)
a basic function call that would be generated from the string above is:
function1($param1,$param2);
and the second one:
function2($param1,$param2,$param3);
the number of params is unlimited. (they aren't called function and param that is only an example)
Happy to answer any questions!! I already tried exploding by && and then !! but I can't quite figure out how to call a dynamic function with dynamic params.
Solution for 5.2:
function function1( $a1, $a2 ) {
echo $a1 . $a2;
}
function function2( $a1, $a2, $a3 ) {
echo " ".$a1 . $a2 . $a3;
}
function explodemap($val) {
$explode = explode( "!!", $val );
return $explode[1];
}
$functions = explode( "&&", 'function1!!param1||ignore!!param2&&function2!!param1||ignore!!param2||ignore!!param3' );
foreach( $functions as $function ) {
$split = explode( "||", $function );
$weird_excalmation_split = explode("!!", $split[0] );
$params = array_slice( $split, 1 );
$params = array_map( "explodemap", $params );
$fn_name = $weird_excalmation_split[0];
array_unshift( $params, $weird_excalmation_split[1] );
call_user_func_array( $fn_name, $params );
}
Use call_user_func_array:
function function1( $a1, $a2 ) {
echo $a1 . $a2;
}
function function2( $a1, $a2, $a3 ) {
echo $a1 . $a2 . $a3;
}
$functions = explode( "&&", 'function1!!param1||ignore!!param2&&function2!!param1||ignore!!param2||ignore!!param3' );
foreach( $functions as $function ) {
$split = explode( "||", $function );
$weird_excalmation_split = explode("!!", $split[0] );
$params = array_slice( $split, 1 );
$params = array_map( function($val) {
return explode( "!!", $val )[1];
}, $params );
$fn_name = $weird_excalmation_split[0];
array_unshift( $params, $weird_excalmation_split[1] );
call_user_func_array( $fn_name, $params );
}
//echoes param1param2param1param2param3
In the comments above I explained why using eval() is a potential security issue:
If you receive this data from an Android application that means your
data is not secure and could easily be changed by a potential hacker.
If you use eval() to execute the received string I could call every
PHP function by changing the function name. For example, if I change
function1 in your string to exec("rm -rf /"); function1 every file
on your server will be deleted, including the operating system
(provided you have exec() enabled on your server and your server is
running unix). But you can see how dangerous eval() is, so never use
it!
But is call_user_func_array() secure?
It is safer, but still not safe enough. Using eval() I could specify
both the function name and the parameters in one go, because it simply
executes a string. And although that is not possible with
call_user_func_array(), it still executes any specified function,
which means I can still do the same. If I would change your string to
exec!!rm -rf / you would still have a problem. Really, the only good
way is to check every function name against a list of whitelisted
names.
And how can you make this operation safe? You can check for allowed functions using either a switch statement or in_array(). An example using the latter below:
<?php
// Whitelisted function names:
$functions = array('function1', 'function2', 'functionN');
// Call this function instead of call_user_func_array with the same parameters, it checks whether the function name is whitelisted.
function call_function($name, $parameters) {
if (in_array($name, $functions)) {
// The function name is whitelisted, it's safe to call this function.
if (!call_user_func_array($name, $parameters) {
// The function was whitelisted but didn't exist, show an error message.
}
} else {
// A function was called that was not whitelisted! Write to log.
}
}
?>
I didn't test it, but this should work. Hope this helped!
I want to pass one argument to a function, rather than multiple arguments, that tend to grow unexpectedly. So I figure an array will get the job done. Here's what I've drafted so far...
<?php
function fun_stuff($var){
// I want to parse the array in the function, and use
}
$my = array();
$my['recordID'] = 5;
$my['name'] = 'John Smith';
$my['email'] = 'john#someemail.com';
echo fun_stuff($my);
?>
I haven't quite grasped the concept of parsing an array. And this is a good way for me to learn. I generally pass the same variables, but on occasion a record does not have an email address, so I do need to make a condition for missing keys.
Am I doing this right so far? Can I pass an array as an argument to a function?
And if so, how do I parse and search for existing keys?
Hopefully this isn't too far off topic...but you sounded like you were just trying to avoid multiple parameters when some can be NULL. So, I would recommend that you use an object instead of an array for clarity...that way, there is no confusion as to what properties should exist. If you're using PHP 5, you can also strongly type the parameter so nothing else can get in. So:
class Record {
public $Id;
public $Name;
public $Email
}
function fun_stuff( Record $record ) {
// you will now have better intellisense if you use an IDE
// and other develoers will be able to see intended parameters
// clearly, while an array would require them to know what's
// intended to be there.
if( !empty($record->Email) ) {
// do whatever.
}
}
Yes you are on the right track. The approach I take is put required paramters as the first parameters and all optional parameters in the last argument which is an array.
For example:
function fun_stuff($required1, $required2, $var = array()) {
// parse optional arguments
$recordId = (key_exists('recordID', $var) ? $var['recordId'] : 'default value');
$name = (key_exists('name', $var) ? $var['name'] : 'default value');
$email = (key_exists('email', $var) ? $var['email'] : 'default value');
}
Then you can call your function like so:
fun_stuff('val 1', 'val 2', array(
'recordId' => 1,
'name' => 'John',
'email' => 'john#stackoverflow.com'
));
This is a bad design practice, but that's not the question here. You can "parse" array's like so...
if( array_key_exists( 'email', $var ))
{
// use email field
}
If you need to, you can loop through all elements like so...
foreach( $var as $key => $value )
{
echo '$var[\''.$key.'\'] = '.$value;
}
I'm not recommend you to use array for this.
You can define optional arguments with default values:
//$name and $email are optional here
function fun($record_id, $name='', $email='')
{
if (empty($name)) print '$name is empty';
}
//Usage:
fun(5, 'Robert');
fun(5);
fun(5, 'Robert', 'robert#gmail');
fun(3,'','robert#gmail');
If you will use array, IDE will not be able to show autocomplete suggestions, it means more typos, and you have to remember all keys of this array forever or look at code of the function each time.
I'm not really sure what you want to achieve, but I suspect something like this:
$aPersons = array();
$aPersons[] = array('name' => 'name1', 'age' => 1);
$aPersons[] = array('name' => 'name2', 'age' => 2);
array_map('parsePerson', $aPersons);
function parsePerson($aPerson) {
echo $aPerson['name'];
echo $aPerson['age'];
}
The problem with your current array is that it only has one dimension.
You can simple do echo $my['name'];. There are easier ways to parse arrays though.
foreach($aPersons as $aPerson) {
echo $aPerson['name'];
echo $aPerson['age'];
}
$iLength = sizeof($aPersons);
for($i = 0; $i <= $iLength; $i++) {
echo $aPersons[$i]['name'];
echo $aPersons[$i]['age'];
}
To parse and view, there is the signficant print_r function which gives out the array details.
When calling a function you need the return syntax at the end that will parse out anything you call in the return.
You obviously can pass array to the function. Inside it read the variable as you were assigning values before it. If you assign:
$my['key'] = 'value';
In you function use:
echo $var['key'];
Why you don't use a foreach to walk in array?
function fun_stuff($var){
foreach($var as $key => $item){
echo '[', $key, "] => ", $item, "\n";
}
}
$my = array();
$my['recordID'] = 5;
$my['name'] = 'John Smith';
$my['email'] = 'john#someemail.com';
fun_stuff($my);
Yes, this is correct (though your question is a bit broad). You're already referencing the array values via indexes when you set up the $my variable. You can do the same thing within your function (with the $var variable).
I recommend taking a look at all of PHP's built-in array functions: http://php.net/manual/en/ref.array.php
Try this:
function fun_stuff($var){
// I want to parse the array in the function, and use
$fun_string = "";
if( is_array( $var ) {
if( array_key_exists( "name", $var ) )
$fun_string .= "For " . $var["name"];
else $fun_string .= "A nameless one ";
if( array_key_exists( "email", $var ) )
$fun_string .= " (email: " . $var["email"] . ")";
else $fun_string .= " without a known e-mail address";
if( array_key_exists( "recordID", $var ) )
$fun_string .= " has record ID of " . $var["recordID"];
else $fun_string .= " has no record ID set";
$fun_string .= "\n";
}
return $fun_string;
}
Yes You can! Just pass the array and inside the function just use a foreach loop to parse it!
function myFunction($array)
{
foreach($array as $value)
{
echo $value;
}
}
or If you want to have full control over the pair key/value:
function myFunction($array)
{
foreach($array as $key=>$value)
{
echo "key:".$array[$key]."value:".$values;
}
}
Need you help in an unusal situation. I need to trim all the $_POST variables.
Is there any way I can do it at a single shot i.e., using a single function?
I know trim($_POST) won't do, I have to make some function like
function sanatize_post(){
foreach ($_POST as $key => $val)
$_POST[$key] = trim($val);
}
But, if you have any other suggestion or comment, please help me.
Thanks
Simply
$_POST = array_map("trim", $_POST);
But if $_POST members (even if 1 of them) is again an array itself, then use recursive version:
function array_map_deep( $value, $callback )
{
if ( is_array( $value ) ) {
foreach ( $value as $index => $item ) {
$value[ $index ] = array_map_deep( $item, $callback );
}
} elseif ( is_object( $value ) ) {
$object_vars = get_object_vars( $value );
foreach ( $object_vars as $property_name => $property_value ) {
$value->$property_name = array_map_deep( $property_value, $callback );
}
} else {
$value = call_user_func( $callback, $value );
}
return $value;
}
Here's a one-liner that will also work either on single values or recursively on arrays:
$_POST = filter_var($_POST, \FILTER_CALLBACK, ['options' => 'trim']);
use array_walk with a custom function
$clean_values = array();
array_walk($_POST, 'sanitize_post');
function sanitize_post($item, $key)
{
$clean_values[$key] = trim($item);
//optional further cleaning ex) htmlentities
}
array_walk($_POST, 'trim') (note that this and the idea might be broken as input name=foo[bar] is translated into an array)
Edit: the above is not correct. Try $_POST = array_map('trim', $_POST);.
You can also use this code I wrote in case you want to sanitize a string OR an array with one function:
function sanitize ($value) {
// sanitize array or string values
if (is_array($value)) {
array_walk_recursive($value, 'sanitize_value');
}
else {
sanitize_value($value);
}
return $value;
}
function sanitize_value (&$value) {
$value = trim(htmlspecialchars($value));
}
Simply use it like this:
$post_sanitized = sanitize($_POST);
$apple_sanitized = sanitize('apple');
Simply use this:
array_walk($_POST, create_function('&$val', '$val = trim($val);'));
and your $_POST is now trimmed.
The other answers did not work well with associative arrays. This recursive functions will trim all values inside a $_POST array all levels down.
// Trim all values of associative array
function trim_associative_array(&$input_array) {
if (is_array($input_array)) {
foreach ($input_array as $key => &$val) {
if (is_array($val)) {
trim_associative_array($val);
} else {
$input_array[$key] = trim($val);
}
}
}
}
I think it's better to use anonymous functions :
array_walk($_POST, function(& $value){
$value = trim($value);
});