Trying to detect your application URI using DOCUMENT_ROOT? Read this first!
The application URI is the part of the URL after the host name (domain
name) and before the home of your application. For example, for this
blog, the URI is /blog/
. If you are writing a PHP application
to be distributed and installed in many environments that you have no
idea how they are configured, you need to write your application to be
as generic as possible when it comes to handling environment parameters.
And one of the things you should not assume, is the path in which the
application is going to be installed, and accordingly, the application
URI.
I used to do the following in my applications to detect the application URI, and I thought that was generic enough to work on any environment:
|
|
I’d place the above function into a helper file and include it in all
application pages. calling app_uri()
should then return the
application URI and it should, presumably, work on all environments,
right? Wrong! Sorry!
First of all, let’s see how the above function works, and then see where
it would fail. Line 2 above uses DOCUMENT_ROOT
to retrieve the
path to the main directory for serving pages from our web server. This
would, in most Debian systems, for example, return
/var/www/html
.
Line 3 would return the path to the file containing the
app_uri()
function. This might be something like
/var/www/html/blog/lib
, assuming the helper file is stored
inside a sub directory named ’lib’. Now, retrieving the application URI
would be a simple matter of removing the DOCUMENT_ROOT
from
that path, and removing the ’lib’ sub directory at the end, which is
what line 7 does, returning the precious /blog/
URI.
This used to work beautifully, until it didn’t! Like I mentioned above, we have no idea whatsoever about the environments in which our app is going to be deployed. Making the assumption that the above works everywhere was, as I found painfully, an arrogant claim!
Some environments are configured so that the document root is a symbolic
link. Maybe, to make things easier for users of a server, the server
administrator decided to define a symbolic link in each user’s home
directory named public_html
that links to a directory in their
name under /var/www
… For example, let’s say our user is
johndoe, and his home directory on the server is /home/johndoe
…
When johndoe lists his home directory contents, he sees
public_html
and so he installs our app into that directory. So,
is our app installed into /home/johndoe/public_html/blog
?
Well, not really. Since public_html
is actually a symbolic
link, the real path to our app is /var/www/johndoe/blog
.
The problem with DOCUMENT_ROOT
is that it doesn’t resolve real
paths .. so it would return /home/johndoe/public_html
… OTOH,
__FILE__
does resolve the real path, thus it would return
/var/www/johndoe/blog/lib
… Suddenly, our beautiful little
function above that used to work elegantly no longer works!
I tried fixing this by applying realpath()
to
DOCUMENT_ROOT
.. Unfortunately, some server configurations
would fail that as well.
So, after researching the above for hours, I came to the conclusion that
the ONLY accurate way of detecting the application URI is not to
try! Simply avoid DOCUMENT_ROOT
and __FILE__
altogether, and write the URI into a config file:
|
|
And the above line would be added to the config file by the setup script
that runs the first time the app is installed to the server, and can be
obtained from dirname($_SERVER['REQUEST_URI'])
. It seems this
is indeed the way popular open source PHP apps/frameworks retrieve the
application URI .. they don’t try to detect it from
DOCUMENT_ROOT
.. they just save it as a fixed predefined config
value. I didn’t find this explicitly mentioned anywhere I searched. I
guess this is because people usually write about how to do things rather
than how not to do them! So, I thought I’d write a post about this.
To sum up, don’t try to detect the application URI from
DOCUMENT_ROOT
… you’re doing it wrong this way, even if it
seems to work! Save yourself the trouble, detect it once during setup
using REQUEST_URI
, save that to a config file, and read it
from there from now on!
Archived Comments
Kim
2019-03-05 at 8:17 pm
Thanks first of all for you great work. May be this can also assist Working with Include Paths Another issue when working with large applications is tracking the locations of files to include. Say you’ve organized all your common library code, including
animal_functions.php
, into a centrally located/home/joe/lib
folder. You have a Web site in/home/joe/htdocs
that contains the previously shown mouse.php file in the document root. How does mouse.php referenceanimal_functions.php
? It could use an absolute path:
1 2 3 4
include_once("/home/joe/lib/animal_functions.php"); //Alternatively, it could use a relative path: include_once("../lib/animal_functions.php");
Either approach would work, but the resulting code isn’t very portable. For example, say the Web site needs to be deployed on a different Web server, where the library code is stored in a
/usr/lib/joe
folder. Every line of code that includedanimal_functions.php
(or any other library files, for that matter) would need to be updated with the new path:
1
include_once("/usr/lib/joe/animal_functions.php");
To get around this problem, you can use PHP’s include_path directive. This is a string containing a list of paths to search for files to include. Each path is separated by a colon (on Linux servers) or a semicolon (on Windows servers).
The
PATH_SEPARATOR
constant contains the separator in use on the current system. The paths are searched in the order they appear in the string. You can display the current include_path value — which is usually pulled from thephp.ini
configuration file — by calling theget_include_path()
function:
1
echo get_include_path(); // Displays e.g. ".:/usr/local/lib/php"
In this example, the
include_path
directive contains two paths: the current directory (.
) and/usr/local/lib/php
.To set a new
include_path
value, use — you guessed it —set_include_path()
:
1
set_include_path(".:/home/joe/lib");
It’s a good idea to precede your include path with the current directory(
.
), as just shown. This means that PHP always searches the current directory for the file in question first, which is usually what you want.You’d usually set your include path just once, right at the start of your application. Once you’ve set the include path, you can include any file that’s in the path simply by specifying its name:
Genedy
2019-03-05 at 8:56 pm
Thanks for sharing this, Kim. I tend to use
dirname(__FILE__)
for includes to address include issues and relative paths. Your suggestion is insightful as well.