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

JBZoo / Cli / 7685763438

28 Jan 2024 12:35PM UTC coverage: 80.746% (-3.1%) from 83.803%
7685763438

push

github

web-flow
Add support for PHP 8.3 and update Symfony dependencies to ^6.4 (#24)

* Add support for PHP 8.3 and update Symfony dependencies to ^6.4
* Fix default value handling in CliCommand

0 of 3 new or added lines in 1 file covered. (0.0%)

41 existing lines in 5 files now uncovered.

952 of 1179 relevant lines covered (80.75%)

234.54 hits per line

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

85.03
/src/ProgressBars/ProgressBarSymfony.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\ProgressBars;
18

19
use JBZoo\Cli\CliRender;
20
use JBZoo\Cli\Icons;
21
use JBZoo\Cli\OutputMods\AbstractOutputMode;
22
use JBZoo\Utils\Str;
23
use Symfony\Component\Console\Helper\ProgressBar as SymfonyProgressBar;
24
use Symfony\Component\Console\Output\OutputInterface;
25

26
use function JBZoo\Utils\isStrEmpty;
27

28
class ProgressBarSymfony extends AbstractSymfonyProgressBar
29
{
30
    private const LIMIT_ITEMT_FOR_PROFILING = 10;
31

32
    private OutputInterface     $output;
33
    private ?SymfonyProgressBar $progressBar = null;
34

35
    private string $finishIcon;
36
    private string $progressIcon;
37

38
    public function __construct(AbstractOutputMode $outputMode)
39
    {
40
        parent::__construct($outputMode);
138✔
41

42
        $this->output = $outputMode->getOutput();
138✔
43

44
        $this->progressIcon = Icons::getRandomIcon(Icons::GROUP_PROGRESS, $this->output->isDecorated());
138✔
45
        $this->finishIcon   = Icons::getRandomIcon(Icons::GROUP_FINISH, $this->output->isDecorated());
138✔
46
    }
47

48
    /**
49
     * @SuppressWarnings(PHPMD.NPathComplexity)
50
     */
51
    public function execute(): bool
52
    {
53
        if (!$this->init()) {
138✔
54
            return false;
18✔
55
        }
56

57
        $exceptionMessages = [];
120✔
58
        $isSkipped         = false;
120✔
59

60
        $currentIndex = 0;
120✔
61

62
        if ($this->callbackOnStart !== null) {
120✔
63
            \call_user_func($this->callbackOnStart, $this);
×
64
        }
65

66
        $isOptimizeMode = $this->isOptimizeMode();
120✔
67

68
        foreach ($this->list as $stepIndex => $stepValue) {
120✔
69
            $this->setStep($stepIndex, $currentIndex);
120✔
70

71
            $startTime   = 0;
120✔
72
            $startMemory = 0;
120✔
73
            if (!$isOptimizeMode) {
120✔
74
                $startTime   = \microtime(true);
12✔
75
                $startMemory = \memory_get_usage(false);
12✔
76
            }
77

78
            [$stepResult, $exceptionMessage] = $this->handleOneStep($stepValue, $stepIndex, $currentIndex);
120✔
79

80
            if (!$isOptimizeMode) {
114✔
81
                $this->stepMemoryDiff[] = \memory_get_usage(false) - $startMemory;
12✔
82
                $this->stepTimers[]     = \microtime(true) - $startTime;
12✔
83

84
                $this->stepMemoryDiff = self::sliceProfileStats($this->stepMemoryDiff);
12✔
85
                $this->stepTimers     = self::sliceProfileStats($this->stepTimers);
12✔
86
            }
87

88
            $exceptionMessages = \array_merge($exceptionMessages, (array)$exceptionMessage);
114✔
89

90
            if ($this->progressBar !== null) {
114✔
91
                $errorNumbers = \count($exceptionMessages);
24✔
92
                $errMessage   = $errorNumbers > 0 ? "<red-bl>{$errorNumbers}</red-bl>" : '0';
24✔
93
                $this->progressBar->setMessage($errMessage, 'jbzoo_caught_exceptions');
24✔
94
            }
95

96
            if (\str_contains($stepResult, ExceptionBreak::MESSAGE)) {
114✔
97
                $isSkipped = true;
12✔
98
                break;
12✔
99
            }
100

101
            $currentIndex++;
114✔
102
        }
103

104
        if ($this->progressBar !== null) {
108✔
105
            if ($isSkipped) {
24✔
106
                $this->progressBar->display();
×
107
            } else {
108
                $this->progressBar->finish();
24✔
109
            }
110
        }
111

112
        if ($this->callbackOnFinish !== null) {
108✔
113
            \call_user_func($this->callbackOnFinish, $this);
×
114
        }
115

116
        self::showListOfExceptions($exceptionMessages);
108✔
117

118
        return true;
90✔
119
    }
120

121
    protected function buildTemplate(): string
122
    {
123
        $progressBarLines = [];
24✔
124
        $footerLine       = [];
24✔
125

126
        $bar     = '[%bar%]';
24✔
127
        $percent = '%percent:2s%%';
24✔
128
        $steps   = '(%current% / %max%)';
24✔
129

130
        if (!isStrEmpty($this->title)) {
24✔
131
            $progressBarLines[] = "Progress of <blue>{$this->title}</blue>";
12✔
132
        }
133

134
        if ($this->output->isVeryVerbose()) {
24✔
135
            $progressBarLines[] = \implode(' ', [$percent, $steps, $bar, $this->finishIcon]);
6✔
136

137
            $footerLine['Time (pass/left/est/median/last)'] = \implode(' / ', [
6✔
138
                '%jbzoo_time_elapsed:9s%',
6✔
139
                '<info>%jbzoo_time_remaining:8s%</info>',
6✔
140
                '<comment>%jbzoo_time_estimated:8s%</comment>',
6✔
141
                '%jbzoo_time_step_median%',
6✔
142
                '%jbzoo_time_step_last%',
6✔
143
            ]);
6✔
144

145
            $footerLine['Mem (cur/peak/limit/leak/last)'] = \implode(' / ', [
6✔
146
                '%jbzoo_memory_current:8s%',
6✔
147
                '<comment>%jbzoo_memory_peak%</comment>',
6✔
148
                '%jbzoo_memory_limit%',
6✔
149
                '%jbzoo_memory_step_median%',
6✔
150
                '%jbzoo_memory_step_last%',
6✔
151
            ]);
6✔
152

153
            $footerLine['Caught exceptions'] = '%jbzoo_caught_exceptions%';
6✔
154
        } elseif ($this->output->isVerbose()) {
18✔
155
            $progressBarLines[] = \implode(' ', [
×
UNCOV
156
                $percent,
×
UNCOV
157
                $steps,
×
UNCOV
158
                $bar,
×
159
                $this->finishIcon,
×
UNCOV
160
                '%jbzoo_memory_current:8s%',
×
UNCOV
161
            ]);
×
162

163
            $footerLine['Time (pass/left/est)'] = \implode(' / ', [
×
UNCOV
164
                '%jbzoo_time_elapsed:8s%',
×
UNCOV
165
                '<info>%jbzoo_time_remaining:8s%</info>',
×
UNCOV
166
                '%jbzoo_time_estimated%',
×
UNCOV
167
            ]);
×
168

169
            $footerLine['Caught exceptions'] = '%jbzoo_caught_exceptions%';
×
170
        } else {
171
            $progressBarLines[] = \implode(' ', [
18✔
172
                $percent,
18✔
173
                $steps,
18✔
174
                $bar,
18✔
175
                $this->finishIcon,
18✔
176
                '%jbzoo_time_elapsed:8s%<blue>/</blue>%jbzoo_time_estimated% | %jbzoo_memory_current%',
18✔
177
            ]);
18✔
178
        }
179

180
        $footerLine['Last Step Message'] = '%message%';
24✔
181

182
        return \implode("\n", $progressBarLines) . "\n" . CliRender::list($footerLine) . "\n";
24✔
183
    }
184

185
    private function init(): bool
186
    {
187
        $progresBarLevel = $this->getNestedLevel();
138✔
188
        $levelPostfix    = $progresBarLevel > 1 ? " Level: {$progresBarLevel}." : '';
138✔
189

190
        if ($this->max <= 0) {
138✔
191
            if (isStrEmpty($this->title)) {
18✔
192
                $this->outputMode->_("Number of items is 0 or less.{$levelPostfix}");
×
193
            } else {
194
                $this->outputMode->_("{$this->title}. Number of items is 0 or less.{$levelPostfix}");
18✔
195
            }
196

197
            return false;
18✔
198
        }
199

200
        $this->progressBar = $this->createProgressBar();
120✔
201
        if ($this->progressBar === null) {
120✔
202
            if (isStrEmpty($this->title)) {
96✔
203
                $this->outputMode->_("Number of steps: <blue>{$this->max}</blue>.{$levelPostfix}");
×
204
            } else {
205
                $this->outputMode->_(
96✔
206
                    "Working on \"<blue>{$this->title}</blue>\". " .
96✔
207
                    "Number of steps: <blue>{$this->max}</blue>.{$levelPostfix}",
96✔
208
                );
96✔
209
            }
210
        }
211

212
        return true;
120✔
213
    }
214

215
    private function setStep(float|int|string $stepIndex, int $currentIndex): void
216
    {
217
        if ($this->progressBar !== null) {
120✔
218
            $this->progressBar->setProgress($currentIndex);
24✔
219
            $this->progressBar->setMessage($stepIndex . ': ', 'jbzoo_current_index');
24✔
220
        }
221
    }
222

223
    private function handleOneStep(mixed $stepValue, float|int|string $stepIndex, int $currentIndex): array
224
    {
225
        if ($this->callback === null) {
120✔
226
            throw new Exception('Callback function is not defined');
×
227
        }
228

229
        $exceptionMessage = null;
120✔
230
        $prefixMessage    = $stepIndex === $currentIndex ? $currentIndex : "{$stepIndex}/{$currentIndex}";
120✔
231
        $callbackResults  = [];
120✔
232

233
        $this->outputMode->catchModeStart();
120✔
234

235
        // Executing callback
236
        try {
237
            $callbackResults = (array)($this->callback)($stepValue, $stepIndex, $currentIndex);
120✔
238
        } catch (ExceptionBreak $exception) {
36✔
239
            $callbackResults[] = '<yellow-bl>' . ExceptionBreak::MESSAGE . '</yellow-bl> ' . $exception->getMessage();
6✔
240
        } catch (\Exception $exception) {
30✔
241
            if ($this->throwBatchException) {
30✔
242
                $errorMessage      = '<error>Exception:</error> ' . $exception->getMessage();
18✔
243
                $callbackResults[] = $errorMessage;
18✔
244
                $exceptionMessage  = " * ({$prefixMessage}): {$errorMessage}";
18✔
245
            } else {
246
                throw $exception;
12✔
247
            }
248
        }
249

250
        // Collect eventual output
251
        $cathedMessages = $this->outputMode->catchModeFinish();
114✔
252
        if (\count($cathedMessages) > 0) {
114✔
253
            $callbackResults = \array_merge($callbackResults, $cathedMessages);
6✔
254
        }
255

256
        // Handle status messages
257
        $stepResult = '';
114✔
258
        if (\count($callbackResults) > 0) {
114✔
259
            $stepResult = \str_replace(["\n", "\r", "\t"], ' ', \implode('; ', $callbackResults));
90✔
260

261
            if ($this->progressBar !== null) {
90✔
262
                if (\strlen(\strip_tags($stepResult)) > self::MAX_LINE_LENGTH) {
12✔
263
                    $stepResult = Str::limitChars(\strip_tags($stepResult), self::MAX_LINE_LENGTH);
×
264
                }
265

266
                $this->progressBar->setMessage($stepResult);
12✔
267
            } else {
268
                $this->outputMode->_(" * ({$prefixMessage}): {$stepResult}");
82✔
269
            }
270
        } elseif ($this->progressBar === null) {
54✔
271
            $this->outputMode->_(" * ({$prefixMessage}): n/a");
30✔
272
        }
273

274
        return [$stepResult, $exceptionMessage];
114✔
275
    }
276

277
    private function createProgressBar(): ?SymfonyProgressBar
278
    {
279
        if ($this->outputMode->isProgressBarDisabled()) {
120✔
280
            return null;
96✔
281
        }
282

283
        $this->configureProgressBar($this->isOptimizeMode());
24✔
284

285
        $progressBar = new SymfonyProgressBar($this->output, $this->max);
24✔
286

287
        $progressBar->setBarCharacter('<green>•</green>');
24✔
288
        $progressBar->setEmptyBarCharacter('<yellow>_</yellow>');
24✔
289
        $progressBar->setProgressCharacter($this->progressIcon);
24✔
290
        $progressBar->setBarWidth($this->output->isVerbose() ? 70 : 40);
24✔
291
        $progressBar->setFormat($this->buildTemplate());
24✔
292

293
        $progressBar->setMessage('n/a');
24✔
294
        $progressBar->setMessage('0', 'jbzoo_caught_exceptions');
24✔
295
        $progressBar->setProgress(0);
24✔
296
        $progressBar->setOverwrite(true);
24✔
297

298
        if (!$this->isOptimizeMode()) {
24✔
299
            $progressBar->setRedrawFrequency(1);
6✔
300
            $progressBar->minSecondsBetweenRedraws(0.5);
6✔
301
            $progressBar->maxSecondsBetweenRedraws(1.5);
6✔
302
        }
303

304
        return $progressBar;
24✔
305
    }
306

307
    private function isOptimizeMode(): bool
308
    {
309
        return $this->outputMode->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL;
120✔
310
    }
311

312
    private static function sliceProfileStats(array $arrayOfItems): array
313
    {
314
        if (\count($arrayOfItems) > self::LIMIT_ITEMT_FOR_PROFILING) {
12✔
315
            $arrayOfItems = \array_slice(
×
UNCOV
316
                $arrayOfItems,
×
UNCOV
317
                -self::LIMIT_ITEMT_FOR_PROFILING,
×
UNCOV
318
                self::LIMIT_ITEMT_FOR_PROFILING,
×
UNCOV
319
            );
×
320
        }
321

322
        return $arrayOfItems;
12✔
323
    }
324

325
    private static function showListOfExceptions(array $exceptionMessages): void
326
    {
327
        if (\count($exceptionMessages) > 0) {
108✔
328
            $listOfErrors = \implode("\n", $exceptionMessages) . "\n";
18✔
329
            $listOfErrors = \str_replace('<error>Exception:</error> ', '', $listOfErrors);
18✔
330

331
            throw new Exception("\n Error list:\n" . $listOfErrors);
18✔
332
        }
333
    }
334
}
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