Monday, April 18, 2011

Find Your Application Root in PHP - Part 1

This is Part 1 of 2. Part 2 describes the preferred technique and this part describes less sophisticated solutions.

This two part article catalogs the different ways a PHP application can find its application root directory in the filesystem. This is important because the application root directory is usually used as the reference point to find all other directories, such as include files and add-ons. The goal is to put something that looks like this at the front of each of our PHP files:

define('APP_ROOT', ???);

The question is what replaces the question marks.

There are two general methods of finding the application root directory. You can either calculate it or you can define it. If you look in the PHP manual page for include(), you will see numerous ways people have tried to solve this problem, all of which are based on these two methods.

The requirements for my solution were:
  • Cross-environment (LAMP, WAMP, Windows/IIS, and PHP-CLI)
  • Maintainable
  • Scalable for growing applications
  • Deployable with source control
  • Works with a subdirectory
  • Fast
This article is primarily targeted at PHP 5, up to and including PHP 5.3. This article emphasizes breadth over depth – there are endless nuances to variations of PHP version/web server/operating system/server permissions that this article does not try to address.

Calculating the application root is an alluring solution because it promises zero maintenance. You can change anything, move anything, update anything – and it will still work. However, there’s no way to reliably calculate your application root without making make some compromises.

There are four common methods for calculating the application root in PHP:
  • Relative paths
  • The file’s directory
  • The DOCUMENT_ROOT directory
  • A reference file

Relative Paths

The most popular way to calculate the application root directory is using relative paths. With this strategy, each PHP file has a definition at the top of the relative path to the application root. For example:

define('APP_ROOT', './');

This works quite well for a simple application that’s completely contained in a single directory. It’s simple, there’s no maintenance, and it’s cross-platform. For example:

include APP_ROOT . 'coolclass.php';

Notice that I used './' as the APP_ROOT instead of ''. If I used '', then PHP would have searched the include_path before searching the current directory. That syntax is used for including system files, like this:

include 'pear/pearclass.php';

When your application grows to multiple directories, this Relative Paths technique starts showing its flaws. Consider what happens if your parent source file was admin/index.php. Now you need to manually define a different APP_ROOT:

define('APP_ROOT', '../');

The first problem with this solution is that it is relative to the current working directory, which makes it somewhat fragile.

The second problem is that, if you are defining the APP_ROOT relative to the current directory, you constantly have to be aware of what directory you are in and make sure to use the proper path. If a file moves to a new directory, then all of the include paths need to change. While this is certainly doable, it’s maintenance overhead and an accident waiting to happen in a production system.

You’ll see this relative path strategy implemented in different ways. On Island Linux, you’ll see a solution similar to what I’ve shown above. On one StackOverflow question, most of the solutions are based on relative paths, although you have to read the Clarification to determine that. If you look at the PHP manual page for include(), you’ll see a comment by tim at atwoodglass dot com showing a boilerplate file header where you have to define “header_to_root_distance”.

The File’s Directory

A variation on the Relative Paths strategy is a path relative to the current file instead of the current directory. For example:

define('APP_ROOT', realpath(dirname(__FILE__) . '/../'));

This strategy is also helpful for library files that are intended for reuse. You can’t use the current directory because you have no idea who included you and you can’t use APP_ROOT because you don’t know anything about the application. The solution is to include the file relative to the directory of the current file. For example:

include dirname(__FILE__) . 'helperclass.php';

In PHP 5.3 and later, this also works:

include __DIR__ . 'helperclass.php';

Some examples for this problem use $_SERVER['PHP_SELF'], but this refers to the filename of the currently executing script, relative to the document root. This solves a different problem.

The DOCUMENT_ROOT Directory

The DOCUMENT_ROOT directory is wildly popular for developers who have learned not to use relative paths and are looking for the next best thing. For example:

define('APP_ROOT', realpath($_SERVER['DOCUMENT_ROOT']));

There are several people on the PHP manual page for include() who recommend variations on this theme.

There are three key problems with this approach:
  • Some web servers don’t support DOCUMENT_ROOT (such as some versions of IIS under Windows.)
  • It fails completely if you are using PHP-CLI for unit testing. Since there is no web server, DOCUMENT_ROOT is undefined.
  • It only works if your application is in the root directory of the web server. If your application moves to a subdirectory, it breaks.
Therefore, I would say that you could use the DOCUMENT_ROOT as a default or fallback, but it’s not a good choice as a reliable solution.

A Reference File

Searching for the application root is often where developers end up after discovering that calculated solutions have significant limitations. This strategy is most commonly implemented by placing a file with a name like “.approot” in the application root directory.

Advantages to searching for the application root are that it will allow your application to work in a subdirectory and will usually allow your application to work cross platform.

There are two downsides. First, the code to search for that file must be copied to the top of every PHP file that needs to refer to its root. This can add up to quite a lot of code duplication.

Second, this strategy makes it difficult to use the file in a custom environment. For example a unit test framework may want to use a custom APP_ROOT for testing purposes.

Part II

In Part 2 of this article, we'll look at how to define the application root from the environment.

4 comments:

  1. ...and sad that there's no Part II.

    ReplyDelete
  2. A very valuable 5-year-old post. One question re the .approot solution. Couldn't the problem of the need for the code to be copied to the top of every PHP file be solved, at least in part, by including it in a header fill called by all pages?

    ReplyDelete
    Replies
    1. Part 2 exists. I fixed the link.

      Delete
    2. Thanks a lot. And to also fix my previous comment, that should have been "a header FILE called by all pages".

      To be more effusive in praise: Not all technical experts are as clear in discussing and explaining tech stuff. Your quality (from experience?) shines through.

      Delete