Jump to content

Why don't include() and OR get along?


mellisande
 Share

Recommended Posts

I've noticed that the require() and include() statements don't seem to get along too well with the OR statement. For example, these two lines seem to behave identically:

include('some_file.php') or exit('That isn't a file.');

// Nothing interesting here.

And these two also seem to behave identically:

require('some_file.php') or exit('That isn't a file.');

?>

It would make more sense to me for the parser to ignore the OR parts if they aren't supposed to work with include() and require(), and simply move on as if the OR part wasn't even there. Or better yet, throw an error. After all, PHP just loves yelling out "Parse error" at me.I've found a way to get the same results without using the OR statement next to require() or include() (it even verifies that I got the right file too, as an added bonus), but it still leaves me wondering: Is this a bug? Or by design? And if it's by design, is there any particular reason why?

Link to comment
Share on other sites

Earlier I wasn't getting any errors about this from PHP, so I tried to change my error reporting level and apparently caused PHP to quit reporting everything. I changed it back to exactly what it was before and for some reason, I'm getting errors about it now. Weird...It looks like in require('file.php') or die('Unable to find file'), the OR clause causes the parser to evaluate the string 'file.php' as either true or false before passing it to require(). As a result, require() is receiving Boolean true (1) rather than the string 'file.php'. It doesn't seem to display the same behavior with other constructs (mainly the print one), so maybe it's just a bug or something.I really can't think of any way that this behavior would be useful... But I guess just one person finding it annoying isn't really enough to mean it's really not worth keeping.Oh well, either way, I got it working now, haha. Thanks anyway though. :)

Link to comment
Share on other sites

The tricky bit here is that you think you're having

(include (file)) or error_handler;

but the truth is that with or without brackets, you're actually having

include (file or error_handler);

Because include and require are language constructs, not functions. THAT is by design.Now, what's the impact of this you ask? The impact comes from another tricky bit - that any non empty string evaluates to true, so the error handler is never executed. If the file is not found, this will result in an error from PHP, and that's that.In the case of include, if you actually write things out as the first example above, it would behave slightly differently, because like Deirdre's Dad mentioned, the include statement, similarly to functions, returns a value. It is null by default, which evaluates to false, so your error handler will always be executed if the include statement doesn't return a value.The best way to check in advance if the file exists is to do it "manually" with an if statement that uses is_file(), and act accordingly on true and false.

Link to comment
Share on other sites

Hmm... I hadn't thought of it that way. That certainly does explain why things were behaving strangely though. So then,

(include('file.php')) or throwAnError()

would make PHP try to include 'file.php', and if it couldn't include it, then call the throwAnError() function?I was kind of more interested in whether or not the file could be included, not whether or not it existed. I doubt if it would happen often, but something could always go wrong and deny my scripts access to include the file, even though it exists.That's a good idea though. If for some reason inclusion fails, having my error handler check if the file exists and log that too would probably be helpful in figuring out what went wrong.Now I'm wondering though, why would the print statement behave differently? Print is a language construct too, isn't it? From what I understand, a language construct is just like a function, except that you can call it with or without parenthesis and it will work. Print and echo work that way too, so if:

include('file.php') or die('Oops')

throws an error because it evaluates 'file.php' as true before passing it to include (a language construct), then if I tried:

print('Hello') or die('Oops')

wouldn't I get the number 1 (or true) printed to my screen, because PHP is evaluating 'Hello' as true before passing it to constructs?

Link to comment
Share on other sites

Hmm... I hadn't thought of it that way. That certainly does explain why things were behaving strangely though. So then,
(include('file.php')) or throwAnError()

would make PHP try to include 'file.php', and if it couldn't include it, then call the throwAnError() function?

Not exactly. If it includes it and if it returns a truthful value from it (which can happen if in the included file, you end up with an appropriate return statement)... if the included file doesn't return a value or returns a falsy value, the throwAndError() function will be executed.
Now I'm wondering though, why would the print statement behave differently?
"Evaluating as true/false" and "printing something" are two different things. "Evaluating as true/false" means you're still having the original value, but are treating it as if it's a boolean true/false. "printing something" would actually mean to print the string representation of whatever you want to print.If you had
print 0 or throwAnError()

you'd print the string representation of whatever throwAnError() returns, because 0 evaluates to a boolean false, so the throwAnError() function is executed, and used as the thing to print.If something is evaluted to true, it would print its string representation. For strings, that's the string itself. For an actual boolean value, that would indeed be "1".

Link to comment
Share on other sites

If you had
print 0 or throwAnError()

you'd print the string representation of whatever throwAnError() returns, because 0 evaluates to a boolean false, so the throwAnError() function is executed, and used as the thing to print.

That's not entirely true. The OR operator doesn't work that way, OR is a boolean operator. That means the OR expression will evaluate to a boolean value, and that's what gets printed (the boolean value). OR doesn't have a special meaning where it evaluates to the first non-false value, it always evaluates to a boolean.That is, the expression (0 or throwAnError()) evaluates to either true or false, so this statement would either print true or false (actually 1 or 0):print (0 or throwAnError())This type of thing can be confusing to spot, I found one of these errors in my own code recently. I have a database class that includes a method called exec, which takes an optional parameter to either pass the error back to the calling code or handle the error normally like the rest of the database class. Originally the method looked like this:
  /*  exec  Execute a SQL statement and return the result.  If the argument is true, do not die on an error.  This returns a mysql result resource.  */  function exec($passthrough_errors = false)  {	$this->check_db();	if ($this->sql_stmt == '')	  trigger_error('SQL statement not set', E_USER_ERROR);	if (is_array($this->params) && count($this->params))	{	  $this->sql_stmt = vsprintf($this->sql_stmt, $this->params);	  $this->params = false;	}	$this->last_stmt = $this->sql_stmt;	if ($passthrough_errors)	  return mysql_query($this->sql_stmt);	else	  return mysql_query($this->sql_stmt) or trigger_error($this->error(), E_USER_ERROR);  }

I was having some problems and realized that the function was returning boolean true instead of the return value from mysql_query, for the same reason I described above. The or operator always evaluates to a boolean, so the return value was also a boolean. The fixed version looks like this:

  /*  exec  Execute a SQL statement and return the result.  If the argument is true, do not die on an error.  This returns a mysql result resource.  */  function exec($passthrough_errors = false)  {	$this->check_db();	if ($this->sql_stmt == '')	  trigger_error('SQL statement not set', E_USER_ERROR);	if (is_array($this->params) && count($this->params))	{	  $this->sql_stmt = vsprintf($this->sql_stmt, $this->params);	  $this->params = false;	}	$this->last_stmt = $this->sql_stmt;	$res = mysql_query($this->sql_stmt);	if ($passthrough_errors)	{	  return $res;	}	else	{	  if ($res === false)		trigger_error($this->error(), E_USER_ERROR);	  else		return $res;	}  }

Link to comment
Share on other sites

I suppose you're right... I remember JavaScript having the behaviour I described above, but I never tried it with PHP (I use the ternary operator if I need a default value), so I suppose it could be like that there.

Link to comment
Share on other sites

That's right, the operators work differently in java script:

The logical operators are generally used with Boolean values and, when they are, they return a Boolean value. However, both && and || actually return the value of one of their operands, so if the relevent operand is not a Boolean value, the operator may return a non-Boolean value.
Link to comment
Share on other sites

I'm kind of confused...If I run this code:

<?phpfunction OtherFile(){	return 'also_missing.php';}require('missing.php') or OtherFile();?>

I get: Warning: require(1): failed to open stream: No such file or directory in [path] on line 5If I run this code:

<?phprequire('missing.php');?>

I get: Warning: require(missing.php): failed to open stream: No such file or directory in [path] on line 2From the second test, I can see that the string inside of the parenthesis is the name of the file that couldn't be found. This would mean that for the first test, the string 'missing.php' is being converted to True (1), and then passed to require(), as boen_robot said.This makes sense to me - OR takes precedence over require(), and the parenthesis on require() aren't implicitly connected to it. So OR is allowed to grab the contents of the parenthesis for itself without taking the require keyword with them. Then OR evaluates itself to True or False, and passes the result (True or False) to require().What I don't understand is why this doesn't work the same way with print(), which is also a construct (assuming I'm not mistaken).

require('file') or someFunction()

throws an error because OR grabs the contents of the parenthesis, evaluates itself to True, and then passes itself (which is now equal to True) to require(). But with

print('Hello') or someFunction()

the result is that "Hello" is printed to the screen. Why doesn't OR grab the contents of the parenthesis, evaluate itself to True, and pass True on to print()? It did that with require() and include(), why not print()?

Link to comment
Share on other sites

I think it's because print() can be used as a function, unlike most language constructs. In other words, if you have parenthesis, you're essentially using it as a function. Try it like:

print 'Hello' or someFunction()

I belive that should result in "1" being printed.

Link to comment
Share on other sites

print 'Hello' or errorFunction()

(without any parenthesis given along with print) also results in 'Hello' being printed to the screen. It doesn't seem to mind that errorFunction() isn't defined, either, but that's probably because there's no need to call it since print is apparently returning something that OR considers to be equivalent to True.I tested the return value of print() too, just out of curiosity - it seems to be returning 1. I also tried out this line of code as well:

exit('Hello') or exit('Goodbye');

The script exited with the message "Hello," ignoring the second part. I tried without parenthesis too, but that just got me a parse error. I think in the case of exit() at least, exit() is too attached to the parenthesis to give them up to OR (judging by the parser throwing fits when you exit without parenthesis).

Link to comment
Share on other sites

I tested the return value of print() too, just out of curiosity - it seems to be returning 1.
A print statement always returns 1.Without the parentheses, the exit statement is evaluated like this:exit('Hello' or exit 'Goodbye');The reason that is a parse error, where using print works, is because print returns a value and exit does not (I think; the PHP parser obviously has some special rules). This line executes:print 'Hello' or exit ('Goodbye');these are syntax errors:print 'Hello' or exit 'Goodbye';exit 'Hello' or exit ('Goodbye');
Link to comment
Share on other sites

Exit does, in fact, return nothing it appears. This fact doesn't seem to matter to OR though, otherwise the line you said would execute, wouldn't.Also, to add to the two lines that throw parse errors, this one does too:

exit 'error'; // Parse error: syntax error, unexpected T_CONSTANT_ENCAPSED_STRING

Like I said, exit() seems much more attached to its parenthesis - to the point where it crashes the parser if you don't supply them.Maybe the thing with require() and OR is just a bug? I find it kind of difficult to believe that something like that would go unnoticed until I ran into it, though. I'm never the first to know. :)It was probably intended for some reason... But I can't imagine why.

Edited by Candle
Link to comment
Share on other sites

Exit does, in fact, return nothing it appears.
Right, you can look that up in the manual:
void exit ([ string $status ] )
A return value of void means it doesn't return anything at all.
This fact doesn't seem to matter to OR though
In a logical expression, anything which does not evaluate to true is considered false. Since a non-value does not evaluate to true, in a logical expression it would be false.
Maybe the thing with require() and OR is just a bug?
Unlikely, this is certainly following a pattern, even though the rules aren't apparent to us.
exit() is a function, it needs the parenthesis.
I originally had that in post 13, but it turns out that according to the manual exit is also a "construct" and not a function. I edited my reply and instead wrote "the PHP parser obviously has some special rules". We're definitely seeing some behavior which is obviously by design, but it's not real apparent what the strict rules are. We have to be careful about statements like this:
exit() seems much more attached to its parenthesis
Exit isn't a living being, it's not "attached" to anything. There are very specific rules in the PHP parser, and we're seeing the results of those rules and trying to infer what the rules are (side note: if you're down for it, you can browse the PHP code written in C to find the actual rules). Be careful about applying human emotions to computers - they don't like it when you do that.Even though the manual categorizes both require and exit as "language constructs", that's not to say they act exactly the same way. Look at the examples in the manual for exit, the only time they use it without parens is when it is passed nothing at all:
<?php//exit program normallyexit;exit();exit(0);//exit with an error codeexit(1);exit(0376); //octal?>

Link to comment
Share on other sites

Right, you can look that up in the manual:A return value of void means it doesn't return anything at all.
My apologies - I tend to have a habit of forgetting about online references and just searching for a result within the language itself (well, at least until the parser starts acting weird... Then I go to the internet). Thus the wording of my post - I actually tested it rather than looking it up because that's how I do things.
In a logical expression, anything which does not evaluate to true is considered false. Since a non-value does not evaluate to true, in a logical expression it would be false.
My comment was toward this statement:
The reason that is a parse error, where using print works, is because print returns a value and exit does not (I think; the PHP parser obviously has some special rules).
I was under the impression that you were suggesting that since exit() didn't return anything, it was resulting in causing trouble with the OR statement, when in reality it was the lack of parenthesis around the argument given to exit(). Again, if that was a mistake, I'm sorry to have confused you.
Unlikely, this is certainly following a pattern, even though the rules aren't apparent to us.
Yeah... Like I said, I'm never the first person to discover a bug. Meaning that all the "bugs" I discover are always either already well known or not bugs at all. :)
I originally had that in post 13, but it turns out that according to the manual exit is also a "construct" and not a function. I edited my reply and instead wrote "the PHP parser obviously has some special rules". We're definitely seeing some behavior which is obviously by design, but it's not real apparent what the strict rules are.
I do agree that this is probably what's going on. It's probably what bothers me the most, too - I hate not knowing the rules I have to play by.
We have to be careful about statements like this:Exit isn't a living being, it's not "attached" to anything. There are very specific rules in the PHP parser, and we're seeing the results of those rules and trying to infer what the rules are (side note: if you're down for it, you can browse the PHP code written in C to find the actual rules). Be careful about applying human emotions to computers - they don't like it when you do that.
Computers don't like that... Haha. Very funny. :)I was just thinking of my math teacher explaining that when you solved 2x+5, you had to undo adding and subtraction before multiplying and dividing because "The x is more attached to the 2 than the 5, so you have to take the 5 off first." Require() allowing OR to take the file name sounded like the loosely attached 5 to me, and exit() being taken along with its parenthesis sounded more like the 2 that was glued to the x. And yes, I was thinking "attached" as in "glued" or "stuck." Or magnets. I like magnets... Or would if my computer hadn't turned me into a robot.
Even though the manual categorizes both require and exit as "language constructs", that's not to say they act exactly the same way. Look at the examples in the manual for exit, the only time they use it without parens is when it is passed nothing at all:
<?php//exit program normallyexit;exit();exit(0);//exit with an error codeexit(1);exit(0376); //octal?>

Yet again, I agree. Exit() stops the script when you call it... Every time... That doesn't sound like require() at all. Obviously they function differently. And the fact that exit() throws an error when you pass it a string without parenthesis while require() accepts it without complaint is plenty of evidence to me that the syntax around them is different.On your side note though... That's actually an interesting idea. The rules would be defined in PHP's source code, wouldn't they. And the PHP Developer Gods have graciously given my humble eyes the opportunity to view the source code in all its splendor... :)I think I'll give it a try and look into the source code to see if I can figure out exactly what the rule with require() is and why OR seems to be allowed to evaluate what's being passed to require() before it's passed.That's a task for tomorrow (actually, later today now) though - I need to recharge my batteries for an exam... later today. :) Edited by Candle
Link to comment
Share on other sites

So, I must have been pretty tired last night. It never occurred to me that the PHP source code would contain more than a few dozen files. Apparently it contains a little over 14 thousand files. Lovely!On the other hand, I think I figured out most of the answers. They were in the PHP documentation on include()... right in front of my nose.So, is it intended? Probably. In Example #4 on the include() documentation it says,

<?php// won't work, evaluated as include(('vars.php') == 'OK'), i.e. include('')if (include('vars.php') == 'OK') { echo 'OK';}// worksif ((include 'vars.php') == 'OK') { echo 'OK';}?>
...which is exactly how boen_robot explained it (maybe he knew all along and wanted me to work for it :)). So, the fact that it was placed right in the documentation pretty heavily implies that the developers know about it, and the fact that they haven't done anything to change it also heavily implies that it was intended.As to why OR is given the contents of the parentheses rather than require(), one of the comments had this to say:
<?php@include('/foo') OR die ("bar"); # <- Won't work@(include('/foo')) OR die ("bar"); # <- Works?>so "or" have prority on "include"
This makes sense - OR has precedence over include(). And since this is a valid (albeit not very helpful) statement:
('A string') or exit('Some error')

then OR can successfully take the contents of the parentheses without taking require with it. Of course, the string containing the file's path evaluates to true, so OR takes the parentheses, evaluates to true and that's what require() gets.I think that just about wraps it up, except for the question of why the developers decided to give OR precedence over require(). It seems like giving require() precedence would be more useful than giving OR precedence. I guess the developers of PHP must know something that I don't though, or else they probably wouldn't have given OR precedence.

Link to comment
Share on other sites

Be sure to read about the values returned by include and require: http://us2.php.net/manual/en/function.include.php They are not intuitively obvious.
I think this comment from Deirdre's Dad was direct enough... did I had to copy&paste it for you to understand that you should "be sure to read about" the whole thing?
Link to comment
Share on other sites

I think this comment from Deirdre's Dad was direct enough... did I had to copy&paste it for you to understand that you should "be sure to read about" the whole thing?
I should have waited before I said that. Sorry if you read this before I changed it.No, you don't need to copy and paste the whole article to get me to read it, but if you wanted me to read the whole thing, it would have been nice if you had said so, because for me, "Be sure to read about the return values" is not the same as "Be sure to read the entire article closely." Edited by Candle
Link to comment
Share on other sites

The only thing that "affected" me was the phrase

(maybe he knew all along and wanted me to work for it :) )
I thought I was direct enough in my explanations, without being an a-hole (I mean, a typical a-hole reply is "Read the fine manual."). I guess I'll have to take a note for myself to point to the PHP manual more often...
Link to comment
Share on other sites

I kind of meant that statement in a good way... It really doesn't bother me if people try to help me do it myself. I actually kind of prefer it that way. "Teach a man to fish, feed him for a lifetime," you know? That's why I tested things myself in previous posts. I almost always start by trying to find out for myself before I ask for help.You've probably heard the term "reading between the lines." When I read things, I think of it like someone is speaking out loud, and the tone of the speaker's voice is based on the way what I'm reading was worded. As a result, when I read this comment:

I think this comment from Deirdre's Dad was direct enough... did I had to copy&paste it for you to understand that you should "be sure to read about" the whole thing?
As a result of the tone of voice, I was seeing something like:
Anybody with a brain would have realized that when he said "return values," he really meant "the entire article." You must be stupid or something.
The first sentence is because you seemed to be saying that following the instructions in the quote would have solved my problem, while at the same time saying that I would have had to do something more that nobody really mentioned. That's contradicting. The second sentence and overall tone was because of the copy&paste comment.Upon reflection, and in light of your latest post, I realize that this most likely isn't the way you intended. In fact, it looks like I caused the very same problem for you (and by extension, me). What a mess.
I guess I'll have to take a note for myself to point to the PHP manual more often...
I'd just like to say that the PHP manual is nice, but I think that it would have been more helpful if you had said that my comment had affected you earlier. I said right away that your comment had affected me, and though it's a bit of a mess, I think that the result of doing so (apparently making amends) is better than the alternative (probably each thinking the other is a jerk). That's another thing about me... I like to make friends rather than enemies. :)Also, I think that I should be the one taking a note for myself: "Never reply to an upsetting comment on the same day that I read it if I don't have to." I should have known better... :)
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...