• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / jobs / 26886467550

03 Jun 2026 01:01PM UTC coverage: 88.948% (+14.0%) from 74.974%
26886467550

push

github

web-flow
v3.0: single clean architecture (remove V1, lease-based queues, secure-by-default)

Complete v3.0 rewrite into a single, clean architecture. The v1 API and the V2\ scaffolding
are removed (no facade, no dual code); the package passes PHPStan level 6 + strict-rules +
codeigniter with NO baseline.

- Definition: Jobs::define()->...->dispatch() fluent builder -> immutable JobDefinition.
- Handlers decoupled from the god-object (JobHandlerInterface / AbstractJobHandler / TypedJobHandler + JobContext).
- One QueueBackend contract (enqueue/fetch(lease)/ack/nack(delay)/abandon/reapExpired) with 5 backends:
  Sync, Database, Redis, Beanstalk, ServiceBus.
- Runtime: one attempt per fetch; real interrupting Timeout; opt-in idempotency; single-instance lock.
- Worker/Cron: jobs:queue:work, jobs:queue:reap, jobs:cronjob:run, jobs:queue:purge.
- Secure-by-default: HMAC-signed envelopes, per-queue handler allowlist, ShellHandler deny-by-default,
  EventHandler allowlist, UrlHandler anti-SSRF.

Resolves audit findings #1,#2,#3,#4,#5,#6,#7,#8,#10,#12,#13,#17,#18,#19,#20,#22.
Tests: 359 (Beanstalk live); line coverage 88.9%; PHPStan/Psalm/Rector/cs green on PHP 8.2-8.5.

BREAKING CHANGE: v1 API removed. See docs/MIGRATION-v1-to-v3.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

983 of 1103 new or added lines in 43 files covered. (89.12%)

15 existing lines in 3 files now uncovered.

1497 of 1683 relevant lines covered (88.95%)

7.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.97
/src/Cron/Scheduler.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of Daycry Queues.
7
 *
8
 * (c) Daycry <daycry9@proton.me>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace Daycry\Jobs\Cron;
15

16
use CodeIgniter\Exceptions\RuntimeException;
17
use Daycry\Jobs\Definition\JobBuilder;
18
use Daycry\Jobs\Definition\JobDefinition;
19

20
/**
21
 * Fluent registry of scheduled jobs for the v3 cron runner.
22
 *
23
 * This is the v3 replacement for the v1 {@see \Daycry\Jobs\Cronjob\Scheduler}. Where the v1
24
 * scheduler held mutable {@see \Daycry\Jobs\Job} instances, the v3 scheduler accumulates
25
 * {@see JobBuilder} instances registered through {@see define()} and materialises them to
26
 * immutable {@see JobDefinition} value objects on demand.
27
 *
28
 * Usage:
29
 *   $scheduler->define('command', 'jobs:test')->named('reports')->dailyAt('02:00')->queue('reports');
30
 */
31
final class Scheduler
32
{
33
    /**
34
     * @var list<JobBuilder> Registered builders, in declaration order.
35
     */
36
    private array $builders = [];
37

38
    /**
39
     * Register a job by handler key and payload, returning the {@see JobBuilder} so the caller
40
     * can chain frequency/queue/identity helpers (e.g. ->dailyAt('02:00')->queue('reports')).
41
     */
42
    public function define(string $handler, mixed $payload = null): JobBuilder
43
    {
44
        $builder          = new JobBuilder($handler, $payload);
17✔
45
        $this->builders[] = $builder;
17✔
46

47
        return $builder;
17✔
48
    }
49

50
    /**
51
     * Materialise every registered builder into an immutable {@see JobDefinition}.
52
     *
53
     * @return list<JobDefinition>
54
     */
55
    public function getDefinitions(): array
56
    {
57
        return array_map(static fn (JobBuilder $builder): JobDefinition => $builder->toDefinition(), $this->builders);
19✔
58
    }
59

60
    /**
61
     * Return the registered definitions in dependency-safe execution order (topological sort by
62
     * {@see JobDefinition::$dependsOn}). Throws on circular dependencies or unknown dependencies.
63
     *
64
     * @return list<JobDefinition>
65
     */
66
    public function getExecutionOrder(): array
67
    {
68
        /** @var array<string, JobDefinition> $byName */
69
        $byName = [];
16✔
70
        /** @var array<string, list<string>> $graph */
71
        $graph = [];
16✔
72
        /** @var array<string, int> $inDegree */
73
        $inDegree = [];
16✔
74

75
        foreach ($this->getDefinitions() as $definition) {
16✔
76
            $name            = $this->resolveName($definition);
14✔
77
            $byName[$name]   = $definition;
14✔
78
            $graph[$name]    = [];
14✔
79
            $inDegree[$name] = 0;
14✔
80
        }
81

82
        foreach ($byName as $name => $definition) {
16✔
83
            foreach ($definition->dependsOn as $dependency) {
14✔
84
                if (! isset($byName[$dependency])) {
5✔
85
                    throw new RuntimeException("Dependency '{$dependency}' for job '{$name}' does not exist.");
1✔
86
                }
87
                $graph[$dependency][] = $name;
4✔
88
                $inDegree[$name]++;
4✔
89
            }
90
        }
91

92
        /** @var list<string> $queue */
93
        $queue = array_keys(array_filter($inDegree, static fn (int $degree): bool => $degree === 0));
15✔
94

95
        /** @var list<JobDefinition> $order */
96
        $order = [];
15✔
97

98
        while ($queue !== []) {
15✔
99
            $current = array_shift($queue);
12✔
100
            $order[] = $byName[$current];
12✔
101

102
            foreach ($graph[$current] as $neighbour) {
12✔
103
                if (--$inDegree[$neighbour] === 0) {
3✔
104
                    $queue[] = $neighbour;
3✔
105
                }
106
            }
107
        }
108

109
        if (count($order) !== count($byName)) {
15✔
110
            throw new RuntimeException('Circular dependency detected in jobs.');
1✔
111
        }
112

113
        return $order;
14✔
114
    }
115

116
    /**
117
     * Remove every registered builder.
118
     */
119
    public function clear(): void
120
    {
121
        $this->builders = [];
1✔
122
    }
123

124
    /**
125
     * Resolve the topological-sort key for a definition: its explicit name, or a stable fallback
126
     * derived from the handler when none was set (mirrors the v3 envelope naming).
127
     */
128
    private function resolveName(JobDefinition $definition): string
129
    {
130
        if ($definition->name !== null && $definition->name !== '') {
14✔
131
            return $definition->name;
14✔
132
        }
133

NEW
134
        return $definition->handler . ':' . substr(md5(serialize($definition->payload)), 0, 8);
×
135
    }
136
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc