Related
I'm trying to write a function that will take any number of arguments, and will return the first argument, in order, that is valid (in my case !empty).
I've been able to pretty much get it working how I want, except I'm getting some notices because of undefined variables. See below for examples:
function cascade()
{
if (func_num_args() < 1) return false;
foreach (func_get_args() as &$arg) {
if (!empty($arg)) return $arg;
}
return false;
}
You can see that I've tried to declare that each $arg of the foreach is passed by reference, but that doesn't seem to have done the trick.
To elaborate on how I plan to use this function, see below:
$a = 'a';
$c = 'c';
echo cascade($z, $b, $a, $c);
Since $z and $b are undefined, the first non-empty variable in the list is $a so the output is a as expected. However, you then get the undefined variable notices, which I wish to get rid of.
I realise I can just say echo #cascade($z, $b, $a, $c); which would suppress the errors, but I want to know if there is a way around this issue, so that the reference can be passed somehow. Any thoughts?
Edit:
To further highlight what I'm trying to acheive, see the following function that DOES work without throwing errors, even when passed an undefined variable:
// returns default value if input variable is not set
function ifset(&$var, $default = false) {
return isset($var) ? $var : $default;
}
With this function, if param 1 is not set, then the default value in param 2 is returned. Either way, no error is thrown.
What I am trying to achieve is the same result, but with ANY number of arguments, as this function is limited to 1, unless I nest them (gets messy).
A real life example:
This is WHY I want this function and how I would use it in a real life scenario:
<input type="text" name="customerName" value="<?= cascade($order->fullName, $currentUser->fullName, 'Anonymous') ?>">
So if we have an order in the making, and there is a name available from that, we use that, if that info hasn't been saved yet, we use the logged in user's name as the default value, if no one is logged in, we use 'Anonymous'.
Not exactly what I would do in real life, but perhaps it highlights example usage?
For those who are suggesting defining the variables to mitigate the errors, the who point of this function is to work though a chain of values, giving priority to the ones that come first, then moving to the next if that is 'empty' and so on, until eventually a FALSE default value is returned if all are empty.
The Notice you're seeing is triggered at the function call, not inside the function itself. Therefore, there is nothing you can do inside the function to solve the problem. However, there are a number of ways of solving this problem depending on how your variables are prepared before the function call.
Solution #1: Define the variables before the function call.
for instance:
$z = ''; OR $z = null;
or any falsy value like : null, "", 0, "0", 0.0, [], ..., your function will still work as expected and you won't see the notice.
Solution #2: Test for validity before the function call.
if( !isset($z) ){ $z = ''; }
echo cascade($z);
Solution #3: Test for validity as part of the function call.
This is the same thing as solution #2 but a bit more elegant. Use the Ternary function to pass the variable value or an empty string depending on whether or not the variable is set.
echo cascade(
isset($z)?$z:'',
isset($b)?$b:'',
isset($a)?$a:'',
isset($c)?$c:''
);
Solution #4: If using PHP 7 or above, you can use the new Null Coalescing Operator. This is the same thing as solution #3, but more elegant still.
echo cascade($z ?? '', $b ?? '', $a ?? '', $c ?? '');
If you want a clean alternative to your function, you can try this:
function cascade()
{
$args = func_get_args();
while (!($arg = array_shift($args)));
return $arg ? $arg : false;
}
BIG THANKYOU to JBH for Editing significantly this answer.
The undefined variable notice is not from the code in function but it is from the function call cascade($z, $b, $a, $c)
You are passing undefined variables ($z and $b) as arguments to the function. Since they are not defined any where in the code, you are getting notice.
To get rid of the the notices, define the variables before passing them as arguments.
$a = 'a';
$c = 'c';
$z = '';
$b = '';
echo cascade($z, $b, $a, $c);
OR
echo cascade('', '', $a, $c);
Don't know if this is "good enough".
Create an array of the variables and use array filter to remove the null values.
Use array values to reset the keys.
Now $arr[0] is the first non empty item.
https://3v4l.org/PlmrS
$a = 'a';
$c = 'c';
$arr =array_values(array_filter([$z, $b, $a, $c]));
Var_dump($arr);
Thank you for all your kind answers, but unfortunately none of them solved my problem. Perhaps I didn't explain it well enough, or I over-complicated it with too much information confusing the issue, but anyway I've been able to find the answer.
The following function does exactly what I wanted. It uses the variadic syntax (php 5.6 and over) which allows a variable number of arguments, all of which are passed by reference.
function cascade(&...$args)
{
if (count($args) < 1) return false;
foreach ($args as &$arg) {
if (!empty($arg)) return $arg;
}
return false;
}
I'm trying to extract values from an array, and pass them to a function where they'll be used. I've used echo inside the function for this example.
I'm using extract to get all the values.
Thing is, this wont work. Do you see how this can be done?
<?php
$my_array = array("a" => "Prince", "b" => "Funky"); // around 10 more
$g = extract($my_array);
foo($g);
function foo($g) {
echo 'My name is '.$a.', and I am '.$b;
}
?>
Functions in PHP have a different scope, so the variables $a, $b etc. aren't available inside your function. Trying to use them inside the function would result in Undefined variable notices (if you enable error reporting, that is).
Right now, you're storing the return value of extract() (which is the total number of variables parsed) into your function. You want the values instead, so change your function like so:
function foo($array) {
extract($array);
echo 'My name is '.$a.', and I am '.$b;
}
Note that I've moved the extract() call inside the function. This way, you wouldn't pollute the global scope with random variables (which may have undesired results and will make your debugging hard for no reason).
Now you can call your function, like so:
foo($my_array);
Output:
My name is Prince, and I am Funky
Demo
It's better to avoid extract() altogether, though. See: What is so wrong with extract()?
You can pass your array in your function as you do with any other variable
$my_array = array("a" => "Prince", "b" => "Funky"); // around 10 more
foo($my_array);
function foo($arrayData)
{
echo 'My name is '.$arrayData['a'].', and I am '.$arrayData['b'];
}
This might seem like an academic or useless topic, but I'm curious.
When developing web pages with PHP, I often need to call functions that take several arguments. I frequently need to look up the spec for the function (on php.net or in my include files, if it's a function I defined) to remind myself what the variables are and what order they're in and what the defaults are, etc. I imagine many of you can relate to this.
A function defined like this:
function do_something_awesome ($people_array, $places_recordset, $num_cycles, $num_frogs,
$url = '?default=yes', $submit_name = 'default_submit_label') {
...
}
when called, might look like this:
$result = do_something_awesome($names, $rsTowns, $c, $f);
My question is this: I'd like to write my code in a way that reminds me of which argument corresponds to each variable, during function calls like this. Is it ever legal to call a function as follows?
$result = do_something_awesome($people_array = $names, $places_recordset = $rsTowns,
$num_cycles = $c, $num_frogs = $f);
If not in PHP, are there other languages where method calls can be made in this way?
To answer your first question:
My question is this: I'd like to write my code in a way that reminds me of which argument corresponds to each variable, during function calls like this.
AFAIK, many PHP coders do it by passing in an associative array as the only argument. However, you'll have to do your own variables checking inside the called function.
$result = do_something_awesome(array(
'people_array' => $names,
'places_recordset' => $rsTowns,
'num_cycles' => $c,
'num_frogs' => $f
));
As for:
Is it ever legal to call a function as follows?
It won't cause any PHP errors, but what you are effectively doing is:
$result = do_something_awesome( expression, expression, expression, expression );
See: PHP Functions arguments
PHP won't know to put $people_array = ... or $num_frogs = ... in their corresponding places when you decide to switch their order around. Furthermore, as DCoder said, these expressions actually take place in the current scope, and will change any pre-existing variables without letting you know.
What about using an object as the only argument:
function my_function($arguments) {
if (!is_object($arguments)) throw new Exception();
$default_values = array('arg1' => 'value1', 'arg2' => 'value2');
foreach ($default_values as $key => $default_value)
if (!isset($arguments->$key)) $arguments->$key = $default_value;
## do the job ##
}
## and then
$my_arguments = new stdClass();
$my_arguments->arg2 = 'some_value';
my_function($my_arguments);
You can try this out:
$bas = 'This is passed to the function.';
$bar = 'This will be modified.';
function foo($bar)
{
echo $bar;
}
foo($bar = $bas);
echo $bar;
The output from this script would be 'This is passed to the function.This is passed to the function.'. So like DCoder said, while you can use them and it's perfectly legal but if you had other variables with the same name as the function arguments, this will overwrite them (in this case the original $bar was overwritten).
Background
In every other programming language I use on a regular basis, it is simple to operate on the return value of a function without declaring a new variable to hold the function result.
In PHP, however, this does not appear to be so simple:
example1 (function result is an array)
<?php
function foobar(){
return preg_split('/\s+/', 'zero one two three four five');
}
// can php say "zero"?
/// print( foobar()[0] ); /// <-- nope
/// print( &foobar()[0] ); /// <-- nope
/// print( &foobar()->[0] ); /// <-- nope
/// print( "${foobar()}[0]" ); /// <-- nope
?>
example2 (function result is an object)
<?php
function zoobar(){
// NOTE: casting (object) Array() has other problems in PHP
// see e.g., http://stackoverflow.com/questions/1869812
$vout = (object) Array('0'=>'zero','fname'=>'homer','lname'=>'simpson',);
return $vout;
}
// can php say "zero"?
// print zoobar()->0; // <- nope (parse error)
// print zoobar()->{0}; // <- nope
// print zoobar()->{'0'}; // <- nope
// $vtemp = zoobar(); // does using a variable help?
// print $vtemp->{0}; // <- nope
PHP can not access array results from a function. Some people call this an issue, some just accept this as how the language is designed. So PHP makes you create unessential variables just to extract the data you need.
So you need to do.
$var = foobar();
print($var[0]);
This is specifically array dereferencing, which is currently unsupported in php5.3 but should be possible in the next release, 5.4. Object dereferencing is on the other hand possible in current php releases. I'm also looking forward to this functionality!
Array Dereferencing is possible as of PHP 5.4:
http://svn.php.net/viewvc?view=revision&revision=300266
Example (source):
function foo() {
return array(1, 2, 3);
}
echo foo()[2]; // prints 3
with PHP 5.3 you'd get
Parse error: syntax error, unexpected '[', expecting ',' or ';'
Original Answer:
This has been been asked already before. The answer is no. It is not possible.
To quote Andi Gutmans on this topic:
This is a well known feature request
but won't be supported in PHP 5.0. I
can't tell you if it'll ever be
supported. It requires some research
and a lot of thought.
You can also find this request a number of times in the PHP Bugtracker. For technical details, I suggest you check the official RFC and/or ask on PHP Internals.
Well, you could use any of the following solutions, depending on the situation:
function foo() {
return array("foo","bar","foobar","barfoo","tofu");
}
echo(array_shift(foo())); // prints "foo"
echo(array_pop(foo())); // prints "tofu"
Or you can grab specific values from the returned array using list():
list($foo, $bar) = foo();
echo($foo); // prints "foo"
echo($bar); // print "bar"
Edit: the example code for each() I gave earlier was incorrect. each() returns a key-value pair. So it might be easier to use foreach():
foreach(foo() as $key=>$val) {
echo($val);
}
There isn't a way to do that unfortunately, although it is in most other programming languages.
If you really wanted to do a one liner, you could make a function called a() and do something like
$test = a(func(), 1); // second parameter is the key.
But other than that, func()[1] is not supported in PHP.
As others have mentioned, this isn't possible. PHP's syntax doesn't allow it. However, I do have one suggestion that attacks the problem from the other direction.
If you're in control of the getBarArray method and have access to the PHP Standard Library (installed on many PHP 5.2.X hosts and installed by default with PHP 5.3) you should consider returning an ArrayObject instead of a native PHP array/collection. ArrayObjects have an offetGet method, which can be used to retrieve any index, so your code might look something like
<?php
class Example {
function getBarArray() {
$array = new ArrayObject();
$array[] = 'uno';
$array->append('dos');
$array->append('tres');
return $array;
}
}
$foo = new Example();
$value = $foo->getBarArray()->offsetGet(2);
And if you ever need a native array/collection, you can always cast the results.
//if you need
$array = (array) $foo->getBarArray();
Write a wrapper function that will accomplish the same. Because of PHP's easy type-casting this can be pretty open-ended:
function array_value ($array, $key) {
return $array[$key];
}
If you just want to return the first item in the array, use the current() function.
return current($foo->getBarArray());
http://php.net/manual/en/function.current.php
Actually, I've written a library which allows such behavior:
http://code.google.com/p/php-preparser/
Works with everything: functions, methods. Caches, so being as fast as PHP itself :)
You can't chain expressions like that in PHP, so you'll have to save the result of array_test() in a variable.
Try this:
function array_test() {
return array(0, 1, 2);
}
$array = array_test();
echo $array[0];
This is too far-fetched, but if you really NEED it to be in one line:
return index0( $foo->getBarArray() );
/* ... */
function index0( $some_array )
{
return $some_array[0];
}
You could, of course, return an object instead of an array and access it this way:
echo "This should be 2: " . test()->b ."\n";
But I didn't find a possibility to do this with an array :(
my usual workaround is to have a generic function like this
function e($a, $key, $def = null) { return isset($a[$key]) ? $a[$key] : $def; }
and then
echo e(someFunc(), 'key');
as a bonus, this also avoids 'undefined index' warning when you don't need it.
As to reasons why foo()[x] doesn't work, the answer is quite impolite and isn't going to be published here. ;)
These are some ways to approach your problem.
First you could use to name variables directly if you return array of variables that are not part of the collection but have separate meaning each.
Other two ways are for returning the result that is a collection of values.
function test() {
return array(1, 2);
}
list($a, $b) = test();
echo "This should be 2: $b\n";
function test2() {
return new ArrayObject(array('a' => 1, 'b' => 2), ArrayObject::ARRAY_AS_PROPS);
}
$tmp2 = test2();
echo "This should be 2: $tmp2->b\n";
function test3() {
return (object) array('a' => 1, 'b' => 2);
}
$tmp3 = test3();
echo "This should be 2: $tmp3->b\n";
Extremely ghetto, but, it can be done using only PHP. This utilizes a lambda function (which were introduced in PHP 5.3). See and be amazed (and, ahem, terrified):
function foo() {
return array(
'bar' => 'baz',
'foo' => 'bar',
}
// prints 'baz'
echo call_user_func_array(function($a,$k) {
return $a[$k];
}, array(foo(),'bar'));
The lengths we have to go through to do something so beautiful in most other languages.
For the record, I do something similar to what Nolte does. Sorry if I made anyone's eyes bleed.
After further research I believe the answer is no, a temporary variable like that is indeed the canonical way to deal with an array returned from a function.
Looks like this will change starting in PHP 5.4.
Also, this answer was originally for this version of the question:
How to avoid temporary variables in PHP when using an array returned from a function
Previously in PHP 5.3 you had to do this:
function returnArray() {
return array(1, 2, 3);
}
$tmp = returnArray();
$ssecondElement = $tmp[1];
Result: 2
As of PHP 5.4 it is possible to dereference an array as follows:
function returnArray() {
return array(1, 2, 3);
}
$secondElement = returnArray()[1];
Result: 2
As of PHP 5.5:
You can even get clever:
echo [1, 2, 3][1];
Result: 2
You can also do the same with strings. It's called string dereferencing:
echo 'PHP'[1];
Result: H
If it is just aesthetic, then the Object notation will work if you return an object. As far as memory management goes, no temporary copy if made, only a change in reference.
Short Answer:
Yes. It is possible to operate on the return value of a function in PHP, so long as the function result and your particular version of PHP support it.
Referencing example2:
// can php say "homer"?
// print zoobar()->fname; // homer <-- yup
Cases:
The function result is an array and your PHP version is recent enough
The function result is an object and the object member you want is reachable
There are three ways to do the same thing:
As Chacha102 says, use a function to return the index value:
function get($from, $id){
return $from[$id];
}
Then, you can use:
get($foo->getBarArray(),0);
to obtain the first element and so on.
A lazy way using current and array_slice:
$first = current(array_slice($foo->getBarArray(),0,1));
$second = current(array_slice($foo->getBarArray(),1,1));
Using the same function to return both, the array and the value:
class FooClass {
function getBarArray($id = NULL) {
$array = array();
// Do something to get $array contents
if(is_null($id))
return $array;
else
return $array[$id];
}
}
Then you can obtain the entire array and a single array item.
$array = $foo->getBarArray();
or
$first_item = $foo->getBarArray(0);
Does this work?
return ($foo->getBarArray())[0];
Otherwise, can you post the getBarArray() function? I don't see why that wouldn't work from what you posted so far.
You could use references:
$ref =& myFunc();
echo $ref['foo'];
That way, you're not really creating a duplicate of the returned array.
I want to trigger a function based on a variable.
function sound_dog() { return 'woof'; }
function sound_cow() { return 'moo'; }
$animal = 'cow';
print sound_{$animal}(); *
The * line is the line that's not correct.
I've done this before, but I can't find it. I'm aware of the potential security problems, etc.
Anyone? Many thanks.
You can do that, but not without interpolating the string first:
$animfunc = 'sound_' . $animal;
print $animfunc();
Or, skip the temporary variable with call_user_func():
call_user_func('sound_' . $animal);
You can do it like this:
$animal = 'cow';
$sounder = "sound_$animal";
print ${sounder}();
However, a much better way would be to use an array:
$sounds = array('dog' => sound_dog, 'cow' => sound_cow);
$animal = 'cow';
print $sounds[$animal]();
One of the advantages of the array method is that when you come back to your code six months later and wonder "gee, where is this sound_cow function used?" you can answer that question with a simple text search instead of having to follow all the logic that creates variable function names on the fly.
http://php.net/manual/en/functions.variable-functions.php
To do your example, you'd do
$animal_function = "sound_$animal";
$animal_function();
You can use curly brackets to build your function name. Not sure of backwards compatibility, but at least PHP 7+ can do it.
Here is my code when using Carbon to add or subtract time based on user chosen type (of 'add' or 'sub'):
$type = $this->date->calculation_type; // 'add' or 'sub'
$result = $this->contactFields[$this->date->{'base_date_field'}]
->{$type.'Years'}( $this->date->{'calculation_years'} )
->{$type.'Months'}( $this->date->{'calculation_months'} )
->{$type.'Weeks'}( $this->date->{'calculation_weeks'} )
->{$type.'Days'}( $this->date->{'calculation_days'} );
The important part here is the {$type.'someString'} sections. This will generate the function name before executing it. So in the first case if the user has chosen 'add', {$type.'Years'} becomes addYears.
For PHP >= 7 you can use this way:
function sound_dog() { return 'woof'; }
function sound_cow() { return 'moo'; }
$animal = 'cow';
print ("sound_$animal")();
You should ask yourself why you need to be doing this, perhaps you need to refactor your code to something like the following:
function animal_sound($type){
$animals=array();
$animals['dog'] = "woof";
$animals['cow'] = "moo";
return $animals[$type];
}
$animal = "cow";
print animal_sound($animal);
You can use $this-> and self:: for class-functions. Example provided below with a function input-parameter.
$var = 'some_class_function';
call_user_func(array($this, $var), $inputValue);
// equivalent to: $this->some_class_function($inputValue);
And yet another solution to what I like to call the dog-cow problem. This will spare a lot of superfluous function names and definitions and is perfect PHP syntax and probably future proof:
$animal = 'cow';
$sounds = [
'dog' => function() { return 'woof'; },
'cow' => function() { return 'moo'; }
];
print ($sounds[$animal])();
and looks a little bit less like trickery as the "string to function names" versions.
JavaScript devs might prefer this one for obvious reasons.
(tested on Windows, PHP 7.4.0 Apache 2.4)