I have a PHP foreach from an array, the array is given to me by my DB provider via a soap web service so I cannot change the array I get. When there are no elements to return, I get an empty array, this results in
Warning: Invalid argument supplied for foreach()
the loop looks like
foreach (($point1['return']) as $val)
Where can I put an # to stop this warning, and if I cant, what I do I do to turn off php warnings.
Hiding the warning is not the right way. You should check whether it exists and is an array.
if (is_array($point1['return'])) {
foreach ($point1['return'] as $val) {
...
}
}
PHP is_array()
Actually, turning off warnings or using the # operator is not the right way to go 99% of the time.
Solve the problem instead of hiding it.
foreach() can handle not only arrays but also objects by either using the the default "all visible properties" implementation or a custom implementation via the traversable/iterator interface.
And a "DB provider via a soap web service" is something where I'd keep an eye on the possibility of (suddenly) having an object/iterator instead of a plain array.
So, if you're going to test the existence and data type before passing the variable to foreach, you should consider not only testing for is_array() but also for instanceof Traversable.
<?php
class Foo implements Iterator {
protected $v = 0;
public function current() { return $this->v; }
public function key() { return $this->v; }
public function next() { ++$this->v; }
public function rewind() { $this->v=0; }
public function valid() { return 10>$this->v; }
}
//$a = array(1,2,3,4);
$a = new Foo;
if( is_array($a) || $a instanceof Traversable ) {
foreach($a as $e) {
echo $e, "\n";
}
}
An empty array does not cause that error, the problem is that you are trying to iterate trough something that is not an array. You could add a check using is_array function.
Better to let errors display but check that the input is an array first. So you could wrap the foreach in an if, like this:
if ((is_array($point1)) && (is_array($point1['return']))) {
foreach (($point1['return']) as $val)
...
}
Check first for an array:
if(is_array($point1['return']))
{
...
}
You can also explicitly cast the argument to array:
foreach ((array) $point1['return'] as $val) {
Note: this still will issue undefined index, if there is no 'return' key in $point1
Check whether that is actually an array. with is_array(); !!
There's no need to suppress the warning.
As a matter of fact, It's not possible to suppress that invalid argument warning.
Paste this into your function file:
set_error_handler(function($errno, $errstr){
if(stristr($errstr,'Invalid argument supplied for foreach()')){
return true;
}
return false;
});
Related
I have some code that looks for a branch of a JSON string called applications and then for each item it finds, it executes some code.
This works fine if the JSON contains "applications" but falls over if it doesn't.
My PHP
$json = file_get_contents("programs.json");
$array = json_decode($json, true);
foreach ($array['applications'] as $app) {
$application = $app['application'];
// Do some stuff
}
My JSON looks like this:
{
"hostname": "MINIPC025",
"applications": [{
"application": "7-Zip 19.00 (x64)"
},
{
"application": "Active Directory Rights Management Services Client 2.1"
},
{
"application": "Adobe Acrobat Reader DC MUI"
}
]
}
So my question is - is it possible to put some kind of if statement that says only loop through the applications bit of the JSON data if the applications property exists.
Thank you.
There are many ways to Rome, as they say. A simple if, where you can use a variety of native PHP functions, but also the usage of the null-coalescing operator ??.
They all end up with the same idea -- just iterate over $array['applications'] if it exists. None of these checks will actually check if its an iterable variable though, so if $array['applications'] = 'myString';, you will still receive errors. You can use the native PHP function is_iterable() to check that.
Using null-coalescing operator ??
foreach ($array['applications'] ?? [] as $app) {
// ...
}
Using array_key_exists()
if (array_key_exists('applications', $array)) {
foreach ($array['applications'] as $app) {
// ...
}
}
Using isset()
if (isset($array['applications'])) {
foreach ($array['applications'] as $app) {
// ...
}
}
Using !empty()
if (!empty($array['applications'])) {
foreach ($array['applications'] as $app) {
// ...
}
}
Of the above here, I'd recommend using either isset()/!empty() or the null-coalescing operator, as array_key_exists() will still return true for the case of $array['applications'] = null;.
Some useful, related reading
What's the difference between isset() and array_key_exists()?
isset() and empty() - what to use
You could use isset to check if a variable is declared and is different than NULL
if(isset($array['applications'])){
foreach ($array['applications'] as $app)
...
}
You can use
property_exists($json, 'applications')
$json = file_get_contents("programs.json");
$array = json_decode($json, true);
if (property_exists($array, 'applications')) {
foreach ($array['applications'] as $app) {
$application = $app['application'];
// Do some stuff
}
}
Works only if json is object.
I need to know if there is a better way to avoid Call to a member function xxxx() on null
currently I'm coding as follows but it is cumbersome.
if($event->getForm()
&& $event->getForm()->getParent()
&& $event->getForm()->getParent()->getParent()
&& $event->getForm()->getParent()->getParent()->getData()
&& $event->getForm()->getParent()->getParent()->getData()->getComponente()
){
$componente = $event->getForm()->getParent()->getParent()->getData()->getComponente();
$formModifier($event->getForm(), $componente, $defaultComponente);
}
In PHP 7 this is actually a catchable Error (if you're using hhvm it's a regular Exception):
try {
$componente = $event->getForm()->getParent()->getParent()->getData()->getComponente();
} catch (\Error $e) {
$componente = null;
}
if ($componente !== null) {
$formModifier($event->getForm(), $componente, $defaultComponente);
}
In PHP 5 there is a workaround using intermediate variables and the and keyword instead of &&:
if (
$f = $event->getForm() and
$p = $f->getParent() and
$p2 = $p->getParent() and
$d = $p2->getData() and
$componente = $d->getComponente()
) {
$formModifier($f, $componente, $defaultComponente);
}
If you use && instead of and you'll get "undefined variable" notices and this workaround won't work.
Working examples: https://3v4l.org/0S6ps
no there is no way, but at least you can do some performance improvement
$form = $event->getForm();
if(!$form){
//do error handling
return;
}
$parent = $form->getParent();
if(!$parent){
//do error handling
return;
}
$p_parent = $parent->getParent();
if(!$p_parent){
//do error handling
return;
}
$data = $p_parent->getData();
if(!$data){
//do error handling
return;
}
$component = $data->getComponente();
...
this way you call each function only once and you can do better error handling
I think this is a great example of a bad code. By having a code like this you're breaking several rules and making your life much harder than it should be.
Your code is rigid, fragile, hard to understand and maintain etc.
Simpler is ALWAYS better.
If you can't make your $xx->getComponent() a proper object easily accessible without such ugly nested relationship, you should at least encapsulate the method into something appropriate and use that instead, so if anything changes, you don't have to go full mental and change it all over the place.
This class seems strange in it's creation, but if you are not extracting these methods dynamically using __call(), you can use method_exists() in a loop inside a function, something similar to:
function getMethodChain($class,$arr = ['getForm','getParent','getParent','getData','getComponente'])
{
# First check the object is set
if(!is_object($class))
return false;
# Loop intended method chain
foreach($arr as $method) {
# Check if the method exists in the current class or passed already
$useClass = (!isset($classPass))? $class : $classPass;
# Check if the method exists in the current class
if(is_object($useClass) && method_exists($useClass,$method)) {
# Assign this class/method to use next in the loop
$classPass = $useClass->{$method}();
}
else
return false;
}
# Just send back
return (isset($classPass))? $classPass : false;
}
The use would be something like:
# This will either be the data you expect or false
$componente = getMethodChain($event);
I've been out of PHP programming for a while, but I'm almost sure I used to be able to write something like the following:
function checkData($data, $moreData) {
if ($foo != validate($data)) {
return false;
}
if ($bar != validate($moreData)) {
return false;
}
$result = "$foo" . "$bar";
return $result;
}
...where "$foo" and "$bar" haven't been set yet and where the "validate()" function either returns validated data, or returns false if validation fails.
I can't figure out where I'm going wrong, but that code (and variations of it) is throwing an "Undefined variable" error for $myVar.
What would be the correct syntax for this concept?
I think you meant a little bit different thing. Instead of
if ($foo != validate($data)) {
You've been using this
if (!$foo = validate($data)) {
What is happening there is:
1. Call validate function
2. Assign the result to variable
3. Check if condition for that variable.
It's kind of the same as
$foo = validate($data)
if(!$foo)
But as you can understand it's not recommended way of doing the things as it's hard to read and needs explanation(otherwise why do u ask it here, hehe)
PHP has become more strict over the years. You should always instantiate your variables. The following modifications should resolve your "undefined variable" issues:
function checkData($data, $moreData) {
$foo = $bar = null;
if (!$foo = validate($data)) {
return false;
}
if (!$bar = validate($moreData)) {
return false;
}
$result = "$foo" . "$bar";
return $result;
}
It isn't the conditional that is throwing the warning, but where you attempt to assign the non-existent variables to $result. Additionally, your conditionals are checking for comparison rather than assignment.
here is what im doing
im using
pthreads from - pthreads.org
php Simple Html DOM parser from - simplehtmldom.sourceforge.net
now the process of what i (will) do is:
I am reading a bulk of links that is from a text file.
I initialize a thread to have a separate process
I am creating a log file for this thread so that i will know, what happens later on.
now, this is my code for my thread class.
class ReadLinks extends Thread {
private $conn;
private $links;
private $fileObj;
public function __construct($conn, $links, $fileObj) {
//.. well just asign this to the global variables
}
public function run() {
try {
$this->logMsg("Start Reading Reviews");
$this->readLinks();
} catch (Exception $ex) {
$this->logMsg($ex);
}
$this->closeLog();
}
private function readLinks() {
$this->logMsg("Links");
foreach ($this->links as $link) {
$link = trim(preg_replace('/\s\s+/', ' ', $link));
$this->logMsg("Link: " . $link);
$html = html_readLink($link);
break;
}
}
private function logMsg($msg) {//something to write on the text file
}
private function closeLog() {//closes the textfile
}}
$conn - is my mysqli link to have db actions in the future
$links - is an array of links to be read.
$fileObj- is a resource return from fopen(). ( well to write into a file)
now who is that html_readlink,
its an outer function that is like this:
function html_readLink($link) {
return file_get_html($link);}
basically it is the resource returned by a function from simple html dom parser
now, i have as well a function that reads a link alone to do the other (different business requirement) and im using the simple html dom parser with ease.
with the pthreads, i tried writing the file(logs first) so that i can ensure that everything as well works fine.
about contacting db is not yet sure., well ill try to figure it out if it works fine, but logically it should work.
now when i called this class: its like this:
try {
$thread = new readLinks($conn, $Links, createlog());
if ($thread->start()) {
$thread->join();
} else {
echo "something i need to research if this happens";
}
} catch (Exception $err) {
echo $err; //something i need to research as well if this happens
}
i got this error
Warning: Invalid argument supplied for foreach() in C:\my\path\to\simplehtmldom_1_5\simple_html_dom.php on line 1119
that simplehtmldom code is:
function clear()
{
foreach ($this->nodes as $n) {$n->clear(); $n = null;}
// This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n = null;}
if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);}
if (isset($this->root)) {$this->root->clear(); unset($this->root);}
unset($this->doc);
unset($this->noise);
}
now that is the source code coming from simple html dom. that foreach is the one that is returning the error. now my other code using simple html dom doesn't have a problem with simple html dom. but with pthreads i got this error.
also, when i change my codes and didn't use pthreads, (had some revisions like this:
on pthreads class:
class ReadLinks {// extends Thread {
//insert other codes
public function readLinks() {
$this->logMsg("Links");
foreach ($this->links as $link) {
$link = trim(preg_replace('/\s\s+/', ' ', $link));
$this->logMsg("Link: " . $link);
$html = html_readLink($link);
$this->logMsg(getTitle($html));
//
break;
}
}
and change the way this is called like this:
try {
$thread = new ReadLinks($conn, $revLinks, createlog());
$thread->readLinks();
// if ($thread->start()) {
// $thread->join();
// } else {
// echo "something i need to research if this happens";
// }
} catch (Exception $err) {
echo $err; //something i need to debug and research if this happens
}
everything works fine, i get the desired results.
pthreads is something i need to use since loading bulk links and reading each of them is quite a time consuming process. and i need it to be on a separate thread. now i dont know whats wrong with these pthreads, or simple html dom parser. have i done something unnecessary/wrong? is there other way to do this?
anyone??
EDIT
i followed the answer of Prafulla Kumar Sahu:
the new code for the function clear() of simple html dom is:
function clear() {
if (is_array($this->nodes) || $this->nodes instanceof Traversable) {
foreach ($this->nodes as $n) {
$n->clear();
$n = null;
}
}
// This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
if (isset($this->children))
foreach ($this->children as $n) {
$n->clear();
$n = null;
}
if (isset($this->parent)) {
$this->parent->clear();
unset($this->parent);
}
if (isset($this->root)) {
$this->root->clear();
unset($this->root);
}
unset($this->doc);
unset($this->noise);
}
the result is: it eliminated the error
but it is not the desired result
when using the function
$x=$resource->find($selector,0);
//resource is the return obj of file_gets_content, selector is my css selector string
it returns null/empty where in fact it should have a value.
ive checked a separate function that uses the simple html dom after i updated their code, seems it wasn't affected, and it is working properly. but with my pthread class, it is not working correctly.
The code I have doesn't have a foreach on line 1119; maybe you have an older version. You're getting a warning only, what problem(s) do you see in the results?
1117 // save dom as string
1118 function save($filepath='')
1119 {
1120 $ret = $this->root->innertext();
1121 if ($filepath!=='') file_put_contents($filepath, $ret, LOCK_EX);
1122 return $ret;
1123 }
It happens if the variable you are trying to traverse using foreach is not irritable so please check if your variable is either an array or instanceof Traversable class .
*It may be because you are not getting any value for that variable you want to traverse.
so, I would suggest you to use is_array( $whatever ) || $whatever instanceof Traversable just before foreach.
ie.
if( is_array( $whatever ) || $whatever instanceof Traversable ){
foreach( $whatever as $what ){
//some code
}
}
In your case it is
function clear()
{
foreach ($this->nodes as $n) {$n->clear(); $n = null;}
// This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n = null;}
if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);}
if (isset($this->root)) {$this->root->clear(); unset($this->root);}
unset($this->doc);
unset($this->noise);
}
source:- https://github.com/jalbertbowden/simplehtmldom/blob/master/simplehtmldom_1_5/simple_html_dom.php#L1119
this means you are unable to get $this->nodes correctly, so please var_dump it before you are calling function clear or before the foreach .
I am using PHP 5.3.6 from MAMP.
I have a use case where it would be best to use PHP's Iterator interface methods, next(), current(), and valid() to iterate through a collection. A foreach loop will NOT work for me in my particular situation. A simplified while loop might look like
<?php
while ($iter->valid()) {
// do something with $iter->current()
$iter->next();
}
Should the above code always work when $iter implements PHP's Iterator interface? How does PHP's foreach keyword deal with Iterators?
The reason I ask is that the code I am writing may be given an ArrayIterator or a MongoCursor. Both implement PHP's Iterator interface but they behave differently. I would like to know if there is a bug in PHP or PHP's Mongo extension.
ArrayIterator::valid() returns true before any call to next() -- immediately after the ArrayIterator is created.
MongoCursor::valid() only returns true after the first call to next(). Therefore the while loop above will never execute.
At risk of being verbose, the following code demonstrates these assertions:
<?php
// Set up array iterator
$arr = array("first");
$iter = new \ArrayIterator($arr);
// Test array iterator
echo(($iter->valid() ? "true" : "false")."\n"); // Echoes true
var_dump($iter->current()."\n"); // "first"
$iter->next();
echo(($iter->valid() ? "true" : "false")."\n"); // Echoes false
// Set up mongo iterator
$m = new \Mongo();
$collection = $m->selectDB("iterTest")->selectCollection("mystuff");
$collection->drop(); // Ensure collection is empty
$collection->insert(array('a' => 'b'));
$miter = $collection->find(); // should find one object
// Test mongo iterator
echo(($miter->valid() ? "true" : "false")."\n"); // Echoes false
$miter->next();
echo(($miter->valid() ? "true" : "false")."\n"); // Echoes true
var_dump($miter->current()); // Array(...)
Which implementation is correct? I found little documentation to support either behavior, and the official PHP documentation is either ambiguous or I'm reading it wrong. The doc for Iterator::valid() states:
This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid.
This would suggest that my while loop should first call next().
Yet the PHP documentation for Iterator::next states:
This method is called after each foreach loop.
This would suggest that my while loop is correct as written.
To summarize - how should PHP iterators behave?
This is an interesting question. I'm not sure why a foreach won't work for you, but I have some ideas.
Take a look at the example given on the Iterator interface reference page. It shows the order in which PHP's internal implementation of foreach calls the Iterator methods. In particular, notice that when the foreach is first set up, the very first call is to rewind(). This example, though it's not well-annotated, is the basis for my answer.
I'm not sure why a MongoCursor would not return true for valid() until after next() is called, but you should be able to reset either type of object by calling rewind() prior to your loop. So you would have:
// $iter may be either MongoCursor or ArrayIterator
$iter->rewind();
while( $iter->valid() ){
// do something with $iter->current()
$iter->next();
}
I believe this should work for you. If it does not, the Mongo class may have a bug in it.
Edit: Mike Purcell's answer correctly calls out that ArrayIterator and Iterator are not the same. However, ArrayIterator implements Iterator, so you should be able to use rewind() as I show above on either of them.
Subclassing any Iterator and echo'ing when it's called will tell you how it behaves.
Example (demo)
class MyArrayIterator extends ArrayIterator
{
public function __construct ($array)
{
echo __METHOD__, PHP_EOL;
parent::__construct($array);
}
…
}
foreach (new MyArrayIterator(range(1,3)) as $k => $v) {
echo "$k => $v", PHP_EOL;
}
Output
MyArrayIterator::__construct
MyArrayIterator::rewind
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
0 => 1
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
1 => 2
MyArrayIterator::next
MyArrayIterator::valid
MyArrayIterator::current
MyArrayIterator::key
2 => 3
MyArrayIterator::next
MyArrayIterator::valid
This is equivalent to doing
$iterator = new MyArrayIterator(range(1,3));
for ($iterator->rewind(); $iterator->valid(); $iterator->next()) {
echo "{$iterator->key()} => {$iterator->current()}", PHP_EOL;
}
The sequence in which the methods are called is identical to a custom Iterator:
class MyIterator implements Iterator
{
protected $iterations = 0;
public function current()
{
echo __METHOD__, PHP_EOL;
return $this->iterations;
}
public function key ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations;
}
public function next ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations++;
}
public function rewind ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations = 0;
}
public function valid ()
{
echo __METHOD__, PHP_EOL;
return $this->iterations < 3;
}
}
foreach (new MyIterator as $k => $v) {
echo "$k => $v", PHP_EOL;
}
It seems you may have mixed up Iterator and ArrayIterator, as each has their own valid() api call.
Array Iterator : There is no specific mention to next() etc, but the example clearly demonstrates what you mentioned in your OP, to paraphrase; that no other AI api call needs to be made to determine if current element of array is valid.
Iterator : As you mention: "This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid."
As such, I would stick with ArrayIterator, as it demonstrates the most correct behavior, in that valid() will correctly determine if current element in array is valid, without having to make another api call (next, rewind).
If you want get Mongo to behave like AI does, you could add an instance check before starting the while loop:
if ($iter instanceof MongoCursor) {
$iter->next()
}
while ($iter->valid()) {
// Do stuff
}