juliangut/janitor

Effortless maintenance management for PSR7

0.6 2016-05-22 10:02 UTC

README

Latest Version License

Build status Style Code Quality Code Coverage Total Downloads

Janitor

Effortless maintenance management for PSR7.

Janitor is a ready to use PSR7 package that provides you with an easy configurable and extensible way to handle maintenance mode on your project, because maintenance handling goes beyond responding to the user with an HTTP 503 code and a simple message.

Set several conditions that will be checked to determine if the maintenance handler should be triggered. This conditions are of two kinds, 'activation' conditions (named watchers) and conditions to bypass the normal execution (named excluders).

Already builtin watchers and excluders allows you to cover a wide range of situations so you can drop Janitor in and start in no time, but at the same time it's very easy to create your own conditions if needed by implementing the corresponding interface.

Once Janitor has determined maintenance mode is active it let you use your handler to get a response ready for the user or you can let Janitor handle it all by itself (a nicely formatted 503 response).

Learn more in Janitor's page

Installation

Best way to install is using Composer:

composer require juliangut/janitor

Then require the autoload file:

require_once './vendor/autoload.php';

Usage

use Janitor\Excluder\IP as IPExcluder;
use Janitor\Excluder\Path as PathExcluder;
use Janitor\Runner as Janitor;
use Janitor\Watcher\WatcherInterface;
use Janitor\Watcher\File as FileWatcher;
use Janitor\Watcher\Cron as CronWatcher;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequestFactory;

$watchers = [
    new FileWatcher('/tmp/maintenance'),
    new CronWatcher('0 0 * * 0', new \DateInterval('PT2H')),
];
$excluders = [
    new IPExcluder('127.0.0.1'),
    new PathExcluder(['/maintenance', '/^\/admin/']),
];

$handler = function (ServerRequestInterface $request, ResponseInterface $response, WatcherInterface $watcher) {
    $response->getBody()->write('This page is in maintenance mode!');

    return $response;
}

$activeWatcherAttributeName = 'maintenance_watcher'; // Default is 'active_watcher'
$janitor = new Janitor($watchers, $excluders, $handler, $activeWatcherAttributeName);

$response = $janitor(
    ServerRequestFactory::fromGlobals(),
    new Response('php://temp'),
    function ($request, $response) use ($activeHandlerAttributeName) {
        $activeHandler = $request->getAttribute($activeHandlerAttributeName);
        // ...
    }
);

In case a watcher is active at any given point (and so maintenance mode does) it will be attached as an attribute to the request object so it can be retrieved during execution.

Watchers

Watchers serve different means to activate maintenance mode by verifying conditions.

  • Manual Just set it to be active. Useful to be used with a configuration parameter.
  • File Checks the existence of the provided file(s).
  • Environment Checks if an environment variable is set to a value.
$manualWatcher = new \Janitor\Watcher\Manual(true);
// Always active
$manualWatcher->isActive();

$fileWatcher = new \Janitor\Watcher\File('/tmp/maintenance');
// Active if /tmp/maintenance file exists
$fileWatcher->isActive();

$envWatcher = new \Janitor\Watcher\Environment('maintenance', 'ON');
// Active if 'maintenance' environment variable value is 'ON'
$envWatcher->isActive();

Scheduled watchers

Scheduled watchers are a special type of watchers that identify a point in time in the future for a maintenance period.

  • Fixed Hard set start and/or end times for a scheduled maintenance period.
  • Cron Set maintenance periods using cron expression syntax.
$fixedWatcher = new \Janitor\Watcher\Fixed('2026/01/01 00:00:00', '2026/01/01 01:00:00');
// Active only 1st January 2026 at midnight for exactly 1 hour
$fixedWatcher->isActive();

$cronWatcher = new \Janitor\Watcher\Cron('0 0 1 * *', new \DateInterval('PT2H'));
// Active the first day of each month at midnight during 2 hours
$cronWatcher->isActive();

From a scheduled watcher you can get a list of upcoming maintenance periods

$cronWatcher = new \Janitor\Watcher\Cron('0 0 1 * *', new \DateInterval('PT2H'));
// Array of ['start' => \DateTime, 'end' => \DateTime] of next maintenance periods
$scheduledPeriods = $cronWatcher->getScheduledTimes(10);

Watchers are checked in the order they are added, once a watcher is active the rest won't be checked.

If you perform maintenance tasks periodically (maybe on the same day of every week) you may want to use either Cron watcher to identify the date and the time period needed, or File watcher to watch for a file in your system and set your maintenance process to touch and rm that file as part of the maintenance process.

Cron watcher uses Michael Dowling's cron-expression.

Excluders

Excluders set conditions to bypass maintenance mode in order to allow certain persons through or certain pages to be accessed.

  • IP Verifies user's IP to allow access.
  • Path Excludes certain URL paths from maintenance.
  • BasicAuth Excludes based on request Authorization header.
  • Header Excludes based on request Header value.
$ipExcluder = new \Janitor\Excluder\IP('127.0.0.1');
// Users accessing from IP 127.0.0.1 are excluded
$ipExcluder->isExcluded($request);

$pathExcluder = new \Janitor\Excluder\Path('/maintenance');
// Users accessing 'http://yourdomain.com/maintenance' are excluded
$pathExcluder->isExcluded($request);

$pathExcluderRegex = new \Janitor\Excluder\Path('/^\/admin/');
// Can also be a regex
$pathExcluderRegex->isExcluded($request);

$basicAuthExcluder = new \Janitor\Excluder\BasicAuth(['root' => 'secret']);
// Users accessing with basic authorization 'root' user are excluded
$basicAuthExcluder->isExcluded($request);

$headerExcluder = new \Janitor\Excluder\Header('X-Custom-Header', 'custom');
// Users accessing with 'X-Custom-Header' header's value 'custom' are excluded
$headerExcluder->isExcluded($request);

$headerExcluderRegex = new \Janitor\Excluder\Header('X-Custom-Header', '/^custom/');
// Again a regex can be used
$headerExcluderRegex->isExcluded($request);

When adding excluders consider they are checked in the same order they are included so that when an excluder condition is met the rest of the excluders won't be tested. Add more general excluders first and then more focused ones.

Tipically you'll want to exclude your team's IPs and certain pages such as maintenance or administration zone.

Handlers

In order to handle maintenance mode any callable can be provided to setHandler method given it follows this signature:

function (\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, \Janitor\Watcher\WatcherInterface $watcher);

Two really basic handlers are suplied by default to cope with maintenance mode.

  • Render Sets response with 503 code and add basic formatted maintenance output based on request's Accept header.
  • Redirect Prepares response to be a 302 redirection to a configured URL (typically maintenance page).

Of the two Render will be automatically created and used in case none is provided.

Scheduled maintenance service

If scheduled watchers are being used they open the option to show a list of future maintenance periods, for example on a page dedicated to inform users about future maintenance actions.

use Janitor\Runner as Janitor;
use Janitor\Watcher\Cron;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequestFactory;

$watchers = [new Cron('0 0 1 * *', new \DateInterval('PT2H'));];

$janitor = new Janitor($watchers);

$response = $janitor(
    ServerRequestFactory::fromGlobals(),
    new Response('php://temp'),
    function ($request, $response) use ($janitor) {
        // Array of ['start' => \DateTime, 'end' => \DateTime]
        $scheduledPeriods = $janitor->getScheduledTimes();
    }
);

Examples

Slim3

use Janitor\Runner as Janitor;
use Slim\App;

$watchers = [];
$excluders = [];

$app = new App();

// Add middleware (using default Render handler)
$app->add(new Janitor($watchers, $excluders));

$app->run();

Zend Expressive

use Janitor\Handler\Redirect;
use Janitor\Runner as Janitor;
use Zend\Expresive\AppFactory;

$watchers = [];
$excluders = [];
$handler = new Redirect('/maintenance');

$app = AppFactory::create();

// Add middleware
$app->pipe(new Janitor($watchers, $excluders, $handler));

$app->run();

Symfony's HttpFoundation

If using Symfony's HttpFoundation you can still add Janitor to your tool belt by using Symfony's PSR HTTP message bridge

An example using Silex

use Janitor\Runner as Janitor;
use Silex\Application;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Component\HttpFoundation\Request;
use Zend\Diactoros\Response;

$janitor = new Janitor();

$app = new Application;

$app->before(function (Request $request, Application $app) use ($janitor) {
    $response = $janitor(
        (new DiactorosFactory)->createRequest($request),
        new Response('php://temp'),
        function ($request, $response) {
            return $response;
        }
    );

    return (new HttpFoundationFactory)->createResponse($response);
});

$app->run();

Contributing

Found a bug or have a feature request? Please open a new issue. Have a look at existing issues before.

See file CONTRIBUTING.md

License

See file LICENSE included with the source code for a copy of the license terms.