Matthieu Napoli
Matthieu Napoli

Reputation: 49533

How to return false from a PHP script from inside a class/function?

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?).

Upvotes: 12

Views: 757

Answers (3)

C&#233;dric Mourizard
C&#233;dric Mourizard

Reputation: 576

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.

Upvotes: 0

Sherif
Sherif

Reputation: 11943

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.

Upvotes: 4

tacone
tacone

Reputation: 11441

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.

Upvotes: 6

Related Questions