PHPackages                             ez-php/scheduler - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. ez-php/scheduler

ActiveLibrary[Framework](/categories/framework)

ez-php/scheduler
================

Cron-based job scheduler with mutex-backed overlap prevention for ez-php applications

1.11.1(1mo ago)030MITPHPPHP ^8.5CI passing

Since Mar 23Pushed 1mo agoCompare

[ Source](https://github.com/ez-php/scheduler)[ Packagist](https://packagist.org/packages/ez-php/scheduler)[ Docs](https://github.com/ez-php/scheduler)[ RSS](/packages/ez-php-scheduler/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (14)Versions (26)Used By (0)

ez-php/scheduler
================

[](#ez-phpscheduler)

Cron-based job scheduler for ez-php applications. Register commands with a fluent frequency API, prevent overlapping runs via pluggable mutex drivers (File, Database), and execute due jobs from a single cron entry.

---

Installation
------------

[](#installation)

```
composer require ez-php/scheduler
```

---

Quick Start
-----------

[](#quick-start)

Create a schedule definition (e.g. `app/schedule.php`):

```
use EzPhp\Scheduler\Mutex\DatabaseMutex;
use EzPhp\Scheduler\Scheduler;

$pdo = $app->make(\PDO::class); // or any PDO instance
$scheduler = new Scheduler(new DatabaseMutex($pdo));

$scheduler->command('queue:work')->everyMinute()->withoutOverlapping();
$scheduler->command('cache:prune')->hourly();
$scheduler->command('reports:generate')->daily();
```

Run from a cron entry (once per minute):

```
* * * * * php /var/www/html/ez schedule:run
```

In the `schedule:run` command, pass a callable executor that dispatches to your console:

```
$scheduler->run(new DateTimeImmutable(), static function (string $command) use ($console): void {
    $console->call($command);
});
```

---

Frequency Methods
-----------------

[](#frequency-methods)

All methods are fluent and return `ScheduleEntry` for chaining:

MethodWhen due`everyMinute()`Every cron invocation`everyFiveMinutes()`When `minute % 5 === 0``hourly()`At `:00` of every hour`daily()`At `00:00``weekly()`On Sunday at `00:00``monthly()`On the 1st of the month at `00:00`An entry without a frequency set is **never due**.

---

Overlap Prevention
------------------

[](#overlap-prevention)

Call `withoutOverlapping()` to skip a command if a previous invocation is still running:

```
$scheduler->command('queue:work')->everyMinute()->withoutOverlapping();
```

Requires a `MutexInterface` passed to the `Scheduler` constructor. A `SchedulerException` is thrown at runtime if `withoutOverlapping()` is used without a mutex configured.

---

Mutex Drivers
-------------

[](#mutex-drivers)

### FileMutex

[](#filemutex)

Uses PHP's `flock(LOCK_EX|LOCK_NB)` on per-command lock files in a configurable directory.

```
use EzPhp\Scheduler\Mutex\FileMutex;

$mutex = new FileMutex('/var/run/ez-php/locks');
$scheduler = new Scheduler($mutex);
```

- The lock directory is created automatically if it does not exist.
- Lock files are never deleted — their inodes remain stable across runs.
- The lock is tied to the file handle, so a crashed process releases it automatically on the next cron run.
- Suitable for single-server deployments.

### DatabaseMutex

[](#databasemutex)

Uses a `scheduler_locks` table (created automatically via `CREATE TABLE IF NOT EXISTS`). Acquiring a lock inserts a row; releasing it deletes the row. A duplicate-key violation signals the lock is already held.

```
use EzPhp\Scheduler\Mutex\DatabaseMutex;

$mutex = new DatabaseMutex($pdo); // any PDO instance
$scheduler = new Scheduler($mutex);
```

- Compatible with MySQL and SQLite.
- No automatic TTL/expiry — stale rows from crashed processes must be cleaned manually.
- Suitable for multi-server deployments sharing the same database.

---

API Reference
-------------

[](#api-reference)

### `Scheduler`

[](#scheduler)

```
new Scheduler(?MutexInterface $mutex = null)
```

MethodDescription`command(string $name): ScheduleEntry`Register a command and return its entry for chaining`all(): list`Return all registered entries`dueEntries(DateTimeInterface $time): list`Return entries whose predicate matches `$time``run(DateTimeInterface $time, callable $executor): void`Execute all due entries via the callable### `ScheduleEntry`

[](#scheduleentry)

MethodDescription`everyMinute(): self`Due on every invocation`everyFiveMinutes(): self`Due at minute `:00`, `:05`, `:10`, …`hourly(): self`Due at minute `:00``daily(): self`Due at `00:00``weekly(): self`Due on Sunday at `00:00``monthly(): self`Due on the 1st at `00:00``withoutOverlapping(bool $enabled = true): self`Enable mutex-based skip`isDue(DateTimeInterface $time): bool`Evaluate the frequency predicate`getCommand(): string`Return the registered command name`getMutexKey(): string`Return a stable `sha1`-derived lock key### `MutexInterface`

[](#mutexinterface)

```
interface MutexInterface
{
    public function acquire(string $key): bool;
    public function release(string $key): void;
}
```

Implement this interface to add custom mutex backends (e.g. Redis, Memcached).

---

Custom Mutex
------------

[](#custom-mutex)

```
use EzPhp\Scheduler\MutexInterface;

final class RedisMutex implements MutexInterface
{
    public function __construct(private readonly \Redis $redis) {}

    public function acquire(string $key): bool
    {
        return (bool) $this->redis->set($key, 1, ['NX', 'EX' => 300]);
    }

    public function release(string $key): void
    {
        $this->redis->del($key);
    }
}
```

---

Exceptions
----------

[](#exceptions)

`SchedulerException` (extends `RuntimeException`) is thrown when:

- `withoutOverlapping()` is used but no `MutexInterface` was passed to `Scheduler`
- `FileMutex` cannot create the lock directory or open a lock file

Exceptions from the executor callable propagate up after the mutex lock is released (guaranteed via `finally`).

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance91

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity61

Established project with proven stability

 Bus Factor1

Top contributor holds 93% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

25

Last Release

43d ago

Major Versions

0.9.3 → 1.0.02026-03-24

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/122030400?v=4)[AU9500](/maintainers/AU9500)[@AU9500](https://github.com/AU9500)

---

Top Contributors

[![AU9500](https://avatars.githubusercontent.com/u/122030400?v=4)](https://github.com/AU9500 "AU9500 (66 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (5 commits)")

---

Tags

phpschedulerframeworkez-php

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ez-php-scheduler/health.svg)

```
[![Health](https://phpackages.com/badges/ez-php-scheduler/health.svg)](https://phpackages.com/packages/ez-php-scheduler)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
