How can I check if a variable is "loopable"? - php

I got a method that receives a parameter that could be any number of types.
It could be an array.
It could be an object that is in some way iterable such as a collection.
It could be something else altogether such as a string or integer which would throw
Warning: Invalid argument supplied for foreach()
How can I reliably check that the variable is "loopable" to avoid the warning?
I've tried is_array() as below but that only works for arrays:
if(is_array($mystery_type)){
foreach($mystery_type as $value){
...
}
}
I was surprised to not find an answer to this here, which probably means it's very simple and I'm missing something obvious.

If you are using PHP 7.1+ you can use is_iterable():
if (is_iterable($mystery_type)) {
// your loop
}
Or its polyfill (found in the documentation comments):
if (!function_exists('is_iterable')) {
function is_iterable($obj) {
return is_array($obj) || (is_object($obj) && ($obj instanceof \Traversable));
}
}
Read more: iterable pseudo-type (PHP 7.1+)

The best way is to not write methods that accept multiple types of data as input but to write smaller specific methods for each type of input. The code of such smaller methods is shorter, simpler, easier to read and understand (and to test).
The method for iterable data structures should use the Traversable interface as the type of its argument:
public function f(Traversable $input) {
// This foreach is guaranteed to always work
foreach ($input as $key => $value) {
// Do something with $value and/or $key
}
}
PHP triggers an error when method f() is invoked with a value that does not implement the Traversable interface.

Related

php recursive function in class

This function works fine outside of a class. Ie simply define the function and call it. Yet when I add it to a class it no longer works - any help is greatly appreciated:
public function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value || (is_array($value) && $this->recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
obviously the $this is removed when not in a class.
Edit:
The error I an getting when using it in a class is:
Invalid argument supplied for foreach()
app\components\GenFun::recursive_array_search('9377907', 9378390)
My sole expectation from the function is that it returns any key (ie identifies that the needle exists in the haystack) - I actually dont care about the actual index.
To be perfectly honest, "it no longer works" isn't a helpful metric by which to assist you in debugging your problem. Nor is "it works fine", since that doesn't tell us your definition of what works means to you. More precisely, these statements don't tell us what you expected the code to do that it's not doing, or what the code is doing that you did not expect.
To me this code is doing exactly what you've told it to do and the result of both a function as well as a class method (using the same code) are identical... See the working 3v4l pastebin here.
However, my guess is that your expectations may be different from what this code actually does. Specifically, this function will return at the very first match of the $needle in the $haystack. Such that the following array, returns 0 (_that is with a needle of 'foo').
$haystack = ['foo', ['foo', 'bar']];
It will also return only the key of the outer-most array in the $haystack. Meaning, the following array returns 0 as the key. Even though the actual match is in $haystack[0][1][2]
$haystack = [['bar',['quix','baz','foo'],'baz'],'quix'];
So depending on what you expected (the inner-most key, or the outer-most key), you may believe this function doesn't work.
So you'll need to clarify exactly what you want the code to do and provide some reproducible example of what didn't work (and that includes the data used or arguments provided to the function).
EDIT:
Hey, I'm glad you figured it out. Here are just a few suggestions to maybe help you refactor this code slightly as well...
So since you're looking for the existence of the needle in any part of the array and don't actually care about the key, you may want to make your intent more obvious in the logic.
So for example, always return a boolean (true on success and false on failure) rather than return false on failure and the key on success. This makes checking the function's result easier and clearer from the caller's perspective. Also, consider naming the function to describe it's intent more clearly (for example: in_array_recursive rather than recursive_array_search since we're not actually intent on searching the array for something, but proving that something is actually in the array). Finally, consider avoiding multiple return points in the same function as this makes debugging harder.
So a cleaner way to write the same code might be something like this:
public function in_array_recursive($needle, $haystack, $strict = false) {
$result = false;
foreach($haystack as $value) {
if(!is_array($value)) {
$result = $strict ? $needle === $value : $needle == $value;
} else {
$result = $this->in_array_recursive($needle, $value, $strict);
}
if ($result) {
break;
}
}
return $result;
}
Now the caller simply does...
$arr = ['bar',['foo']];
if (in_array_recursive('foo', $arr)) {
/* 'foo' is in $arr! */
} else {
/* 'foo' is not in $arr... */
}
Making the code more readable and easier to debug. Notice you also don't have to use exact match if you wanted to add an optional argument for $strict at the end of the function there and also be more inline with in_array.
So the reason this was not working was due to the method by which I was defining $needle.
In my old code it would be input as an integer and in my new code it was a string. The === operator then obviously denied it as being the same. This is why you don't work at 2am :)

dynamically retrieve object method

I try to dynamically retrieve a method of a class but php throws an exception which says Undefined property: stdClass ...
and How i try to get the values
private function getExactValue($row, $name)
{
$tempRow = clone $row;
foreach( explode('->', $name) as $key => $value)
{
$temp = $tempRow->{$value};
unset($tempRow);
$tempRow = $temp;
}
return $tempRow;
}
$row is an instance of an Object (not Std one)
$name is what i need in the Object to traverse , for example when i need $row->student->gifts->totalPoint() just pass the student->gifts->totalPoint() to the method for $name parameter
can you tell me what my mistake is?
I see what you are trying to do here. My first word of advice is that you are going about what you are trying to achieve in a very hackish way. If you wanted a better way to be able to execute arbitrary methods on an unknown object, I would suggest you look into PHP's reflection capabilities.
That being said, the problem with your code would appear to be that you are trying to execute a method via string, where what you need to do is utilize the method's name. What I would suggest is that within your loop where you explode the string on ->, you try to detect if it is a method or not, and then act accordingly. That could look like this:
foreach( explode('->', $name) as $value)
{
$value_trimmed = rtrim($value, '()');
if ($value === $value_trimmed) {
// this is a property
$tempRow = $tempRow->{$value};
} else {
// this is a method
$tempRow = $tempRow->{$value_trimmed}();
}
}
You should probably also do some validation on the input as well to make sure you have valid property/method names for each segment, as well as add validation that the entire string is indeed properly formed (i.e. you don't have things like foo->->bar(())). Of course this make no mention of how to handle array like foo[0]->bar() which you might also need to accommodate.

PHP - way to validate many parameters of a function call quickly

I have identified an issue that always produce bugs in my application. It is that PHP is generally quite lax about passing null or empty variables to a function. For example
function do_this($a, $b, $c) {
....
}
One error-prone call could be
do_this($request['a'], $request['b'], $request['c']);
As PHP just silently passes null if any of the keys is not found. I have tried use doing error checking inside the function, as below:
function do_this($a, $b, $c) {
if (empty($a)) throw new Exception('$a is not defined!');
if (empty($b)) throw new Exception('$b is not defined!');
if (empty($c)) throw new Exception('$c is not defined!');
}
It's a headache when the function takes many parameters.
I could use E_STRICT, but I am using many third-party plugins and working off Wordpress, so I'll be getting warnings from other packages.
What's a good way to validate many parameters of a function call in PHP?
First off. you shouldn't be passing values without checking your indices like that. That's very very bad.
As for your question, why not just define default values and then loop through your variables to print an error?
function do_this($a = null, $b = null, $c = null) {
$numargs = func_num_args();
$arg_list = func_get_args();
for ($i = 0; $i < $numargs; $i++) {
if ($arg_list[$i] == null) {
//fail
}
}
}
You don't need all that code of course, just an example.
See here for more info : http://www.php.net/manual/en/function.func-get-args.php
You don't need to use empty inside your function, since the variables are certainly set. if (!$a) will do.
It's always possible to pass incorrect values, regardless of whether this happens due to variables not being set in the scope calling the function or just because the values are bad. At some point you need to check anyway. See PHP function param type best practices.
You must check in the scope calling the function for non-existing variables, not within the function. I.e.:
if (isset($foo, $bar, $baz)) {
do_this($foo, $bar, $baz);
}
You must always do this if there's a legitimate chance the variables may not exist, this is not specific to passing them as parameters to functions.
Have a look at args module from NSPL. It makes argument validation an easy process. To check all arguments in the function from your example you just have to add only one line of code:
function do_this($a, $b, $c)
{
expectsAll([nonEmpty, int], [$a, $b, $c]);
// do this...
}
More examples here.

What's the best way to verify that an element is ready for use in a foreach() loop in php?

example:
foreach($boxes as $box) {
echo "$box \n";
}
Used to be fairly easy, I could just wrap the foreach around a check like:
if(is_array($boxes) && count($boxes) > 0) {
//foreach loop here
}
Without having to worry about a warning getting thrown if for whatever reason bad input was passed to the $boxes array.
When Iterators, were added to the mix, this no longer works, as Iteratable objects are not arrays. So, I have a few solutions, but am wondering if there is a 'best practice' for this.
// 1:
if($boxes instanceof Traversable && count($boxes) > 0) {
//foreach loop here
}
// 2:
if($boxes && count($boxes) > 0) {
//foreach loops here
}
There are others, but these seem like the most obvious. Anyone have any suggestions. PHP docs seem to be silent.
You shouldn't have the count($array) > 0 part, because a) foreach works fine with empty arrays, b) objects can be Traversable yet not be Countable and c) the value returned by count() may even (for objects) be disconnected from the number of items the traversal will yield.
And #1 there is different from #2; since $boxes instanceOf Traversable is not the same as $boxes. Also note that internally arrays don't implement Traversable.
I would go with
if (is_array($boxes) || $boxes instanceof Traversable) {
foreach (...)
}
This still doesn't guarantee that the traversal will be successful; the iteration may throw an exception at any point. In particular, for some classes it may not make sense to traverse them more than once.
I think generally in these cases you would probably know that the variable is going to be iterable if it is not null or false etc., so I would be happy to just do:
if ($boxes) {
foreach ($boxes as $box) {}
}
Maybe that is naive though
One possibility depending on your php version is a cast:
<?php
$a = array('foo', array('bar'));
foreach ($a as $thing)
foreach ((array) $thing as $item) // <-- here
echo "$item\n";
?>
This test will give true for both arrays and array-like objects
if (is_array($obj) || $obj instanceof Traversable) {
foreach ($obj as $item) { /* foreach loop is safe here */
}
}
In PHP5 you can iterate over any array or object so..
if (is_array($my_var) || is_object($my_var)) {
// Do some foreachin'
}

Do you consider foreach((array)$foo as $bar) a code smell?

Do you consider this a code smell?
foreach((array)$foo as $bar)
{
$bar->doStuff();
}
Should i use that instead?
if (isset($foo) && is_array($foo))
{
foreach($foo as $bar)
{
$bar->doStuff();
}
}
Any other good practices to cover not set variables and assert an array?
They're both code smells. The second one is just evading all the error messages, kind of like turning off the fire alarm before you set your kitchen on fire. Both of those tell you that you have no idea what's in the variable $foo or if it's even been defined in the code above. You need to go back up through the code and find out exactly what's going on with $foo.
If it was my code, $foo would probably be always defined either as an array, or else false to indicate the array isn't needed:
if(do_we_need_an_array())
$foo = function_returning_an_array();
else
$foo = false;
[...snip...]
if($foo)
foreach($foo as $f) { ... }
If you are testing if variables are set, you can initialize them:
if (! $foo or !is_array($foo))
$foo = array();
foreach($foo as $bar)
{
$bar->doStuff();
}
Personally, I would never do the first method and always opt for the second.
If $foo should always be an array, then the second form would be much better if you did some kind of handling for the error case, e.g.:
if (isset($foo) && is_array($foo))
{
foreach($foo as $bar)
{
$bar->doStuff();
}
}
else
{
// This should not happen, exit angrily.
exit("Oh crap, foo isn't an array!");
}
Of course you don't have to just exit the application, but do whatever is appropriate in that case, maybe logging or some alternate logic.
(array)$foo != if (isset($foo) && is_array($foo))
The (array) cast can be useful for casting objects to arrays or scalars to arrays so you can create consistent interfaces to variables that may contain single values or arrays.
(array)$foo == array($foo)
As defined in the PHP Manual for Array Types.
So if you need to always use an array then the first code snippet you presented would be the answer. However type casting rules still apply so you may not get what you want, so look to the manual for more info. Otherwise the second option would prevent accessing unset variables that are not arrays.
As far as a code smell, I would say that checking for unset variables can certainly be avoided, however always knowing that a variable is going to have an array is more often than not, going to creep up. So I would aim to keep code wrapped in is_array($foo) if-then statements to a minimum.
I usually do this to make sure a foreach can handle both scalars and collections:
<?php
foreach (makeSureTraversable($scalarOrCollection) as $val)
{
// Do something.
}
function
makeSureTraversable($anything)
{
if (is_array($anything) || ($anything instanceof Traversable))
{
return $anything;
}
else
{
return array($anything);
}
}
This way I also handle classes that implement Traversable (from the SPL), which means allowing them to be used in foreaches.
if (!isset($foo) && !is_array($foo)) {
throw new InvalidArgumentException('Wrong array passed');
// Or do something to recover lost array
}
foreach($foo as $bar) {
$bar->doStuff();
}
There's quite a few times that you'd like to write a function to take one or more values for a parameter:
function getNamesById($id) { }
In this case, it would make sense that if this function was called with an array of ids, it should probably return an array of names. Similarly, to save the calling code from having to wrap the input in an array and then unwrap the output, if you just pass a scalar, then a scalar should be returned. Consider the likely contents of the function designed to handle both scalar and array parameters:
function getNamesById($id) {
$returnAnArray = is_array($id);
$output = array();
foreach ((array)$id as $theId) {
// perform some logic
$output[] = someFunction($theId);
}
return $returnAnArray ? $output : $output[0];
}
You can see that in this case, casting to an array definitely makes things a lot easier for everyone. As they say, be liberal in what you accept... As long as it is documented that it is expected that a variable could be either, then I see no problem. PHP is a duck-typed language, which has both benefits and drawbacks, but this is one of the benefits, so enjoy it!

Categories