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

JBZoo / Cli / 18073060279

28 Sep 2025 10:34AM UTC coverage: 80.982% (-0.3%) from 81.315%
18073060279

push

github

web-flow
build(deps): Upgrade Symfony to v7 and PHP to 8.2 (#30)

- Bump Symfony components (console, process, lock) to ^7.3.4.
- Increase minimum PHP requirement to 8.2.
- Replace external process manager with an internal
JBZoo\Cli\ProcessManager implementation to maintain compatibility with
Symfony 7.
- Update other core dependencies: jbzoo/utils, jbzoo/event,
monolog/monolog, jbzoo/toolbox-dev.
- Adjust CI workflows to support PHP 8.2+ and remove scheduled runs.
- Refactor various classes to be final and update type hints for
improved code quality and Psalm compatibility.
- Remove deprecated phpstan.neon configuration file.

65 of 78 new or added lines in 6 files covered. (83.33%)

15 existing lines in 3 files now uncovered.

1056 of 1304 relevant lines covered (80.98%)

229.63 hits per line

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

90.91
/src/ProcessManager/ProcessManager.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Cli.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Cli
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Cli\ProcessManager;
18

19
use Symfony\Component\Process\Exception\ProcessTimedOutException;
20
use Symfony\Component\Process\Process;
21

22
/**
23
 * The process manager for executing multiple processes in parallel.
24
 * @author BluePsyduck <bluepsyduck@gmx.com>
25
 * @psalm-suppress ClassMustBeFinal
26
 */
27
class ProcessManager implements ProcessManagerInterface
28
{
29
    /** The number of processes to run in parallel. */
30
    protected int $numberOfParallelProcesses;
31

32
    /**
33
     * The processes currently waiting to be executed.
34
     * @var array<array{Process, null|callable, array}>
35
     */
36
    protected array $pendingProcessData = [];
37

38
    /**
39
     * The processes currently running.
40
     * @var array<Process>
41
     */
42
    protected array $runningProcesses = [];
43

44
    /**
45
     * The callback for when a process is about to be started.
46
     * @var null|callable
47
     */
48
    protected $processStartCallback;
49

50
    /** The interval to wait between the polls of the processes, in milliseconds. */
51
    private int $pollInterval;
52

53
    /** The time to delay the start of processes to space them out, in milliseconds. */
54
    private int $processStartDelay;
55

56
    /**
57
     * The callback for when a process has finished.
58
     * @var null|callable
59
     */
60
    private $processFinishCallback;
61

62
    /**
63
     * The callback for when a process timed out.
64
     * @var null|callable
65
     */
66
    private $processTimeoutCallback;
67

68
    /**
69
     * The callback for when a process is checked.
70
     * @var null|callable
71
     */
72
    private $processCheckCallback;
73

74
    /**
75
     * @param int $numberOfParallelProcesses the number of processes to run in parallel
76
     * @param int $pollInterval              the interval to wait between the polls of the processes, in milliseconds
77
     * @param int $processStartDelay         the time to delay the start of processes to space them out, in milliseconds
78
     */
79
    public function __construct(
80
        int $numberOfParallelProcesses = 1,
81
        int $pollInterval = 100,
82
        int $processStartDelay = 0,
83
    ) {
84
        $this->numberOfParallelProcesses = $numberOfParallelProcesses;
18✔
85
        $this->pollInterval              = $pollInterval;
18✔
86
        $this->processStartDelay         = $processStartDelay;
18✔
87
    }
88

89
    /**
90
     * Sets the number of processes to run in parallel.
91
     */
92
    public function setNumberOfParallelProcesses(int $numberOfParallelProcesses): self
93
    {
94
        $this->numberOfParallelProcesses = $numberOfParallelProcesses;
18✔
95
        $this->executeNextPendingProcess(); // Start new processes in case we increased the limit.
18✔
96

97
        return $this;
18✔
98
    }
99

100
    /**
101
     * Sets the interval to wait between the polls of the processes, in milliseconds.
102
     */
103
    public function setPollInterval(int $pollInterval): self
104
    {
105
        $this->pollInterval = $pollInterval;
18✔
106

107
        return $this;
18✔
108
    }
109

110
    /**
111
     * Sets the time to delay the start of processes to space them out, in milliseconds.
112
     */
113
    public function setProcessStartDelay(int $processStartDelay): self
114
    {
115
        $this->processStartDelay = $processStartDelay;
18✔
116

117
        return $this;
18✔
118
    }
119

120
    /**
121
     * Sets the callback for when a process is about to be started.
122
     * @param null|callable $processStartCallback the callback, accepting a Process as only argument
123
     */
124
    public function setProcessStartCallback(?callable $processStartCallback): self
125
    {
126
        $this->processStartCallback = $processStartCallback;
18✔
127

128
        return $this;
18✔
129
    }
130

131
    /**
132
     * Sets the callback for when a process has finished.
133
     * @param null|callable $processFinishCallback the callback, accepting a Process as only argument
134
     */
135
    public function setProcessFinishCallback(?callable $processFinishCallback): self
136
    {
137
        $this->processFinishCallback = $processFinishCallback;
18✔
138

139
        return $this;
18✔
140
    }
141

142
    /**
143
     * Sets the callback for when a process timed out.
144
     */
145
    public function setProcessTimeoutCallback(?callable $processTimeoutCallback): self
146
    {
147
        $this->processTimeoutCallback = $processTimeoutCallback;
18✔
148

149
        return $this;
18✔
150
    }
151

152
    /**
153
     * Sets the callback for when a process is checked.
154
     */
155
    public function setProcessCheckCallback(?callable $processCheckCallback): self
156
    {
NEW
157
        $this->processCheckCallback = $processCheckCallback;
×
158

NEW
159
        return $this;
×
160
    }
161

162
    /**
163
     * Adds a process to the manager.
164
     */
165
    public function addProcess(Process $process, ?callable $callback = null, array $env = []): self
166
    {
167
        $this->pendingProcessData[] = [$process, $callback, $env];
18✔
168
        $this->executeNextPendingProcess();
18✔
169
        $this->checkRunningProcesses();
18✔
170

171
        return $this;
18✔
172
    }
173

174
    /**
175
     * Waits for all processes to be finished.
176
     */
177
    public function waitForAllProcesses(): self
178
    {
179
        while ($this->hasUnfinishedProcesses()) {
18✔
180
            $this->sleep($this->pollInterval);
18✔
181
            $this->checkRunningProcesses();
18✔
182
        }
183

184
        return $this;
18✔
185
    }
186

187
    /**
188
     * Returns whether the manager still has unfinished processes.
189
     */
190
    public function hasUnfinishedProcesses(): bool
191
    {
192
        return \count($this->pendingProcessData) > 0 || \count($this->runningProcesses) > 0;
18✔
193
    }
194

195
    /**
196
     * Executes the next pending process, if the limit of parallel processes is not yet reached.
197
     */
198
    protected function executeNextPendingProcess(): void
199
    {
200
        if ($this->canExecuteNextPendingRequest()) {
18✔
201
            $this->sleep($this->processStartDelay);
18✔
202

203
            $data = \array_shift($this->pendingProcessData);
18✔
204
            if ($data !== null) {
18✔
205
                [$process, $callback, $env] = $data;
18✔
206
                // @var Process $process
207
                $this->invokeCallback($this->processStartCallback, $process);
18✔
208
                $process->start($callback, $env);
18✔
209

210
                $pid = $process->getPid();
18✔
211
                if ($pid === null) {
18✔
212
                    // The process finished before we were able to check its process id.
NEW
213
                    $this->checkRunningProcess($pid, $process);
×
214
                } else {
215
                    $this->runningProcesses[$pid] = $process;
18✔
216
                }
217
            }
218
        }
219
    }
220

221
    /**
222
     * Checks whether a pending request is available and can be executed.
223
     */
224
    protected function canExecuteNextPendingRequest(): bool
225
    {
226
        return \count($this->runningProcesses) < $this->numberOfParallelProcesses
18✔
227
            && \count($this->pendingProcessData) > 0;
18✔
228
    }
229

230
    /**
231
     * Checks the running processes whether they have finished.
232
     */
233
    protected function checkRunningProcesses(): void
234
    {
235
        foreach ($this->runningProcesses as $pid => $process) {
18✔
236
            $this->checkRunningProcess((int)$pid, $process);
18✔
237
        }
238
    }
239

240
    /**
241
     * Sleeps for the specified number of milliseconds.
242
     * @phan-suppress PhanPluginPossiblyStaticProtectedMethod
243
     */
244
    protected function sleep(int $milliseconds): void
245
    {
246
        \usleep($milliseconds * 1000);
18✔
247
    }
248

249
    /**
250
     * Checks the process whether it has finished.
251
     */
252
    protected function checkRunningProcess(?int $pid, Process $process): void
253
    {
254
        $this->invokeCallback($this->processCheckCallback, $process);
18✔
255
        $this->checkProcessTimeout($process);
18✔
256
        if (!$process->isRunning()) {
18✔
257
            $this->invokeCallback($this->processFinishCallback, $process);
18✔
258

259
            if ($pid !== null) {
18✔
260
                unset($this->runningProcesses[$pid]);
18✔
261
            }
262
            $this->executeNextPendingProcess();
18✔
263
        }
264
    }
265

266
    /**
267
     * Checks whether the process already timed out.
268
     */
269
    protected function checkProcessTimeout(Process $process): void
270
    {
271
        try {
272
            $process->checkTimeout();
18✔
NEW
273
        } catch (ProcessTimedOutException) {
×
NEW
274
            $this->invokeCallback($this->processTimeoutCallback, $process);
×
275
        }
276
    }
277

278
    /**
279
     * Invokes the callback if it is an callable.
280
     * @phan-suppress PhanPluginPossiblyStaticProtectedMethod
281
     */
282
    protected function invokeCallback(?callable $callback, Process $process): void
283
    {
284
        if (\is_callable($callback)) {
18✔
285
            $callback($process);
18✔
286
        }
287
    }
288
}
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

© 2025 Coveralls, Inc