From time to time I encounter the situation in which I need to put a web/platform on hold for a period of time for maintenance reasons, because we need to block access momentarily or just because the client wants to (really, bussiness thingys).

I've seen several different ways to deal with this situation, many of them making use of mod_rewrite in .htaccess file, or implementing a platform wide mechanism with a freezing capability based on a flag on a DDBB. None of this options is of my liking.

At the end when it comes to setting maintenance mode On it is just a case of a condition being triggered, being this conditions things very different from one another, there are simple conditions such as "we start manual maintenance tasks" or more complex like if we perform maintenance tasks over the DDBB every night between 2AM and 3AM and so we want to put the page in maintenance mode then the condition is "2AM < NOW() < 3AM".

Janitor has been craftet to ease this maintenance mode management in the simplest way possible.

Janitor is composed of three parts, Whatchers that map to conditions to activate maintenance mode, Excluders that map to conditions to bypass maintenance mode even if it's active, the Janitor runner which actually checks the conditions and finally the Strategies which will get triggered appropriately.

I'm going to imagine a scenario and then see how it is mapped and handled with Janitor. Lets assume I've a project in which we perform maintenance tasks every 15th of the month between 1AM and 1:30AM. Additionally we have some maintenance tasks that can be run uppon request at any moment (lets try not to do it at 9AM please). In any case we want our team to be able to still access the page when tasks are running.

We have three conditions in the above paragraph. Two conditions to activate maintenance and one to be excluded from it.

First condition is mapped to a periodical watcher \Janitor\Watcher\Scheduled\Cron that will be active in a time interval starting from a point in time defined by a cron expression

use Janitor\Whatcher\Scheduled\Cron;

// Active every day 15 of evey month at 1AM for 30 minutes
new Cron('0 1 15 * *', new \DateInterval('PT30M'));

The second conditions needs us to decide how to inform the page the maintenance tasks has started and for this we have two options, use a file in the system or an environment variable. In both cases the file or environment variable needs to be created/set on task start and removed/unset when task finishes.

use Janitor\Whatcher\File;
use Janitor\Whatcher\Environment;

new File('/tmp/maintenance_task_running'); // I'd go for this one
new Environment('maintenance', 'On');

The third condition is the opposite, it allows the team to access when maintenance mode is active, this is mapped with an Exclusion. In order to create an example lets assume the team works behind a single IP:130.130.130.130, maybe the office IP.

use Janitor\Excluder\IP;

new IP('130.130.130.130');

If we join all this together and provide Watchers and Excluders to Janitor runner from this point on Janitor can determine maintenance mode status:

use Janitor\Janitor;
use Janitor\Watcher\Scheduled\Cron;
use Janitor\Watcher\File;
use Janitor\Excluder\IP;

$watchers = [
    new Cron('0 1 15 * *', new \DateInterval('PT30M')),
    new File('/tmp/maintenance_task_running'),
];
$excluders = [
    new IP('130.130.130.130'),
];

$janitor = new Janitor($watchers, $excluders);

// Determine if maintenance mode is active and the request is not excluded from it
if ($janitor->inMaintenance() && !$janitor->isExcluded()) {
    // Get active watcher
    $activeWatcher = $janitor->getActiveWatcher();

    // Handle maintenance mode
}

There is one element still missing, Strategies allows you to leverage Janitor to handle maintenance mode for you, so in the previous code the last if statement can be replaced by

if ($janitor->handle()) {
    //finish execution
    die();
}

There are two available Strategies, Render which renders a simple maintenance page and Redirect which well, redirects the user to another part, if no strategy is provided Renderis used. You can define what strategy you want to use on Janitor runner creation or by setting it explicitely

$janitor->setStrategy(new \Janitor\Strategy\Render);
$janitor->setStrategy(new \Janitor\Strategy\Redirect('http://example.com'));

You can even combine Redirect strategy with an excluder for a path and so during maintenance users will be redirected to a beautifully crafted maintenance page:

use Janitor\Janitor;
use Janitor\Watcher\Manual;
use Janitor\Excluder\Path;
use Janitor\Strategy\Redirect;

$janitor = new Janitor([new Manual(true)], [new Path('/maintenance')], new Redirect('/maintenance'));

If you've reached this point you most certainly see the flexibiliy this tool provides for handling maintenance status. If you take a look at the documentation or the repository you'll find examples of integration into other tools and frameworks.