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

daycry / cronjob / 15775568161

20 Jun 2025 09:13AM UTC coverage: 69.514% (+4.1%) from 65.424%
15775568161

push

github

daycry
- New Features

130 of 163 new or added lines in 9 files covered. (79.75%)

6 existing lines in 3 files now uncovered.

415 of 597 relevant lines covered (69.51%)

3.21 hits per line

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

74.12
/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
    {
UNCOV
29
        return $this->tasks;
×
30
    }
31

32
    /**
33
     * Removes all scheduled tasks.
34
     */
35
    public function clearTasks(): void
36
    {
NEW
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

NEW
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);
1✔
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);
6✔
104
        $this->tasks[] = $task;
6✔
105

106
        return $task;
6✔
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
    {
NEW
116
        foreach ($this->tasks as $i => $task) {
×
NEW
117
            if ($task->getName() === $name) {
×
NEW
118
                array_splice($this->tasks, $i, 1);
×
119

NEW
120
                return true;
×
121
            }
122
        }
123

NEW
124
        return false;
×
125
    }
126

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

NEW
138
        return false;
×
139
    }
140

141
    /**
142
     * Get all task names.
143
     *
144
     * @return list<string>
145
     */
146
    public function getTaskNames(): array
147
    {
148
        $names = [];
3✔
149

150
        foreach ($this->tasks as $task) {
3✔
151
            $names[] = $task->getName();
3✔
152
        }
153

154
        return $names;
3✔
155
    }
156

157
    /**
158
     * Validate dependencies for all tasks (existence and cycles).
159
     * Throws exception if invalid.
160
     *
161
     * @throws RuntimeException
162
     */
163
    public function validateDependencies(): void
164
    {
165
        $names = $this->getTaskNames();
3✔
166

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

182
        foreach ($this->tasks as $task) {
2✔
183
            if ($this->hasCycle($task, $visited, $stack)) {
2✔
184
                throw new RuntimeException("Circular dependency detected involving job '{$task->getName()}'.");
1✔
185
            }
186
        }
187
    }
188

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

214
        return false;
1✔
215
    }
216

217
    /**
218
     * Topological sort for job execution order (Kahn's algorithm).
219
     * Returns an array of jobs in execution order or throws on cycle.
220
     *
221
     * @return list<Job>
222
     *
223
     * @throws RuntimeException
224
     */
225
    public function getExecutionOrder(): array
226
    {
227
        $jobsByName = [];
8✔
228
        $inDegree   = [];
8✔
229
        $graph      = [];
8✔
230

231
        foreach ($this->tasks as $job) {
8✔
232
            $name              = $job->getName();
7✔
233
            $jobsByName[$name] = $job;
7✔
234
            $inDegree[$name]   = 0;
7✔
235
            $graph[$name]      = [];
7✔
236
        }
237

238
        foreach ($this->tasks as $job) {
8✔
239
            $name = $job->getName();
7✔
240
            $deps = $job->getDependsOn() ?? [];
7✔
241

242
            foreach ($deps as $dep) {
7✔
NEW
243
                if (! isset($jobsByName[$dep])) {
×
NEW
244
                    throw new RuntimeException("Dependency '{$dep}' for job '{$name}' does not exist.");
×
245
                }
NEW
246
                $graph[$dep][] = $name;
×
NEW
247
                $inDegree[$name]++;
×
248
            }
249
        }
250
        $queue = [];
8✔
251

252
        foreach ($inDegree as $name => $deg) {
8✔
253
            if ($deg === 0) {
7✔
254
                $queue[] = $name;
7✔
255
            }
256
        }
257
        $order = [];
8✔
258

259
        while ($queue) {
8✔
260
            $current = array_shift($queue);
7✔
261
            $order[] = $jobsByName[$current];
7✔
262

263
            foreach ($graph[$current] as $neighbor) {
7✔
NEW
264
                $inDegree[$neighbor]--;
×
NEW
265
                if ($inDegree[$neighbor] === 0) {
×
NEW
266
                    $queue[] = $neighbor;
×
267
                }
268
            }
269
        }
270
        if (count($order) !== count($jobsByName)) {
8✔
NEW
271
            throw new RuntimeException('Circular dependency detected in jobs.');
×
272
        }
273

274
        return $order;
8✔
275
    }
276
}
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