Download the PHP package laikait/laika-queue without Composer

On this page you can find all versions of the php package laikait/laika-queue. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laika-queue

Laika Queue

Background job processing for the Laika PHP MVC Framework — database, Redis, and JSON drivers behind one interface, with delayed jobs, retry backoff, failed-job tracking, and a signal-aware worker process.

This README is the full reference. If you just want the short version, the framework handbook has a Queue chapter.

Contents


Install

Inside a Laika app you normally get it already — laikait/laika-core depends on it.

Installing generates a single worker executable in your project root — the same file on every platform — so the entry point always matches the version in vendor/:

File Platform How you run it
worker all php worker default — or ./worker default on Linux/macOS

It is a thin proxy into vendor/laikait/laika-queue/bin/worker. It's rewritten only when its contents actually change, and regenerates if you delete it.

Windows: run it as php worker ..., not a bare worker .... There is deliberately no worker.bat shim — cmd and PowerShell resolve commands through PATHEXT and will never execute an extensionless file. A worker.bat left over from an earlier version is deleted on the next composer install.

Generation is driven by Laika\Queue\ScriptHandler::generate. Composer only runs scripts declared by the root project, never by a dependency, so a project not created from the framework skeleton needs to wire it itself:

The framework skeleton already has this. ScriptHandler is a safe no-op anywhere lf-boot/app.php isn't present, so a global install or the package's own CI won't trip over it.

This package used to ship as a Composer plugin, which forced an allow-plugins entry into every consuming project. It's a plain library now — you can drop "laikait/laika-queue": true from your config.allow-plugins.

Global install

Want one worker command across every project?

Put Composer's global vendor/bin on your PATH (see the Composer docs), then run worker from inside any Laika project:

The global binary finds the project by walking up from your working directory until it hits lf-boot/app.php — no php prefix needed, and it works from a sub-directory.


Quick start

The whole loop, end to end.

1. Create the job

That writes lf-app/Job/SendWelcomeEmail.php.

2. Fill in handle()

3. Pick a backend in lf-config/queue.php — 'json' needs no setup and is the default:

Choosing 'database' (or leaving failed_driver on 'database') needs its tables created once — see Schema & migrations.

4. Dispatch it from a controller, service, anywhere:

5. Run the worker

That's it. The job runs, retries on failure with backoff, and lands in the failed-job log once it exhausts maxTries — inspect it with php laika queue:failed.


Configuration

Everything lives in lf-config/queue.php:

Key Default Notes
driver 'database' when the key is absent entirely; the shipped config file sets 'json' Selects DatabaseDriver, RedisDriver, or JsonDriver
connection 'default' A connection name from lf-config/database.php, never a PDO instance
failed_driver null null resolves to 'database' when driver is 'database', otherwise 'json'
reserve_timeout 90 Seconds before a reserved job counts as stalled. RedisDriver only — the other two have no reaper
max_tries 3 Reclaims allowed before a stalled job is moved to :dead. 0 disables the cap. RedisDriver only

Why failed_driver is separate: there is no Redis-backed failed-job provider. A redis queue driver still has to log its failures somewhere, so it falls back to json unless you say 'database'.

Beyond reserve_timeout / max_tries above, the redis and json drivers have no connection config of their own:

This same resolution logic lives in two places, kept deliberately in sync: bin/worker (for the worker process) and Laika\Core\Worker\Queue (for the queue:* commands and your own dispatch code).


Defining jobs

Extend Laika\Queue\Abstracts\Job and implement handle(). Use php laika job:make to scaffold one.

Properties

Property Type Default Meaning
$id string '' Assigned by the driver on push(). Don't set it yourself.
$queue string 'default' See the trap below — push() overwrites this.
$tries int 0 Current attempt number, set by the driver on pop().
$maxTries int 3 Attempts before the job is handed to the failed-job provider.
$retryAfter int 60 Base seconds for exponential backoff.
$delay int 0 Not read by anything — see the trap below.
$backoffStrategy int\|array\|null null Retry spacing, see below. Protected.
$jitter bool true Randomize backoff by ±20%. Protected.

Backoff

backoff() returns the seconds to wait before the next attempt:

$backoffStrategy Behaviour
null (default) Exponential: retryAfter * 2^(tries-1), capped at 3600s
int Linear: n * tries
array Per-attempt steps, e.g. [10, 30, 60]. Clamps to the last entry once attempts exceed the array; an empty array falls back to exponential

With $jitter = true (the default) the result is spread by ±20% so a batch of jobs failing together doesn't retry in lockstep. Set $jitter = false for deterministic timing.

Two traps worth knowing

failed() fires on every failed attempt, not just the last one. Worker::process() calls $job->failed($e) before it decides whether to retry or give up. If you only want the final-failure behaviour, check the counter yourself:

$queue and $delay on the job object are not what routes it. Every driver's push() signature is push(Job $job, string $queue = 'default', int $delay = 0), and it assigns $job->queue = $queue from that argument. So a job declaring public string $queue = 'emails'; — which is exactly what job:make --queue=emails generates — still gets pushed to default unless you pass the queue explicitly:

$job->delay is never read by any driver; delay is the third push() argument only.


Dispatching jobs

Inside the framework (recommended)

Laika\Core\Worker\Queue builds whichever driver lf-config/queue.php names, so your dispatch code doesn't hardcode a backend and doesn't drift from the worker's choice:

Queue ships in laikait/laika-core, so it's always available in a Laika app — web requests included, not just CLI.

Laika\Cli\QueueResolver is the former home of this API and still works, delegating to Queue with the CLI's historical database default. Prefer Queue in new code.

Constructing a driver directly

The escape hatch when you need a specific backend regardless of config:

DatabaseDriver never opens PDO itself — it refers to a connection by name from laika-model's registry. Register the connection first, and make sure the tables exist (Schema & migrations) — the driver no longer creates them.

The driver interface

All three drivers implement Laika\Queue\Interfaces\QueueDriverInterface:

size() counts every unreserved job including ones delayed into the future — consistent across all three drivers.


CLI reference

Eight commands ship with laikait/laika-cli for this package.

Jobs

Command Description
php laika job:make <name> [--queue=default] [--max=3] Scaffold lf-app/Job/<name>.php
php laika job:list List every discovered Job subclass
php laika job:remove <name> Delete a job class
php laika job:rename <--old=name> <--new=name> Rename a job class

job:make validates that the name and queue are alphabetic (/^[a-z_]+$/i) and that --max is a positive integer.

Queue

Command Description
php laika queue:work [queue] Run the worker in the foreground (default queue: default)
php laika queue:failed [--queue=name] Table of failed jobs — id, queue, failed-at, first line of the exception
php laika queue:retry <id>\|--all [--queue=name] Re-push failed job(s) onto their original queue
php laika queue:flush [--hours=N] Clear failed jobs; --hours keeps anything newer than N hours

Notes:


Drivers

Driver Storage Concurrency Use when
JsonDriver lf-storage/queues/jobs.json flock — safe on a local filesystem Local/dev, zero setup
DatabaseDriver laika-model (PDO) No row lock, see caveat You already have a database and want durability
RedisDriver phpredis Atomic — safe for multiple workers Throughput, multiple concurrent workers

RedisDriver

The throughput choice, and safe for concurrent workers: pop() is a Lua script, so claiming a job is atomic. It keeps six keys per queue — :pending (list), :delayed (sorted set), :reserved (sorted set), :jobs (hash of payloads), :attempts (hash of per-job delivery counts) and :dead (sorted set) — and every pop() first migrates due delayed jobs into pending, then sweeps reserved jobs older than reserve_timeout back into pending. That sweep is what makes it self-healing when a worker dies mid-job.

The sweep doesn't requeue forever. Each reclaim checks the job's :attempts count, and once it reaches max_tries the job is moved to :dead instead of back onto pending — so a job that reliably kills its worker stops cycling. Its payload stays in :jobs for inspection; nothing prunes :dead automatically.

Build it with RedisDriver::fromConfig() rather than handing it an open Redis instance if it'll run inside a forking worker — see ReconnectableDriver. Requires ext-redis.

DatabaseDriver

Queries through Laika\Queue\Model\QueueModel using laika-model's portable query builder — no raw SQL, no per-driver branching, so it works identically on every backend laika-model supports (mysql, mariadb, pgsql, sqlite, sqlsrv, oci, firebird). The tradeoff is the missing row lock; see Concurrency caveat.

JsonDriver

One JSON array of records in a single file. Every read-modify-write goes through Laika\Core\Storage\JsonStorage::mutate(), which holds a single flock(LOCK_EX) across the whole read → mutate → write, so two workers can't claim the same job. Fine for development, not intended for production. Note it depends on APP_PATH and on JsonStorage itself, so unlike the other two it cannot be used outside a Laika app.


The worker

The queue name is optional and defaults to default. One worker process handles exactly one queue name — run it multiple times for more than one queue. There's no validation against your config: a typo'd name doesn't error, it just runs forever popping nothing.

Worker::work() takes four arguments:

Signals

Signal Effect
SIGTERM, SIGINT Graceful stop — finishes the current job, then exits
SIGUSR2 Pause (keeps running, stops popping)
SIGCONT Resume

Requires ext-pcntl. Without it the worker still runs, it just can't be signalled.

Per-job timeout

With pcntl available, each job runs in a forked child. If the child exceeds timeout, the parent SIGKILLs it and treats it as a failed attempt — released with backoff, or logged to the failed-job provider once maxTries is exhausted. So a job that hangs repeatedly is subject to the same retry ceiling as one that throws.

Without pcntl/posix (i.e. Windows) the worker degrades to running jobs inline in the parent process: still correct, but no timeout enforcement and no signal handling.

Reconnecting after fork

pcntl_fork() duplicates the parent's open sockets into the child without reopening them, so both processes share one connection — and killing the child mid-write can desync the parent's socket too. Drivers that hold a stateful connection implement Laika\Queue\Interfaces\ReconnectableDriver, and the worker calls reconnect() in the freshly forked child before running the job.

RedisDriver implements it (which is why fromConfig() matters — a driver built from a bare Redis instance has no way to reopen). DatabaseDriver and DatabaseFailedJobProvider don't: laika-model's Connection registry owns the PDO lifecycle, not the driver.

Memory

The worker exits gracefully when memory crosses a soft threshold, letting supervisor or systemd restart it with a clean heap — this is normal operation, not a crash.

bin/worker first applies the framework's CLI_MEMORY_LIMIT (from lf-inc/const.php) as the process's real memory_limit via Laika\Core\System\MemoryManager. Then Worker::work() with memoryLimit: null reads PHP's current memory_limit and takes ~90% of it as the restart threshold, so the worker bows out before genuinely risking a hard OOM mid-job. Pass an explicit int (MB) to override.

Falls back to a flat 128MB when laikait/laika-core isn't installed, when memory_limit can't be parsed, or when it's unlimited (-1).


Failed jobs

A job that exhausts maxTries — by throwing or by timing out — is handed to a FailedJobProviderInterface and removed from the queue, rather than silently dropped or retried forever.

Two implementations: DatabaseFailedJobProvider and JsonFailedJobProvider. Chosen by failed_driver; there's deliberately no Redis one.

Typical workflow:

Retrying is subject to the same trusted-class allow-list as the worker, since it has to unserialize the stored payload.


Schema & migrations

Two tables, created only when you use a database driver.

laika_queue_jobs — id (char 36, PK), queue (string 100), payload (longtext), attempts (uint, default 0), reserved_at (uint, nullable), available_at (uint), created_at (uint), plus a composite index on (queue, reserved_at, available_at).

laika_failed_jobs — id (char 36, PK), queue (string 100), payload (longtext), exception (text), failed_at (uint), indexed on queue.

The tables are not created for you. Earlier versions had the driver and the failed-job provider create their own tables on construction via ensureSchema(). That method is gone — run the migration once before the first worker start, or push() will fail against a missing table.

Create them once, on the connection the driver uses:

With no argument, both follow queue.connection from lf-config/queue.php; outside a Laika app they fall back to 'default', or pass a connection name. Both use createIfNotExists(), so running them again is safe.

php laika app:migrate does not create these tables. Since v1.1.1 the package no longer declares its models and schemas under extra.laika.resources, so the framework's resource loader does not see them.

DatabaseDriver::install() and DatabaseFailedJobProvider::install() also create the tables, but drop them first, deleting every pending or failed job. Use them for a fresh install or a test, never on a live queue.

Both schema classes extend Laika\Model\Contract\SchemaAbstract and use Laika\Model\Schema\Schema / Blueprint, so schema creation needs laikait/laika-model, not laika-core. They're plain PSR-4 files, lazily autoloaded only when something references them — and since nothing in the drivers touches them any more, that means the explicit calls above, nothing else.

The models themselves (QueueModel, FailedJobModel) are ordinary Laika\Model\Model subclasses with the usual $table / $id / $connection convention — query them directly if you want your own dashboard.


Production

The worker is a long-running process, not a cron job, and it self-exits on the memory threshold. Keep it alive with a supervisor.

supervisor

systemd

Set stopwaitsecs / TimeoutStopSec comfortably above your longest job timeout so a graceful stop can finish the job in flight instead of being killed.

For several queues, add one program block per queue name rather than raising numprocs — and read the caveat below before raising numprocs at all.

Which user?

www-data above is not created by Laika, and it isn't universal — it's the account the Debian/Ubuntu packages give Apache, nginx and PHP-FPM. Elsewhere it's called something else:

Platform Usual user
Debian, Ubuntu www-data
RHEL, CentOS, Rocky, Alma, Fedora apache (httpd) or nginx
Arch http
Alpine nginx or apache
FreeBSD www
cPanel, Plesk, most shared hosting your account name — each account runs as itself

Find yours rather than guessing:

or from inside the app: posix_getpwuid(posix_geteuid())['name'].

Run the worker as whatever user owns the project files. This is not cosmetic. The worker writes lf-storage/ — the JSON driver's jobs.json and failed.json especially, which the web process reads and writes too. Get the user wrong and you end up with files the web server can't touch, or vice versa: jobs silently failing to enqueue, or a Permission denied the first time a request tries to push.

Shared hosting

On most shared hosting you have no root, so neither supervisor nor systemd is available and the User= line is moot — everything already runs as your account. Check what your panel offers first; many expose a "background worker", "daemon", or long-running process feature that does the same job.

Failing that, a cron watchdog restarts the worker whenever it isn't running. The worker exits on its own memory threshold, so something has to bring it back:

Note this is a watchdog, not a per-minute run — bin/worker takes only a queue name and loops until it's signalled or hits the memory ceiling; there's no --once or max-jobs flag to make it exit after a batch. Match the pgrep pattern to the queue name so one watchdog line per queue doesn't restart the wrong worker.

If your host kills long-running processes outright — some do — the queue isn't a good fit there; dispatch to an external worker instead.


Concurrency caveat

DatabaseDriver::pop() has no row lock.

DatabaseDriver claims a job with a plain SELECT (filtered to unreserved, due rows) followed by an UPDATE, both inside a transaction, entirely through laika-model's portable query builder. There's no FOR UPDATE / SKIP LOCKED or equivalent, because those aren't portable across the seven backends laika-model targets.

The consequence: two worker processes calling pop() on the same queue at close to the same moment can both read the same unreserved row before either commits its UPDATE, and both will process that job.

This matters only if you run more than one worker on the same queue. Your options:

RedisDriver and JsonDriver are unaffected — the former claims jobs in a Lua script, the latter under a single flock(LOCK_EX) spanning its whole read-modify-write. The flock guarantee holds on a local filesystem; it is unreliable on NFS and some other network filesystems, so don't put jobs.json on a share and expect it to arbitrate between hosts.


Security: trusted job classes

Job payloads are restored with PHP's unserialize(). To prevent PHP Object Injection — an attacker-controlled payload instantiating arbitrary classes to build a gadget chain — Job::unserializePayload() only instantiates classes explicitly registered as trusted. The default list is empty, so it throws rather than silently unserializing something unexpected.

In a Laika app this is handled for you. bin/worker and queue:retry both register every Job subclass discovered under lf-app/Job on startup, via Laika\Service\Infra::getQueueJobsClasses() — the same lookup php laika job:list uses. Discovery only admits classes that genuinely extend Job, so this stays much narrower than trusting the codebase at large. No config needed.

Register manually for job classes living outside lf-app/Job, or when using this package standalone:

Do it once at bootstrap, before any pop(). Calls are additive and de-duplicated.


Known gaps


Standalone use (outside the framework)

The package works without the Laika framework, with limits:

Piece Standalone?
Job, Worker, QueueDriverInterface Yes — no framework dependency
RedisDriver Yes — needs ext-redis only
DatabaseDriver, DatabaseFailedJobProvider Yes — needs laikait/laika-model
The Schema classes Yes — needs laikait/laika-model; call up() yourself, in or outside a Laika app
JsonDriver, JsonFailedJobProvider No — depend on the APP_PATH constant and on Laika\Core\Storage\JsonStorage
bin/worker, queue:* / job:* commands No — require a Laika app (lf-boot/app.php)
Auto-registered trusted classes No — call Job::registerTrustedClasses() yourself

A minimal standalone loop:

Note laikait/laika-model is not declared in this package's composer.json, since RedisDriver doesn't need it — install it yourself if you're using the database backend.


Requirements

License

MIT


All versions of laika-queue with dependencies

PHP Build Version
Package Version
Requires php Version ^8.1
ext-pdo Version *
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package laikait/laika-queue contains the following files

Loading the files please wait ...