Skip to content

Latest commit

 

History

History
252 lines (200 loc) · 8.36 KB

File metadata and controls

252 lines (200 loc) · 8.36 KB

Library Status Latest Stable Version Latest Unstable Version License

This package allows continuing processing PHP after having HTTP response sent back to the client under PHP-FPM.

PHP functions added by this package are executed after HTTP response sent back to the client but before PHP shutdown ( before any registered shutdown function is called). For detailed discussions on PHP shutdown sequence, function fastcgi_finish_request() and related topics, please check the post "Background Processing in PHP".

Limitations and Side Effects

This package is for PHP-FPM only. Don't try to run it under CLI, PHP built-in web server, mod_php or FastCGI since it won't work.

After sending HTTP response back to client side, background functions added continue to run and the PHP-FPM process is still running. To avoid side effects on your web server, please use this package accordingly. You may consider using some worker instances or queue servers instead. When using this package, you may consider following suggestions to minimize side effects:

  • increase number of child processes in PHP-FPM.
  • increase maximum execution time for PHP-FPM.

When using locks, please note that subsequent requests might block even client side has received a response from a previous request, since a lock may still be active while running background tasks in the previous request.

Installation

composer require crowdstar/background-processing:~1.0.0

Sample Usage

<?php
use CrowdStar\BackgroundProcessing\BackgroundProcessing;

$sum  = 0;
$file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'background-processing.txt';

// First background task added.
BackgroundProcessing::add(
// Increase $sum by the sum of given numbers. Final value of $sum is this example is 7 (1+2+4).
    function (int ...$params) use (&$sum) {
        $sum += array_sum($params);
    },
    1,
    2,
    4
);
// Second background task added and will be executed after the first one.
BackgroundProcessing::add(
    function () use (&$sum, $file) {
        // Number 7 calculated from first task will be written to the file.
        file_put_contents($file, $sum);
    }
);

// Number 0 will be returned back to HTTP client.
echo
    "Current sum value is {$sum}. ",
    "Please check file {$file} in the web server; final sum value there should be 7.\n";

// Send HTTP response back to the client first, then run the two background tasks added.
BackgroundProcessing::run();

// Anything here also runs in background.
echo "This message won't shown up in HTTP response.";
?>

Error Handling

By default, if a background task throws an exception, execution stops immediately and subsequent tasks will not run. You can change this behavior using the execution type setting.

Stop on Error (Default)

This is the default behavior. When a closure throws an exception, it propagates immediately and remaining closures are skipped.

<?php
use CrowdStar\BackgroundProcessing\BackgroundProcessing;

BackgroundProcessing::add(function () {
    // This runs.
    sendEmail('user@example.com', 'Order confirmed');
});
BackgroundProcessing::add(function () {
    // This throws an exception.
    throw new \RuntimeException('Analytics service unavailable');
});
BackgroundProcessing::add(function () {
    // This will NOT run because the previous task failed.
    updateSearchIndex();
});

BackgroundProcessing::run(); // The RuntimeException is thrown here.
?>

Continue on Error

In this mode, all closures are executed regardless of failures. After all closures have finished, if any threw exceptions, a single BackgroundProcessingFailedException is thrown containing all collected exceptions.

<?php
use CrowdStar\BackgroundProcessing\BackgroundProcessing;
use CrowdStar\BackgroundProcessing\Exception\BackgroundProcessingFailedException;

BackgroundProcessing::add(function () {
    sendEmail('user@example.com', 'Order confirmed');
});
BackgroundProcessing::add(function () {
    // This fails, but the next task still runs.
    throw new \RuntimeException('Analytics service unavailable');
});
BackgroundProcessing::add(function () {
    // This runs even though the previous task failed.
    updateSearchIndex();
});

BackgroundProcessing::setExecutionType(BackgroundProcessing::EXECUTION_TYPE_CONTINUE_ON_ERROR);
try {
    BackgroundProcessing::run();
} catch (BackgroundProcessingFailedException $e) {
    // $e->getMessage() returns e.g. "1 background processing task(s) failed"
    foreach ($e->getExceptions() as $exception) {
        error_log($exception->getMessage());
    }
}
?>

The BackgroundProcessingFailedException provides:

  • getMessage() — a summary like "2 background processing task(s) failed"
  • getExceptions() — an array of all \Throwable instances caught during execution
  • getPrevious() — the first exception in the list, for standard PHP exception chain traversal

Integration Guides

Integrate with Symfony

<?php
// Sample code borrowed from https://github.com/symfony/demo/blob/v1.2.4/public/index.php
use App\Kernel;
use Symfony\Component\HttpFoundation\Request;

require __DIR__.'/../vendor/autoload.php';

$kernel = new Kernel($env, $debug);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

// Method BackgroundProcessing::add() could be called from a bundle, a controller or
// anywhere before method BackgroundProcessing::run() is called.
CrowdStar\BackgroundProcessing\BackgroundProcessing::add(
    function() {
        mail('user@example.com', 'test', 'test');
    }
);
CrowdStar\BackgroundProcessing\BackgroundProcessing::run();
?>

Integrate with Laravel

<?php
// Sample code borrowed from https://github.com/laravel/laravel/blob/5.5/public/index.php
define('LARAVEL_START', microtime(true));

require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

// Method BackgroundProcessing::add() could be called from a controller, a middleware or
// anywhere before method BackgroundProcessing::run() is called.
CrowdStar\BackgroundProcessing\BackgroundProcessing::add(
    function() {
        mail('user@example.com', 'test', 'test');
    }
);
CrowdStar\BackgroundProcessing\BackgroundProcessing::run();
?>

Integrate with Lumen

<?php
// Sample code borrowed from https://github.com/laravel/lumen/blob/5.5/public/index.php
$app = require __DIR__ . '/../bootstrap/app.php';
$app->run();

// Method BackgroundProcessing::add() could be called from a controller, a middleware or
// anywhere before method BackgroundProcessing::run() is called.
CrowdStar\BackgroundProcessing\BackgroundProcessing::add(
    function() {
        mail('user@example.com', 'test', 'test');
    }
);
CrowdStar\BackgroundProcessing\BackgroundProcessing::run();
?>

Integrate with Slim 3

<?php
// Sample code borrowed from https://github.com/slimphp/Slim/blob/3.x/example/index.php
require 'vendor/autoload.php';

$app = new Slim\App();

$app->get('/', function ($request, $response, $args) {
    $response->write("Welcome to Slim!");
    return $response;
});

$app->get('/hello[/{name}]', function ($request, $response, $args) {
    $response->write("Hello, " . $args['name']);
    return $response;
})->setArgument('name', 'World!');

$app->run();

// Method BackgroundProcessing::add() can be called from a route, a middleware or
// anywhere before method BackgroundProcessing::run() is called.
CrowdStar\BackgroundProcessing\BackgroundProcessing::add(
    function() {
        mail('user@example.com', 'test', 'test');
    }
);
CrowdStar\BackgroundProcessing\BackgroundProcessing::run();
?>