12

How can I make the main PHP script return false from inside a class or a function?

Why: this is because of the built-in webserver:

If a PHP file is given on the command line when the web server is started it is treated as a "router" script. The script is run at the start of each HTTP request. If this script returns FALSE, then the requested resource is returned as-is.

from the documentation about the PHP Built-in webserver

In other words, you return false in your router script so that the built-in webserver can serve static files. Example from the documentation:

if (preg_match('/\.(?:png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) {
    return false;    // serve the requested resource as-is.
} else { 
    echo "<p>Welcome</p>";
}

The thing is that I'm trying to add that behavior to a web framework: I don't want to write that into index.php. I'd rather encapsulate that logic into a class (middleware) that will halt the script's execution if php_sapi_name() == 'cli-server' and a static asset is asked.

However, I don't know how I can make the whole PHP script return false from a class or a function, since obviously return false will return from the current method/function/file and not from the main file.

Is there a way to achieve the same behavior with exit() for example? I realize I don't even know what return false in the main file actually means (is that a specific exit code?).

1
  • @Terminus return false has a specific meaning for PHP's built-in server, see the documentation I quoted. Commented Oct 31, 2015 at 11:35

3 Answers 3

6
+50

You should have the router invoke the class method, and then, if the method returns false, you return false from your router file.

Of course it can turn into a headache. There are basically only two methods to achieve what you want to achieve.

There is a faster way though, you can abuse exceptions and create a specialized exception for the case:

StaticFileException.php

<?php
class StaticFileException extends Exception {}

router.php

<?php
try {
    $c = new Controller();
    return $c->handleRequest();
} catch (StaticFileException $e) {
    return false;
}

Once you have this kind of code in place, just throw new StaticFileException and you're done.

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you for the ideas. I'm ideally looking for an equivalent of return false using exit() or something like that (if it's even possible…). If it's not possible, I'm afraid I'll indeed have to look into workarounds :/ But I'd rather keep the index.php as small/simple as possible.
I don't get how wrapping the whole router file into a big try-catch complicates it. By the way: the only way to jump-back from nested contexts is throwing an exception.
It complicates it for framework users. I'm writing a framework. I want users to not have to care about this detail (because they will have to write and understand the index.php). Also die(), exit(), etc are other ways to break nested contexts.
In that case have them (tell them) edit another file and use index.php only to handle that logic. die and exit don't really break nested context from a developer point of view, they just abruptly end the script. What you are really asking is: did the PHP devs have the webserver magically convert a special exit code to a static file resource? AFAIK the answer is no.
"did the PHP devs have the webserver magically convert a special exit code to a static file resource?" Exactly :)
|
4

If the method in your class handles static assets by using exit then the solution can be as simple as replacing exit with return false and having the caller of that method simply return the method in the global scope as well.

So if your class looks something like this...

class Router
{
    public function handleRequest($uri)
    {
        if (is_file($this->docRoot . $uri->path)) {
            exit; // static file found
        } else {
            // handle as normal route
        }
    }
}

Just replace exit there with return false ...

            return false; // static file found

Then if your index.php works something like this...

$router = new Router($docRoot);
$router->handleRequest($_SERVER['REQUEST_URI']);

Simply add a return infront of the handleRequest method like so....

return $router->handleRequest($_SERVER['REQUEST_URI']);

This should have minimal side-effects on your framework design and as you can see requires very little code and refactoring because returning from the script's global scope only has a single side-effect in PHP (in that it returns the value to calling script i.e. if you used include/require as an expression in an assignment). In your case if index.php is the calling script then you have nothing to worry about here just by adding return infront of that method.

Of course, once you return the rest of the script will not continue so make sure it is the last statement in your index.php. You can even just assign the return value to a temporary value and return later if you needed for logic....

$router = new Router($docRoot);
if ($router->handleRequest($_SERVER['REQUEST_URI'])) {
    /* if you need to do anything else here ... */
} else {
    return false; // otherwise you can return false for static here
}

In general I would say that calling exit from inside of a function/method is almost never desirable. It makes your class harder to test and debug and really has no upside from the alternatives like throwing an exception, or just returning from the method, and letting the caller handle the failure scenarios gracefully.

2 Comments

That's a good idea. I just want to point out that today I don't use exit() at all. Static files are just not supported with the built-in server. The one thing that's making it hard to implement for me is that the framework is based on middlewares. Since a middleware must return a ResponseInterface object, I can't return false (I'd like the "static files" feature to be implemented as a middleware to be properly encapsulated). Anyway, that's food for thoughts, thanks.
In which case the suggestion to the throw an exception and return false from the global scope with a catch block is probably your best bet.
0

Isn't possible to play with register_shutdown_function or with auto_append_file setting to deal with that? Not really nice but maybe that can do the job.

2 Comments

Ohh I like the way you think :P I'm afraid register_shutdown_function will mean I'm in a function, so returning false will end the function not the script :/ And for auto_append_file I'm afraid it's not possible to use that at runtime I couldn't get it to work, even in a dumb test file (additionally it would be soooo confusing for users, but why not).
You spoke about "exit" and also about a framework context with internal php webserver it's why I made these purposes. I'm totaly agree with you: both of then aren't very nice.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.