Search by

skorlok / asynchronous-jobs

Skorlok

Execute new php instances easily as you would threads.

Package info

github.com/Skorlok/PHP-AsynchronousJobs

pkg:composer/skorlok/asynchronous-jobs

Statistics

Installs: 50

Dependents: 1

Suggesters: 0

Stars: 0

1.0.0 2026-10-29 22:04 UTC

This package is auto-updated.

Last update: 2026-10-29 22:07:56 UTC


README

Latest Stable Version Total Downloads License

Asynchronous Jobs is a small library to run PHP code in parallel of your main code. Each job runs in a new PHP process: it works a bit like threads, without needing any thread extension. It is a workaround, not a replacement for threads.

It is meant for long tasks in command line tools and daemons, for example downloading files while the main script keeps serving users.

This package is a fork of oliverde8/PHP-AsynchronousJobs by Oliver de Cramer, maintained for eXpansion. The namespace is unchanged (oliverde8\AsynchronousJobs), so it can't be installed together with oliverde8/asynchronous-jobs: to switch, just replace the package name in your composer.json.

Why

Real threads in PHP need a thread-safe (ZTS) build of PHP and the parallel extension (pthreads is no longer maintained), which most servers don't have. This library works with any PHP >= 5.4, on Linux and Windows.

Installation

composer require skorlok/asynchronous-jobs

Requirements:

  • PHP >= 5.4
  • proc_open enabled. Without it, jobs still work but run synchronously in the main process.
  • ext-curl for the Curl jobs.

How it works

When a job starts, its properties are serialized into a temporary directory and a new PHP process, using the same PHP binary as the main process, runs the job. When the job is done, its properties are sent back to the main process, which then calls the job's end() method.

The main process only notices that a job is done when it checks for it, with wait(), waitForAll() or proccess().

The new process only loads Composer's vendor/autoload.php, so your job classes must be autoloadable: a job class declared in the script that starts it can't be found by the new process.

If a job crashes, end() is still called, with the properties as they were when the job started. The error is written in run.log, in the temporary directory, which is deleted when the main process ends.

Usage

Create a job, in a class autoloaded by Composer:

namespace App\Jobs;

use oliverde8\AsynchronousJobs\Job;
use oliverde8\AsynchronousJobs\JobData;

class Sleep extends Job
{
    public $time = 1;

    // Called in the new process.
    public function run()
    {
        sleep($this->time);
    }

    // Called in the main process once the job is done.
    public function end(JobData $jobData)
    {
        echo "I end after: {$this->time}!\n";
    }
}

Start some jobs and wait for them:

use App\Jobs\Sleep;
use oliverde8\AsynchronousJobs\JobRunner;

$job1 = new Sleep();
$job1->time = 3;
$job1->start();

$job2 = new Sleep();
$job2->time = 2;
$job2->start();

JobRunner::getInstance()->waitForAll(1);

You should see "I end after: 2!" first, then "I end after: 3!".

Waiting for jobs

  • JobRunner::getInstance()->waitForAll($sleepTime) blocks until all jobs are done. $sleepTime is the delay between two checks, in seconds; use a float (0.1) for less than a second.
  • $job->wait() blocks until this job is done.

In a daemon

To never block, call proccess() in your main loop, every second or less. It detects the finished jobs, calls their end() method and retries the jobs that couldn't be started:

while (true) {
    // ... your work

    JobRunner::getInstance()->proccess();
    usleep(100000);
}

Curl requests

use oliverde8\AsynchronousJobs\Job\Curl;
use oliverde8\AsynchronousJobs\JobRunner;

$curlJob = new Curl();
$curlJob->setMethod('GET');
$curlJob->setUrl('https://jsonplaceholder.typicode.com/posts');
$curlJob->start();

JobRunner::getInstance()->waitForAll(1);

$response = $curlJob->getResponse();
$info = $curlJob->getCurlInfo();
$error = $curlJob->getCurlError();
  • setParameters(array) sets the query string for GET, or the request body for other methods.
  • setOptions(array) sets any cURL option (CURLOPT_* => value).

Callbacks

CallbackCurl and CallbackComposerHandler call a function when the job is done, with the job and its JobData:

use oliverde8\AsynchronousJobs\Job\CallbackCurl;

$curlJob = new CallbackCurl();
$curlJob->setUrl('https://jsonplaceholder.typicode.com/posts');
$curlJob->setCallback(function ($job, $jobData) {
    echo $job->getResponse();
});
$curlJob->start();

You can also use the Callback trait in your own jobs.

Running a command

ComposerHandler runs an external command in a given directory and returns its output. eXpansion uses it to run Composer, but any command works:

use oliverde8\AsynchronousJobs\Job\ComposerHandler;

$job = new ComposerHandler();
$job->setDirectory('/path/to/project');
$job->setCommand(array(PHP_BINARY, 'composer.phar', 'update', '--no-interaction'));
$job->start();
$job->wait();

$result = $job->getResult(); // array('output' => array(...), 'error' => array(...), 'returnVar' => 0)

Settings

To change the settings, JobRunner::getInstance() must be your very first call, before starting any job:

JobRunner::getInstance(null, 'php', '/var/tmp/my-app/');
  • $id (null): lets several processes share the same runner. Leave it null unless you know you need it.
  • $phpExecutable ('php'): ignored, kept for compatibility. Jobs always run with the same PHP binary as the main process (PHP_BINARY).
  • $tmpPath ('tmp/'): directory of the temporary files used to exchange data with the jobs. It must be writable.

Properties not transferred

All the properties of a job, public or private, are sent to the new process when the job starts, then sent back when it ends. Properties whose name starts with a double underscore (__) are not transferred and stay in the process that set them:

private $__localOnly = 'never sent';

License

MIT, see LICENSE. Original work by Oliver de Cramer.