Simulating "fake" directories using PHP, without .htaccess, mod_rewrite, or 404 redirects

231

Situation

Hello, I'm confused as to PHP's expected/default behavior regarding extensionless PHP files, and/or URL requests that "go past" the actual file that (I want to) processes the request (i.e., PHP's default "fallback" actions before it resorts to completely 404-ing). Here's my situation:

My directory structure on my local server (running nginx 1.5.3 with very basic PHP 5.5.1 setup) looks like the following:

/index
/index.php
/other
/other.php
/rootdir/index
/rootdir/index.php
/rootdir/other
/rootdir/other.php

The contents of all eight files are the same:

<?php
echo $_SERVER['PHP_SELF'] . ', ' . $_SERVER['REQUEST_URI'];
?>

BUT, hitting the respective endpoint produces some strange (to me) results.

Research

GET /index.php
'/index.php, /index.php' # Makes sense...

GET /index.php/something_else
'/index.php, /index.php/something_else' # Also makes sense...

GET /index/something_else
'/index.php, /index/something_else' # Let's call this ANOMALY 1... (see below)

GET /something_else
'/index.php, /something_else' # ANOMALY 2

GET /other.php
'/other.php, /other.php' # Expected...

GET /other.php/something_else
'/index.php, /other.php/something_else' # ANOMALY 3

GET /rootdir/index.php
'/rootdir/index.php, /rootdir/index.php' # Expected...

GET /rootdir/index.php/something_else
'/index.php, /rootdir/index.php/something_else' # ANOMALY 4

GET /rootdir/other.php
'/rootdir/other.php, /rootdir/other.php' # Expected...

GET /rootdir/other.php/something_else
'/index.php, /rootdir/other.php/something_else' # ANOMALY 5

My understanding is that the server redirects to/index.php when it is unable to find what the user is looking for at the request URI; that much makes sense... what I don't understand is:

  1. Why it will do this despite my not having a dedicated 404 page set up (I didn't tell it to try/index.php before 404-ing; I want it to display a legit, non-custom 404 page if something isn't found and/or can't be processed. I figured it should display the default server 404 page when it couldn't find something... apparently that's not always the case...?)
  2. Why it doesn't try/rootdir/index.php when it can't find something within the/rootdir/ subdirectory.

Questions

  1. Would somebody be able to shed some light on what PHP's logic is (or maybe it's nginx's doing; I haven't been able to figure that out yet) with regards to addresses that are not found? Why am I seeing what I am seeing? (Specifically with respect to Anomalies #4 and #5. I expected it to use/rootdir/index.php for handling it's "404," or I expected a real 404 page; the fallback to/index.php was unexpected.)

  2. As a direct corollary (corollical?) question, how can I go about simulating extensionless PHP files that will handle hits that occur "below" them (e.g. in the Anomaly #1 case; that's actually exactly what I want, though it wasn't quite what I expected) without relying on.htaccess, mod_rewriting, or redirects? Or is that a silly question? :-)

References

I'm trying to roll out a custom implementation for handling requests like/some_dir/index.php/fake_subdir and/some_other_dir/index.php/fake_subdir (i.e., different "fallback handlers") without relying on Apache, but the logic behind PHP's (or nginx's?) default fallback behavior is eluding me. These pages are primarily where this question stems from:

328

Answer

Solution:

 GET /other.php/something_else

This is calledPATH_INFO in Apache. As Apache's scanning down a URL's "directories", it will return the first file (or execute the first script) that is actually a file/script, e.g.

GET /foo/bar/baz/index.php/a/b/c/
     ^--dir
         ^--dir
             ^---dir
                 ^---script
                         ^^^^^^^--- path_info

In real terms, the real request is for

GET /foo/bar/baz/index.php

and then Apache will take the unused trailing portion of the directory structure of the URL and turn it into path info. In PHP, you'll have

$_SERVER['REQUEST_URI'] = '/foo/bar/baz/index.php';
$_SERVER['PATH_INFO'] = 'a/b/c';
779

Answer

Solution:

Well, I figured out what was going on: I had a misconfigured nginx server.

Marc B's answer, though related to Apache, prompted me to check out my PATH_INFO value — lo and behold, PATH_INFO was nonexistent, even for requests likeGET /other.php/something_else.

Some more searching turned up the answer. Using nginx'sfastcgi_split_path_info to split the URI into a) the path to the script and b) the path that appears following the script name will fail if done after a use of thetry_files directive, due to its rewriting the URI to point to a file on the disk. This obliterates any possibility of obtaining a PATH_INFO string.

As a solution, I did three things (some of these are probably redundant, but I did them all, just in case):

  1. In a server's location block, make use offastcgi_split_path_info before setting up PATH_INFO via

    fastcgi_param PATH_INFO $fastcgi_path_info;
    

    ...and do both before making use oftry_files.

  2. Store$fastcgi_path_info as a local variable in case it ever changes later (e.g. withset $path_info $fastcgi_path_info;)

  3. When usingtry_files, don't use$uri... use$fastcgi_script_name. (Pretty sure this was my biggest mistake.)

Sample Configuration

server {

    # ... *snip* ...

    location ~ \.php {
        # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
        fastcgi_split_path_info ^(.+\.php)(/.*)$;

        include fastcgi_params;

        fastcgi_index index.php;
        fastcgi_intercept_errors on;
        fastcgi_pass 127.0.0.1:9000;

        try_files $fastcgi_script_name =404;
    }

    # ... *snip* ...

}

Wherefastcgi_params contains:

set            $path_info         $fastcgi_path_info;
fastcgi_param  PATH_INFO          $path_info;
fastcgi_param  PATH_TRANSLATED    $document_root$path_info;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

# ... *snip* ...

Reference

The following links explain the problem pretty well and provide an alternate nginx configuration:

People are also looking for solutions to the problem: php - How to store query results as member variables from within a class using a mysqli function?

Source

Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.