...

View Full Version : Managing Broken Includes / Requires



Custard7A
10-11-2012, 12:15 PM
Hi coding gurus! I'm trying to create a method of including a PHP script to my files that allows me to handle what happens when something goes wrong with the inclusion. Blank pages when something goes wrong is lame!
Not really necessary; It's an experiment more than a problem, I like the idea of covering everything I can. I also make thing harder on myself than I need to, because that seems to be a great way to learn things.

Let's say I put this code at the top of my files:



if(include 'alpha.php')
{ echo 'Success'; } // In practical use I would initialize some objects or something.
else { die('Could not include vital script.'); }

So far it works like a require, but allowing me to say something when it doesn't work. However, I wanted to send the user something a bit more comprehensive and useful, than a little string.

I didn't want to do something like this either:



if(include 'alpha.php')
{ echo 'Success'; }
else { die('<span style=\"ect\"> Formatted message. </span>'); }

In real-world use the above would be a big mess of code, as well as being just awful if I ever wanted to change the error message at all (As would the first solution, for that matter).
Hence, my ideal result would be to trigger a HTTP 500 error page, which as well as having all the information the user needs to know (Even if it's not technically a server error? I don't know.), is also set up to record the error and give the user some useful options (Go to home-page, retry page, ect). It didn't have to be a HTTP 500, I'm just using it for the purposes of trying this.

My first response was something to the effect of: (Error link for example purposes only)



if(include 'alpha.php')
{ echo 'Success'; }
else { header('error.php?id=500&cause=include_failed'); }

This returns three warnings:
Warning: include(alpha.php) [function.include]: failed to open stream: No such file or directory in..
Warning: include() [function.include]: Failed opening 'alpha.php' for inclusion..
Warning: Cannot modify header information - headers already sent by.. (The include line)

Note: The file doesn't exists there, I'm testing the else by including a non-existent file. So basically it's just headers already sent by the include, which wasn't totally unexpected. (Not that I thought very hard when the headers would be sent here).

However, by accident I discovered this produces an error 500 if the file comes back false:



if(@include 'alpha.php') // Notice the error silencer.
{ echo 'Success'; }
else { header('/'); }

Doesn't matter what the header link is, as long as a value exists the presence of the header call causes the HTTP 500. I also tried this with "display_errors" set to off in my php.ini (Yes, it's only on for development), and it produced a HTTP 500 even without the @ silencer.

It does make sense in a way, but not all errors send a HTTP 500 when silenced! If that were so I could be doing this simply with a @require I don't understand why these methods are behaving differently. Perhaps, fatal errors can't return a HTTP error, but others will? I would like to understand this behavior, if anyone knows!

One other issue I noticed with what I was trying to do, is when the include works but the script has errors, which was the other thing I wanted to catch. It will slip right through this check..

Consider I make alpha.php this:



<?php undefinedFunction(); ?>

Now the script will return a fatal error, but will also return true as being included. Since that is all if(include 'alpha.php') checks for, I might as well be using a file_exists or something. How can I catch when my include script is producing errors without hard-coding return false type things into every possible instance? Perhaps I could check for a value that might be hidden if errors choke up the result (I can see that working for fatal errors, but other errors getting past..), or wrap everything in alpha.php with some sort of error catch that returns false or true?

I thought it was time to see what other people knew about the subject. I have little experience. Please no "Your idea is terrible, just trust your includes to always work"; That's already my fall-back, I just want to be clever. I'm open to different approaches too.

poyzn
10-11-2012, 05:59 PM
file_exists (http://php.net/manual/en/function.file-exists.php) ?

Custard7A
10-11-2012, 08:07 PM
file_exists (http://php.net/manual/en/function.file-exists.php) ?

Yes, I did briefly consider that, but only tried it to see the behavior once you mentioned it. I used this:



if(file_exists('alpha.php'))
{ include 'alpha.php'; }
else { header('/'); }

- It produces an HTTP 500 when the file doesn't exist (And used with an invalid header call like this, obviously), regardless of whether PHP error reporting is on or not. Again, I find myself confused as to why some errors send HTTP 500 and why some are content with sending a blank page. (This is even more confusing, because I expected a "Headers already sent" at least).

- Doesn't matter to it if the file that exists is producing fatal errors or not, so it's pretty much doing the same as the if(include 'alpha.php') I was experimenting with. With the exception that it doesn't use the php.ini include path, which is kind of important. (Yeah I could get the path via ini_get, but so far using file_exists doesn't seem to solve the problem. It still seems inconsistent or 'hack-ish' getting the error this way, and can't find the status of the file.)

Thanks anyway..

Fou-Lu
10-11-2012, 10:02 PM
Those headers are no good. There is no such thing as / as an interpretable HTTP header. Perhaps you mean to use a Location: directive? Headers already being sent is caused by previous output, which does include error messages. You cannot specify anything requiring headers anytime after output (sessions, cookies, header, etc), unless the output is buffered.

You cannot do this to detect a link failure on the included script. That is an E_ERROR which is fatal for a reason, and PHP cannot continue with parsing. Because of this, there is no way to capture it at runtime. I don't even think the APD would let you evaluate and capture these, but you can execute a system call to the lint using an exec call to php.exe -l; if it returns 0, then it was syntactically accurate.

This is a complete waste IMO. Syntactical errors should be caught during development, long before they hit a production server.

If you insist on a check against an include instead of a forced require, I'd suggest keeping it with a defined constant. Since you are not using _once functionality, you should be doing it anyways to prevent attempts to recreate definitions to classes and functions which are fatal when attempting to do so.


// include.php
<?php
if (!defined('INCLUDE_H'))
{
define('INCLUDE_H', true);
// definitions
}

// index
include 'include.php';
if (!defined('INCLUDE_H'))
{
die('Failed to include.');
}

Oldschool.

Custard7A
10-12-2012, 07:45 PM
Oh! Thanks Fou-Lu.. A simple mistake of omitting the "Location:" directive, but it could have been making a difference. Sorry if I wasn't clear; I was aware the header call existing there was an invalid move. I was merely interested in the idea of intentionally causing a HTTP 500 error. I'd also like to clarify, that I don't like the idea of using invalid code to get results. I was more-or-less wondering if anybody had a better solution to what I was trying to do. I'm beginning to think it was a bad idea to start with.

Please note: I am liable to be writing PHP which will then be used on a website run not by me, but by script-kiddies, and which will also be a prime candidate for further development (Possibly by said script-kiddies). I didn't want to come over as an attempt to be lazy and get away with not checking errors. I simply thought it would be nice, if my vital PHP resources aren't working right in runtime, to send the user a nice message, and to have some data to work with in regards to logging the problem.

So I'm wasting my time with this? I don't suppose I could check anything while the include is being buffered, and still use a header redirect? (I know little about output buffering)

Fou-Lu
10-12-2012, 08:01 PM
Not for fatal you cannot.
php logs its own errors as well, so you can find those either in the same directory or in the webhost's error log.

Using file_exists and is_readable is a better option. You can branch that for an require[_once] call and use the else to display something else.

Custard7A
10-14-2012, 03:26 AM
Mmh, I was suspecting as much from your last post. I suppose PHP's error file is my only option to log fatal errors in runtime, seeing as fatal errors always get the last word.

That aside, at least checking the file itself seems viable. I like that last suggestion the best so far, however, I'm unfamiliar with is_readable. The documentation seems to imply the only readability it covers is the permissions, and it caches the results. I could un-cache them every time, though I'm not really concerned with permissions being changed.

Hah, I just thought of this:



if(file_exists('alpha.php'))
{ require_once 'alpha.php'; }
else
{ require_once 'error.php';
die(); } // Or die at end of that file.


It's dynamic, isn't cluttery, and a relatively simple little file like "error.php" would be a lot easier to keep exclusive from edits and not have errors itself than hordes of really important stuff in "alpha.php".

Is this innovative, perhaps, or am I over-looking something else?

Edit: I remembered, file_exists doesn't use the include_path, so this solution would require a bit more. :(



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum