yokai/batch
Yokai Batch is a Laravel package for running and managing batch jobs, with tools to define batches, track progress, and handle failures in queued workloads. It integrates with Laravel's queue and provides a simple API for orchestrating long-running or bulk tasks.
yokai/batch is a framework-agnostic PHP implementation of Spring-Batch-style concepts: a Job is composed of Steps, a step is either a Tasklet (one unit of work) or an ItemStep (chunk-oriented read → process → write), and a JobLauncher executes jobs while a JobExecutionRepository records their state.
Minimal path:
composer require yokai/batch.yokai/batch-bundle); on Laravel there is no first-party integration, so register the services yourself in a service provider.src/Job, src/Step, src/Item, src/Launcher and the tests/ directory first — tests are the most reliable documentation for this package.use Yokai\Batch\Job\SimpleJob;
use Yokai\Batch\Step\SimpleStep;
use Yokai\Batch\Tasklet\CallbackTasklet;
$job = new SimpleJob('nightly-cleanup', [
new SimpleStep('purge', new CallbackTasklet(function () {
// one-off logic: delete stale rows, warm a cache, etc.
})),
]);
JobRegistry, construct a SimpleJobLauncher with a JobExecutionRepository (in-memory for a smoke test), and call ->launch('nightly-cleanup', new JobParameters()).First real use case: a CSV/API importer. Use an ItemStep with a streaming ItemReader (yields rows), an optional ItemProcessor (normalise/validate), and an ItemWriter (persist per chunk). This gives you bounded memory and per-chunk flush semantics without writing your own loop.
Chunk-oriented steps are the default pattern. Prefer ItemStep over a fat Tasklet whenever you are transforming a stream. The chunk size is the main tuning knob: it controls memory, transaction/flush frequency, and how much work is lost on a mid-step failure.
new ItemStep(
name: 'import-customers',
reader: new CsvReader($path),
writer: new CustomerWriter($repository),
processor: new CustomerProcessor(),
batchSize: 100,
);
Tasklets for everything that isn't read/process/write. Cleanup, notifications, file moves, cache rebuilds, “mark stale rows” — a CallbackTasklet keeps the job definition readable and keeps orchestration in one place.
Compose multi-step jobs sequentially. Split a pipeline into download → validate → import → report steps. Each step gets its own StepExecution, so failures are localised and the execution record tells you exactly where the job stopped.
Pass everything through JobParameters. File paths, date windows, tenant IDs, dry-run flags — never read ambient globals from a reader/writer. This makes jobs reproducible and lets you launch the same definition from a controller, an Artisan command, or a queue worker.
Launch from your existing entry points. In Laravel, wrap the launcher in an Artisan command and dispatch it to a queue:
Artisan::command('batch:run {job} {--param=*}', function () {
$this->launcher->launch($this->argument('job'), new JobParameters($this->option('param')));
});
Keep the command thin; the job should be runnable without the console.
Treat the execution repository as your observability layer. Query last execution status/start/end times to build a status endpoint or dashboard, and to decide whether a job is safe to re-run. For anything beyond a demo, back it with a persistent store (Doctrine-backed repository in the Symfony bundle, or your own implementation for Laravel) rather than the in-memory one.
Decorate rather than subclass. The interfaces (Job, Step, Tasklet, ItemReader/Processor/Writer, JobLauncher) are small, so wrapping a launcher to add logging, metrics, locking, or “skip if already running” is straightforward and keeps the core definitions clean.
Make writers idempotent. Jobs will be re-run after failures. Upserts keyed on a natural identifier (or a “delete window then insert” strategy) beat plain inserts.
composer.json and audit PHP version constraints before committing.Bus::batch. The naming overlaps, but this library owns job/step lifecycle, not queue fan-out. You still need a queue/worker to run it in the background.JobRegistry, SimpleJobLauncher, and the repository yourself. Keep that wiring in one provider so job definitions stay declarative.ItemReader/ItemWriter implementations, a custom JobExecutionRepository, and launcher decorators cover almost every integration need without touching the library.How can I help you explore Laravel packages today?