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

JBZoo / Cli / 5840386379

pending completion
5840386379

push

github

web-flow
Predefined output formats - ELK, cron. New demo and docs. (#15)

745 of 745 new or added lines in 16 files covered. (100.0%)

899 of 1078 relevant lines covered (83.4%)

136.5 hits per line

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

92.62
/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 OutputInterface     $output;
31
    private ?SymfonyProgressBar $progressBar = null;
32

33
    private string $finishIcon;
34
    private string $progressIcon;
35

36
    public function __construct(AbstractOutputMode $outputMode)
37
    {
38
        parent::__construct($outputMode);
92✔
39

40
        $this->output = $outputMode->getOutput();
92✔
41

42
        $this->progressIcon = Icons::getRandomIcon(Icons::GROUP_PROGRESS, $this->output->isDecorated());
92✔
43
        $this->finishIcon   = Icons::getRandomIcon(Icons::GROUP_FINISH, $this->output->isDecorated());
92✔
44
    }
45

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

55
        $exceptionMessages = [];
80✔
56
        $isSkipped         = false;
80✔
57

58
        $currentIndex = 0;
80✔
59

60
        if ($this->callbackOnStart !== null) {
80✔
61
            \call_user_func($this->callbackOnStart, $this);
×
62
        }
63

64
        $isOptimizeMode = $this->isOptimizeMode();
80✔
65

66
        foreach ($this->list as $stepIndex => $stepValue) {
80✔
67
            $this->setStep($stepIndex, $currentIndex);
80✔
68

69
            $startTime   = 0;
80✔
70
            $startMemory = 0;
80✔
71
            if (!$isOptimizeMode) {
80✔
72
                $startTime   = \microtime(true);
8✔
73
                $startMemory = \memory_get_usage(false);
8✔
74
            }
75

76
            [$stepResult, $exceptionMessage] = $this->handleOneStep($stepValue, $stepIndex, $currentIndex);
80✔
77

78
            if (!$isOptimizeMode) {
76✔
79
                $this->stepMemoryDiff[] = \memory_get_usage(false) - $startMemory;
8✔
80
                $this->stepTimers[]     = \microtime(true) - $startTime;
8✔
81
            }
82

83
            $exceptionMessages = \array_merge($exceptionMessages, (array)$exceptionMessage);
76✔
84

85
            if ($this->progressBar !== null) {
76✔
86
                $errorNumbers = \count($exceptionMessages);
16✔
87
                $errMessage   = $errorNumbers > 0 ? "<red-bl>{$errorNumbers}</red-bl>" : '0';
16✔
88
                $this->progressBar->setMessage($errMessage, 'jbzoo_caught_exceptions');
16✔
89
            }
90

91
            if (\str_contains($stepResult, ExceptionBreak::MESSAGE)) {
76✔
92
                $isSkipped = true;
8✔
93
                break;
8✔
94
            }
95

96
            $currentIndex++;
76✔
97
        }
98

99
        if ($this->progressBar !== null) {
72✔
100
            if ($isSkipped) {
16✔
101
                $this->progressBar->display();
×
102
            } else {
103
                $this->progressBar->finish();
16✔
104
            }
105
        }
106

107
        if ($this->callbackOnFinish !== null) {
72✔
108
            \call_user_func($this->callbackOnFinish, $this);
×
109
        }
110

111
        self::showListOfExceptions($exceptionMessages);
72✔
112

113
        return true;
60✔
114
    }
115

116
    protected function buildTemplate(): string
117
    {
118
        $progressBarLines = [];
16✔
119
        $footerLine       = [];
16✔
120

121
        $bar     = '[%bar%]';
16✔
122
        $percent = '%percent:2s%%';
16✔
123
        $steps   = '(%current% / %max%)';
16✔
124

125
        if (!isStrEmpty($this->title)) {
16✔
126
            $progressBarLines[] = "Progress of <blue>{$this->title}</blue>";
8✔
127
        }
128

129
        if ($this->output->isVeryVerbose()) {
16✔
130
            $progressBarLines[] = \implode(' ', [$percent, $steps, $bar, $this->finishIcon]);
4✔
131

132
            $footerLine['Time (pass/left/est/median/last)'] = \implode(' / ', [
4✔
133
                '%jbzoo_time_elapsed:9s%',
2✔
134
                '<info>%jbzoo_time_remaining:8s%</info>',
2✔
135
                '<comment>%jbzoo_time_estimated:8s%</comment>',
2✔
136
                '%jbzoo_time_step_median%',
2✔
137
                '%jbzoo_time_step_last%',
2✔
138
            ]);
2✔
139

140
            $footerLine['Mem (cur/peak/limit/leak/last)'] = \implode(' / ', [
4✔
141
                '%jbzoo_memory_current:8s%',
2✔
142
                '<comment>%jbzoo_memory_peak%</comment>',
2✔
143
                '%jbzoo_memory_limit%',
2✔
144
                '%jbzoo_memory_step_median%',
2✔
145
                '%jbzoo_memory_step_last%',
2✔
146
            ]);
2✔
147

148
            $footerLine['Caught exceptions'] = '%jbzoo_caught_exceptions%';
4✔
149
        } elseif ($this->output->isVerbose()) {
12✔
150
            $progressBarLines[] = \implode(' ', [
×
151
                $percent,
152
                $steps,
153
                $bar,
154
                $this->finishIcon,
×
155
                '%jbzoo_memory_current:8s%',
156
            ]);
157

158
            $footerLine['Time (pass/left/est)'] = \implode(' / ', [
×
159
                '%jbzoo_time_elapsed:8s%',
160
                '<info>%jbzoo_time_remaining:8s%</info>',
161
                '%jbzoo_time_estimated%',
162
            ]);
163

164
            $footerLine['Caught exceptions'] = '%jbzoo_caught_exceptions%';
×
165
        } else {
166
            $progressBarLines[] = \implode(' ', [
12✔
167
                $percent,
6✔
168
                $steps,
6✔
169
                $bar,
6✔
170
                $this->finishIcon,
12✔
171
                '%jbzoo_time_elapsed:8s%<blue>/</blue>%jbzoo_time_estimated% | %jbzoo_memory_current%',
6✔
172
            ]);
6✔
173
        }
174

175
        $footerLine['Last Step Message'] = '%message%';
16✔
176

177
        return \implode("\n", $progressBarLines) . "\n" . CliRender::list($footerLine) . "\n";
16✔
178
    }
179

180
    private function init(): bool
181
    {
182
        $progresBarLevel = $this->getNextedLevel();
92✔
183
        $levelPostfix    = $progresBarLevel > 1 ? " Level: {$progresBarLevel}." : '';
92✔
184

185
        if ($this->max <= 0) {
92✔
186
            if (isStrEmpty($this->title)) {
12✔
187
                $this->outputMode->_("Number of items is 0 or less.{$levelPostfix}");
×
188
            } else {
189
                $this->outputMode->_("{$this->title}. Number of items is 0 or less.{$levelPostfix}");
12✔
190
            }
191

192
            return false;
12✔
193
        }
194

195
        $this->progressBar = $this->createProgressBar();
80✔
196
        if ($this->progressBar === null) {
80✔
197
            if (isStrEmpty($this->title)) {
64✔
198
                $this->outputMode->_("Number of steps: <blue>{$this->max}</blue>.{$levelPostfix}");
×
199
            } else {
200
                $this->outputMode->_(
64✔
201
                    "Working on \"<blue>{$this->title}</blue>\". " .
64✔
202
                    "Number of steps: <blue>{$this->max}</blue>.{$levelPostfix}",
64✔
203
                );
32✔
204
            }
205
        }
206

207
        return true;
80✔
208
    }
209

210
    private function setStep(int|float|string $stepIndex, int $currentIndex): void
211
    {
212
        if ($this->progressBar !== null) {
80✔
213
            $this->progressBar->setProgress($currentIndex);
16✔
214
            $this->progressBar->setMessage($stepIndex . ': ', 'jbzoo_current_index');
16✔
215
        }
216
    }
217

218
    private function handleOneStep(mixed $stepValue, int|float|string $stepIndex, int $currentIndex): array
219
    {
220
        if ($this->callback === null) {
80✔
221
            throw new Exception('Callback function is not defined');
×
222
        }
223

224
        $exceptionMessage = null;
80✔
225
        $prefixMessage    = $stepIndex === $currentIndex ? $currentIndex : "{$stepIndex}/{$currentIndex}";
80✔
226
        $callbackResults  = [];
80✔
227

228
        $this->outputMode->catchModeStart();
80✔
229

230
        // Executing callback
231
        try {
232
            $callbackResults = (array)($this->callback)($stepValue, $stepIndex, $currentIndex);
80✔
233
        } catch (ExceptionBreak $exception) {
24✔
234
            $callbackResults[] = '<yellow-bl>' . ExceptionBreak::MESSAGE . '</yellow-bl> ' . $exception->getMessage();
4✔
235
        } catch (\Exception $exception) {
20✔
236
            if ($this->throwBatchException) {
20✔
237
                $errorMessage      = '<error>Exception:</error> ' . $exception->getMessage();
12✔
238
                $callbackResults[] = $errorMessage;
12✔
239
                $exceptionMessage  = " * ({$prefixMessage}): {$errorMessage}";
12✔
240
            } else {
241
                throw $exception;
8✔
242
            }
243
        }
244

245
        // Collect eventual output
246
        $cathedMessages = $this->outputMode->catchModeFinish();
76✔
247
        if (\count($cathedMessages) > 0) {
76✔
248
            $callbackResults = \array_merge($callbackResults, $cathedMessages);
4✔
249
        }
250

251
        // Handle status messages
252
        $stepResult = '';
76✔
253
        if (\count($callbackResults) > 0) {
76✔
254
            $stepResult = \str_replace(["\n", "\r", "\t"], ' ', \implode('; ', $callbackResults));
60✔
255

256
            if ($this->progressBar !== null) {
60✔
257
                if (\strlen(\strip_tags($stepResult)) > self::MAX_LINE_LENGTH) {
8✔
258
                    $stepResult = Str::limitChars(\strip_tags($stepResult), self::MAX_LINE_LENGTH);
×
259
                }
260

261
                $this->progressBar->setMessage($stepResult);
8✔
262
            } else {
263
                $this->outputMode->_(" * ({$prefixMessage}): {$stepResult}");
60✔
264
            }
265
        } elseif ($this->progressBar === null) {
36✔
266
            $this->outputMode->_(" * ({$prefixMessage}): n/a");
20✔
267
        }
268

269
        return [$stepResult, $exceptionMessage];
76✔
270
    }
271

272
    private function createProgressBar(): ?SymfonyProgressBar
273
    {
274
        if ($this->outputMode->isProgressBarDisabled()) {
80✔
275
            return null;
64✔
276
        }
277

278
        $this->configureProgressBar($this->isOptimizeMode());
16✔
279

280
        $progressBar = new SymfonyProgressBar($this->output, $this->max);
16✔
281

282
        $progressBar->setBarCharacter('<green>•</green>');
16✔
283
        $progressBar->setEmptyBarCharacter('<yellow>_</yellow>');
16✔
284
        $progressBar->setProgressCharacter($this->progressIcon);
16✔
285
        $progressBar->setBarWidth($this->output->isVerbose() ? 70 : 40);
16✔
286
        $progressBar->setFormat($this->buildTemplate());
16✔
287

288
        $progressBar->setMessage('n/a');
16✔
289
        $progressBar->setMessage('0', 'jbzoo_caught_exceptions');
16✔
290
        $progressBar->setProgress(0);
16✔
291
        $progressBar->setOverwrite(true);
16✔
292

293
        if (!$this->isOptimizeMode()) {
16✔
294
            $progressBar->setRedrawFrequency(1);
4✔
295
            $progressBar->minSecondsBetweenRedraws(0.5);
4✔
296
            $progressBar->maxSecondsBetweenRedraws(1.5);
4✔
297
        }
298

299
        return $progressBar;
16✔
300
    }
301

302
    private function isOptimizeMode(): bool
303
    {
304
        return $this->outputMode->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL;
80✔
305
    }
306

307
    private static function showListOfExceptions(array $exceptionMessages): void
308
    {
309
        if (\count($exceptionMessages) > 0) {
72✔
310
            $listOfErrors = \implode("\n", $exceptionMessages) . "\n";
12✔
311
            $listOfErrors = \str_replace('<error>Exception:</error> ', '', $listOfErrors);
12✔
312

313
            throw new Exception("\n Error list:\n" . $listOfErrors);
12✔
314
        }
315
    }
316
}
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