skorlok / asynchronous-jobs
Execute new php instances easily as you would threads.
Requires
- php: >=5.4.0
Requires (Dev)
None
Suggests
- ext-curl: Required by the Curl jobs
Provides
None
Conflicts
Replaces
None
This package is auto-updated.
Last update: 2026-10-29 22:07:56 UTC
README
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_openenabled. Without it, jobs still work but run synchronously in the main process.ext-curlfor 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.$sleepTimeis 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 forGET, 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 itnullunless 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.