A PHP bootstrapping crash course

3 VT Vito Tardia Sep 19, 2014

As your PHP applications become larger and more complex, managing dependencies via a list of includes becomes a chore, and it's also seen as bad practice today. In addition, many of the scripts only include business logic (i.e. they don't render HTML, and shouldn't be accessed directly). Once you've finished this crash course in bootstrapping, you'll have a basic -- yet powerful -- modern application template with simplified script dependency built in.

You can download the full source code for this tutorial here.

What is bootstrapping?

Bootstrapping refers to the process of loading the environment a program (or a script, in the case of PHP) needs to operate. In the context of PHP development, it also means funneling all web requests through a single script that performs the bootstrapping process, also called "front controller."

In many PHP systems you might have encountered, such as WordPress or PHPMyAdmin, requests are handled ad-hoc, sometimes allowing direct access to scripts, sometimes funneling requests to an index.php file. Aside from security concerns (for example, accessing wp-config.php while the server PHP process is down, showing it as plain text), it also makes it hard to follow the include chain to understand what is loaded for each script. Not to mention how much of a pain it becomes to properly set up the environment for new scripts as they're added to the system. This is especially true if you're not the original developer of the app you're working with.

Bootstrapping alleviates those problems in the following fashion:

  1. All requests are processed by a front controller, usually index.php, which bootstraps the application, including all the relevant dependencies (functions, classes, configuration) and executing the appropriate script to return the response to the originating client.
  2. All PHP files aside from the front controller are placed outside of the publicly accessible folder (the document root on Apache), so they can't be accessed regardless of the status of the server.
  3. This setup allows the use of routing in PHP -- parsing the request URL to call the correct script(s) and returning the response to the client. This is how most PHP frameworks implement their MVC structure.

So, how do we successfully bootstrap our applications using real world best practices?

Bootstrapping in the real world

The first step is to start with a meaningful directory structure for your application, here's an example.

/path/to/myapp/
  app/
    bootstrap.php
    lib/
    public/
      .htaccess
      index.php
  share/
  vendor/

All the code relative to the application itself is self-contained in the app directory. Inside this, the app/public directory is the only directory that must be published to the web, so we can write the following in our virtual host file.

DocumentRoot /path/to/myapp/app/public
<Directory "/path/to/myapp/app/public">
  # other setting here
</Directory>

The .htaccess file then redirects all non-existing URLs to the front controller index.php:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

The app/lib directory contains application-specific code, while the vendor directory contains third-party code such as the database component, the logging component and so on.

The share directory contains data such as log files, configuration files and other stuff that is not strictly part of the application and must be shared between various releases and updates.

Finally, the bootstrap.php file is where the environment of our program gets loaded. We could do it inside index.php but since index is also our router and it's publicly visible, it's a good idea to separate the two functions.

Loading components

The era of infinite inclusions has come to an end. PHP introduced autoloading functions with version 5. Version 5.3 also brought namespaces, a great feature that allows us to organize and package our code in a way that doesn't conflict with that of others.

The PHP community then agreed to common coding standards for library inclusion, the initial PSR-0 and the latest PSR-4. So the ideal situation is to use one single include statement in our bootstrap.php file, the autoloader implementation.

require_once dirname(__FILE__) . '/path/to/autoload.php';

Implementing an autoloader sounds complicated, luckily there's some great code provided by the PHP community. We could use this PSR-4 loader and/or this other PSR-0 and tweak them to our needs, but even better we can use Composer, which implements both solutions the easy way.

Composer is the de facto standard for dependency management in PHP and comes with a powerful autoloader out of the box. I'm not giving you more details on this since it's a bit out of scope, but you can install Composer and then come back and take a look at the composer.json manifest file for our application:

{
    "name": "author/appname",
    "description": "The description of my awesome PHP application",
    "require": {
        "php": ">=5.3.0",
        "ext-pdo": "*",
        "slim/slim": "2.4.*",
        "slim/extras": "*"
    },
    "license": "MIT",
    "authors": [
        {
            "name": "John Doe",
            "email": "john.doe@somedomain.com"
        }
    ],
    "autoload": {
        "psr-4": {
            "SampleApp\\": "app/lib/"
        }
    },
    "archive": {
        "exclude": ["vendor", ".DS_Store", "*.log"]
    }
}

First we have a name for our app - in the format vendorname/productname - and a description. Then we define dependencies and system requirements with the require section. As you can see, we can check that the destination system runs a PHP version suitable for our program with the required extensions loaded (prefixed with ext-).

Our sample app needs the packages slim/slim and slim/extras, which stand for Slim framework and some of its utils, I'll come through this in a moment.

The autoload key says that the root of the SampleApp namespace is mapped to the app/lib directory.

You can find more details by reading Composer's schema reference.

Once your composer.json file is ready you can run composer install in order to start a system check and download the dependencies from the Packagist public archive. You can then periodically run composer update in order to download the most recent version of the packages.

All we need now is that single include statement in our bootstrap.php file:

require_once dirname(__FILE__) . '/../vendor/autoload.php';

and start coding.

Loading and Routing

For a modern application, routing and URL parsing should be considered mandatory. Implementing a routing system is a good learning challenge, but sometimes using a third party solution is a better choice.

There are several packages out there that provide routing capabilities as well as full-featured frameworks like CakePHP or Laravel. My choice for this tutorial is Slim. Slim is a micro framework that offers a powerful routing system and a series of handy utility features such as logging, error handling and HTTP caching. This basic layer of features can be plugged with any other component you need, for example a a better logger, an ORM for database interaction or a templating system.

In our application, routing is managed by the index.php front controller which, in order to run properly, needs to load its configuration from the bootstrap file:

require_once dirname(__FILE__) . '/../bootstrap.php';

Application settings

The bootstrap.php file is where all the application environment is set.

<?php
// Including global autoloader
require_once dirname(__FILE__) . '/../vendor/autoload.php';

// Init config data
$config = array();

// Basic config for Slim Application
$config['app'] = array(
    'name' => 'My Awesome Webapp',
    'log.enabled' => true,
    'log.level' => Slim\Log::INFO,
    'log.writer' => new Slim\Extras\Log\DateTimeFileWriter(array(
        'path' => dirname(__FILE__) . '/../share/logs'
    )),
    'mode' => (!empty($_ENV['SLIM_MODE'])) ? $_ENV['SLIM_MODE']: 'production'
);

// Load config file
$configFile = dirname(__FILE__) . '/../share/config/default.php';

if (is_readable($configFile)) {
    require_once $configFile;
}

// Create application instance with config
$app = new Slim\Slim($config['app']);

// Get logger
$log = $app->getLog();

// Only invoked if mode is "production"
$app->configureMode('production', function () use ($app) {
    $app->config(array(
        'log.enable' => true,
        'log.level' => Slim\Log::WARN,
        'debug' => false
    ));
});

// Only invoked if mode is "development"
$app->configureMode('development', function () use ($app) {
    $app->config(array(
        'log.enable' => true,
        'log.level' => Slim\Log::DEBUG,
        'debug' => true
    ));
});

// Other config here (i.e. database, mail system, etc)...

What you place in this file depends on your application requirements, but you must always include the global autoloader first.

In our sample application I'm defining an array of settings that are given to the Slim application constructor: application name, log settings, a basic log writer and the application mode. The application mode is by default set to production, and can be overridden with the $_ENV['SLIM_MODE'] super global variable if needed.

There are now at least two ways to load additional settings into our application. The most common is loading an external configuration file, in our case share/config/default.php, just after the basic config block:

$configFile = dirname(__FILE__) . '/../share/config/default.php';
if (is_readable($configFile)) {
    require_once $configFile;
}

This file is not part of the standard application code base, instead it's located in the shared directory along with log files and other common data for the application.

With the advent of cloud computing, another good practice becoming popular is to store settings inside the environment variables. This becomes useful if your application runs on more than one server and it's deployed using automated scripts and version control. It also has the advantage of keeping sensitive credentials separated from the application code. An example of this is the Heroku platform.

If you want to try this approach you can set these entries in your virtual host file:

SetEnv SLIM_MODE development
SetEnv DB_USER foo
SetEnv DB_PASS bar
...

and use $_ENV['VAR_NAME'] in the bootstrap file.

With the right application mode in place, Slim allows us to tweak the settings with the $app->configureMode() method. In this case we need different log and debug settings.

Running the app

And here we come to our router. In the index.php we define all the routes that we need to manage an how they are managed. Slim routing is simple:

$app->get('/about', function () use ($app, $log) {
    echo "<h1>About ", $app->config('name'), "</h1>";
    var_dump($_SERVER);
    echo "<p><small>Current mode is: ", $app->config('mode'), '</small></p>';
});

In this example I'm telling my application to map the /about URI, called with the HTTP method GET, to an inline function that has access to the $app and $log global variables. I can use $app->post() or $app->put() to map other HTTP methods or $app->map(...)->via('GET', 'POST') to map the same function to more than one method.

Routes are parsed, and should be written, from the more specific (i.e /en/my/route) to the more generic (/) and can also accept parameters:

$app->get('/hello(/:name)', function ($name = 'anonymous') use ($app, $log) {
    $greeter = new SampleApp\Helpers\Hello($name);
    echo $greeter->greet();
    $log->info("Just logging $name visit...");
});

In this example the /hello route recognizes the :name parameter, that Slim passes as the $name variable to our inline function. I've put the parameter between parenthesis so it's considered optional. Inside the function I'm using the SampleApp\Helpers\Hello dummy class that resides in app/lib/Helpers and it's loaded automatically by Composer.

Summary

So, we now have a modern basic application template, which bootstraps itself by loading only the necessary files and can count on a robust routing system. With this in place all we need is to do is to code our killer features. So go on and happy coding!

Download the full source code for this tutorial here.

3 comments


Or enter your name and Email
  • 8 888888888888 9 months ago
    66666666666666666
  • F fefe 2 years ago
    fsdfsw
    • F fefe 2 years ago
      protect this form from spammers