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

daycry / cronjob / 18288101817

06 Oct 2025 04:45PM UTC coverage: 68.511% (-1.0%) from 69.514%
18288101817

push

github

daycry
Update README.md

483 of 705 relevant lines covered (68.51%)

5.53 hits per line

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

74.36
/src/Scheduler.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Daycry\CronJob;
6

7
use Closure;
8
use CodeIgniter\Exceptions\RuntimeException;
9

10
/**
11
 * Class Scheduler
12
 *
13
 * Handles the registration and management of scheduled jobs.
14
 */
15
class Scheduler
16
{
17
    /**
18
     * @var list<Job> List of scheduled jobs
19
     */
20
    private array $tasks = [];
21

22
    /**
23
     * Returns the created Tasks.
24
     *
25
     * @return list<Job>
26
     */
27
    public function getTasks(): array
28
    {
29
        return $this->tasks;
×
30
    }
31

32
    /**
33
     * Removes all scheduled tasks.
34
     */
35
    public function clearTasks(): void
36
    {
37
        $this->tasks = [];
×
38
    }
39

40
    /**
41
     * Find a task by its name.
42
     */
43
    public function findTaskByName(string $name): ?Job
44
    {
45
        foreach ($this->tasks as $task) {
2✔
46
            if ($task->getName() === $name) {
2✔
47
                return $task;
2✔
48
            }
49
        }
50

51
        return null;
×
52
    }
53

54
    /**
55
     * Schedules a closure to run.
56
     */
57
    public function call(Closure $func): Job
58
    {
59
        return $this->createTask('closure', $func);
4✔
60
    }
61

62
    /**
63
     * Schedules a console command to run.
64
     */
65
    public function command(string $command): Job
66
    {
67
        return $this->createTask('command', $command);
4✔
68
    }
69

70
    /**
71
     * Schedules a local function to be exec'd
72
     */
73
    public function shell(string $command): Job
74
    {
75
        return $this->createTask('shell', $command);
1✔
76
    }
77

78
    /**
79
     * Schedules an Event to trigger
80
     *
81
     * @param string $name Name of the event to trigger
82
     */
83
    public function event(string $name): Job
84
    {
85
        return $this->createTask('event', $name);
×
86
    }
87

88
    /**
89
     * Schedules a cURL command to a remote URL
90
     */
91
    public function url(string $url): Job
92
    {
93
        return $this->createTask('url', $url);
×
94
    }
95

96
    /**
97
     * Internal method to create and register a job.
98
     *
99
     * @param mixed $action
100
     */
101
    protected function createTask(string $type, $action): Job
102
    {
103
        $task          = new Job($type, $action);
9✔
104
        $this->tasks[] = $task;
9✔
105

106
        return $task;
9✔
107
    }
108

109
    /**
110
     * Remove a task by its name.
111
     *
112
     * @return bool True if removed, false if not found
113
     */
114
    public function removeTaskByName(string $name): bool
115
    {
116
        foreach ($this->tasks as $i => $task) {
×
117
            if ($task->getName() === $name) {
×
118
                array_splice($this->tasks, $i, 1);
×
119

120
                return true;
×
121
            }
122
        }
123

124
        return false;
×
125
    }
126

127
    /**
128
     * Check if a task exists by name.
129
     */
130
    public function hasTask(string $name): bool
131
    {
132
        foreach ($this->tasks as $task) {
×
133
            if ($task->getName() === $name) {
×
134
                return true;
×
135
            }
136
        }
137

138
        return false;
×
139
    }
140

141
    /**
142
     * Get all task names.
143
     *
144
     * @return list<string>
145
     */
146
    public function getTaskNames(): array
147
    {
148
        return array_map(static fn(Job $t) => $t->getName(), $this->tasks);
3✔
149
    }
150

151
    /**
152
     * Validate dependencies for all tasks (existence and cycles).
153
     * Throws exception if invalid.
154
     *
155
     * @throws RuntimeException
156
     */
157
    public function validateDependencies(): void
158
    {
159
        $names = $this->getTaskNames();
3✔
160

161
        // Existence check
162
        foreach ($this->tasks as $task) {
3✔
163
            $deps = $task->getDependsOn();
3✔
164
            if (! $deps) { continue; }
3✔
165
            foreach ($deps as $dep) {
3✔
166
                if (! in_array($dep, $names, true)) {
3✔
167
                    throw new RuntimeException("Dependency '{$dep}' for job '{$task->getName()}' does not exist.");
1✔
168
                }
169
            }
170
        }
171
        // Cycle check (DFS)
172
        $visited = [];
2✔
173
        $stack   = [];
2✔
174

175
        foreach ($this->tasks as $task) {
2✔
176
            if ($this->hasCycle($task, $visited, $stack)) {
2✔
177
                throw new RuntimeException("Circular dependency detected involving job '{$task->getName()}'.");
1✔
178
            }
179
        }
180
    }
181

182
    /**
183
     * Helper for cycle detection (DFS).
184
     */
185
    private function hasCycle(Job $job, array &$visited, array &$stack): bool
186
    {
187
        $name = $job->getName();
2✔
188
        if (isset($stack[$name])) {
2✔
189
            return true;
1✔
190
        }
191
        if (isset($visited[$name])) {
2✔
192
            return false;
1✔
193
        }
194
        $visited[$name] = true;
2✔
195
        $stack[$name]   = true;
2✔
196
        $deps           = $job->getDependsOn();
2✔
197
        if ($deps) {
2✔
198
            foreach ($deps as $dep) {
2✔
199
                $depJob = $this->findTaskByName($dep);
2✔
200
                if ($depJob && $this->hasCycle($depJob, $visited, $stack)) {
2✔
201
                    return true;
1✔
202
                }
203
            }
204
        }
205
        unset($stack[$name]);
1✔
206

207
        return false;
1✔
208
    }
209

210
    /**
211
     * Topological sort for job execution order (Kahn's algorithm).
212
     * Returns an array of jobs in execution order or throws on cycle.
213
     *
214
     * @return list<Job>
215
     *
216
     * @throws RuntimeException
217
     */
218
    public function getExecutionOrder(): array
219
    {
220
        $jobsByName = [];
14✔
221
        $inDegree   = [];
14✔
222
        $graph      = [];
14✔
223

224
        foreach ($this->tasks as $job) {
14✔
225
            $name = $job->getName();
13✔
226
            $jobsByName[$name] = $job;
13✔
227
            $inDegree[$name] = 0;
13✔
228
            $graph[$name] = [];
13✔
229
        }
230

231
        foreach ($this->tasks as $job) {
14✔
232
            $name = $job->getName();
13✔
233
            $deps = $job->getDependsOn() ?? [];
13✔
234

235
            foreach ($deps as $dep) {
13✔
236
                if (! isset($jobsByName[$dep])) {
×
237
                    throw new RuntimeException("Dependency '{$dep}' for job '{$name}' does not exist.");
×
238
                }
239
                $graph[$dep][] = $name;
×
240
                $inDegree[$name]++;
×
241
            }
242
        }
243
        $queue = [];
14✔
244

245
        foreach ($inDegree as $name => $deg) { if ($deg === 0) { $queue[] = $name; } }
14✔
246
        $order = [];
14✔
247

248
        while ($queue) {
14✔
249
            $current = array_shift($queue);
13✔
250
            $order[] = $jobsByName[$current];
13✔
251

252
            foreach ($graph[$current] as $neighbor) {
13✔
253
                if (--$inDegree[$neighbor] === 0) { $queue[] = $neighbor; }
×
254
            }
255
        }
256
        if (count($order) !== count($jobsByName)) {
14✔
257
            throw new RuntimeException('Circular dependency detected in jobs.');
×
258
        }
259

260
        return $order;
14✔
261
    }
262
}
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