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

eliashaeussler / cache-warmup / 8036670583

25 Feb 2024 08:29AM UTC coverage: 77.824% (-19.9%) from 97.674%
8036670583

Pull #333

github

web-flow
Merge 6a385c811 into b43777e8b
Pull Request #333: [FEATURE] Introduce Config API

79 of 375 new or added lines in 17 files covered. (21.07%)

3 existing lines in 1 file now uncovered.

1130 of 1452 relevant lines covered (77.82%)

7.84 hits per line

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

97.93
/src/Command/CacheWarmupCommand.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/cache-warmup".
7
 *
8
 * Copyright (C) 2020-2024 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\CacheWarmup\Command;
25

26
use EliasHaeussler\CacheWarmup\CacheWarmer;
27
use EliasHaeussler\CacheWarmup\Config;
28
use EliasHaeussler\CacheWarmup\Crawler;
29
use EliasHaeussler\CacheWarmup\Exception;
30
use EliasHaeussler\CacheWarmup\Formatter;
31
use EliasHaeussler\CacheWarmup\Helper;
32
use EliasHaeussler\CacheWarmup\Log;
33
use EliasHaeussler\CacheWarmup\Result;
34
use EliasHaeussler\CacheWarmup\Sitemap;
35
use EliasHaeussler\CacheWarmup\Time;
36
use GuzzleHttp\Client;
37
use GuzzleHttp\ClientInterface;
38
use Psr\Log\LogLevel;
39
use Symfony\Component\Console;
40

41
use function array_map;
42
use function array_unshift;
43
use function count;
44
use function implode;
45
use function in_array;
46
use function is_string;
47
use function json_encode;
48
use function pathinfo;
49
use function sleep;
50
use function sprintf;
51
use function strtolower;
52

53
/**
54
 * CacheWarmupCommand.
55
 *
56
 * @author Elias Häußler <elias@heussler.dev>
57
 * @license GPL-3.0-or-later
58
 */
59
final class CacheWarmupCommand extends Console\Command\Command
60
{
61
    private const SUCCESSFUL = 0;
62
    private const FAILED = 1;
63

64
    private readonly Time\TimeTracker $timeTracker;
65
    private Config\CacheWarmupConfig $config;
66
    private Console\Style\SymfonyStyle $io;
67
    private Formatter\Formatter $formatter;
68
    private Crawler\CrawlerFactory $crawlerFactory;
69
    private bool $firstRun = true;
70

71
    public function __construct(
39✔
72
        private readonly ClientInterface $client = new Client(),
73
    ) {
74
        parent::__construct('cache-warmup');
39✔
75
        $this->timeTracker = new Time\TimeTracker();
39✔
76
    }
77

78
    protected function configure(): void
39✔
79
    {
80
        $crawlerInterface = Crawler\CrawlerInterface::class;
39✔
81
        $configurableCrawlerInterface = Crawler\ConfigurableCrawlerInterface::class;
39✔
82
        $stoppableCrawlerInterface = Crawler\StoppableCrawlerInterface::class;
39✔
83
        $textFormatter = Formatter\TextFormatter::getType();
39✔
84
        $jsonFormatter = Formatter\JsonFormatter::getType();
39✔
85
        $sortByChangeFrequencyStrategy = Crawler\Strategy\SortByChangeFrequencyStrategy::getName();
39✔
86
        $sortByLastModificationDateStrategy = Crawler\Strategy\SortByLastModificationDateStrategy::getName();
39✔
87
        $sortByPriorityStrategy = Crawler\Strategy\SortByPriorityStrategy::getName();
39✔
88
        $logLevels = implode(
39✔
89
            PHP_EOL,
39✔
90
            array_map(
39✔
91
                static fn (string $logLevel): string => '   * <comment>'.strtolower($logLevel).'</comment>',
39✔
92
                Log\LogLevel::getAll(),
39✔
93
            ),
39✔
94
        );
39✔
95

96
        $this->setDescription('Warms up caches of URLs provided by a given set of XML sitemaps.');
39✔
97
        $this->setHelp(<<<HELP
39✔
98
This command can be used to warm up website caches.
39✔
99
It requires a set of XML sitemaps offering several URLs which will be crawled.
100

101
<info>Sitemaps</info>
102
<info>========</info>
103
The list of sitemaps to be crawled can be defined as command argument:
104

105
   * <comment>%command.full_name% https://www.example.com/sitemap.xml</comment> (URL)
106
   * <comment>%command.full_name% /var/www/html/sitemap.xml</comment> (local file)
107

108
You are free to crawl as many sitemaps as you want.
109
Alternatively, sitemaps can be specified from user input when application is in interactive mode.
110

111
<info>Custom URLs</info>
112
<info>===========</info>
113
In addition or as an alternative to sitemaps, it's also possible to provide a given URL set using the <comment>--urls</comment> option:
114

115
   <comment>%command.full_name% -u https://www.example.com/foo -u https://www.example.com/baz</comment>
116

117
<info>Config file</info>
118
<info>===========</info>
119
All command parameters can be configured in an external config file.
120
Use the <comment>--config</comment> option to specify the config file:
121

122
   <comment>%command.full_name% -c cache-warmup.php</comment>
123

124
The following formats are currently supported:
125

126
   * <comment>json</comment>
127
   * <comment>php</comment>
128
   * <comment>yaml/yml</comment>
129

130
<info>Exclude patterns</info>
131
<info>================</info>
132
You can specify exclude patterns to be applied on URLs in order to ignore them from cache warming.
133
Use the <comment>--exclude</comment> (or <comment>-e</comment>) option to specify one or more patterns:
134

135
   <comment>%command.full_name% -e "*no_cache=1*" -e "*no_warming=1*"</comment>
136

137
You can also specify regular expressions as exclude patterns.
138
Note that each expression must start and end with a <comment>#</comment> symbol:
139

140
   <comment>%command.full_name% -e "#(no_cache|no_warming)=1#"</comment>
141

142
<info>Progress bar</info>
143
<info>============</info>
144
You can track the cache warmup progress by using the <comment>--progress</comment> option:
145

146
   <comment>%command.full_name% --progress</comment>
147

148
This shows a compact progress bar, including current warmup failures.
149
For a more verbose output, add the <comment>--verbose</comment> option:
150

151
   <comment>%command.full_name% --progress --verbose</comment>
152

153
<info>URL limit</info>
154
<info>=========</info>
155
The number of URLs to be crawled can be limited using the <comment>--limit</comment> option:
156

157
   <comment>%command.full_name% --limit 50</comment>
158

159
<info>Crawler</info>
160
<info>=======</info>
161
By default, cache warmup will be done using concurrent HEAD requests.
162
This behavior can be overridden in case a special crawler is defined using the <comment>--crawler</comment> option:
163

164
   <comment>%command.full_name% --crawler "Vendor\Crawler\MyCrawler"</comment>
165

166
It's up to you to ensure the given crawler class is available and fully loaded.
167
This can best be achieved by registering the class with Composer autoloader.
168
Also make sure the crawler implements <comment>{$crawlerInterface}</comment>.
39✔
169

170
<info>Crawler options</info>
171
<info>===============</info>
172
For crawlers implementing <comment>{$configurableCrawlerInterface}</comment>,
39✔
173
it is possible to pass a JSON-encoded array of crawler options by using the <comment>--crawler-options</comment> option:
174

175
   <comment>%command.full_name% --crawler-options '{"concurrency": 3}'</comment>
176

177
<info>Crawling strategy</info>
178
<info>=================</info>
179
URLs can be crawled using a specific crawling strategy, e.g. by sorting them by a specific property.
180
For this, use the <comment>--strategy</comment> option together with a predefined value:
181

182
   <comment>%command.full_name% --strategy {$sortByPriorityStrategy}</comment>
39✔
183

184
The following strategies are currently available:
185

186
   * <comment>{$sortByChangeFrequencyStrategy}</comment>
39✔
187
   * <comment>{$sortByLastModificationDateStrategy}</comment>
39✔
188
   * <comment>{$sortByPriorityStrategy}</comment>
39✔
189

190
<info>Allow failures</info>
191
<info>==============</info>
192
If a sitemap cannot be parsed or a URL fails to be crawled, this command normally exits
193
with a non-zero exit code. This is not always the desired behavior. Therefore, you can change
194
this behavior by using the <comment>--allow-failures</comment> option:
195

196
   <comment>%command.full_name% --allow-failures</comment>
197

198
<info>Stop on failure</info>
199
<info>===============</info>
200
For crawlers implementing <comment>{$stoppableCrawlerInterface}</comment>,
39✔
201
you can also configure the crawler to stop on failure. The <comment>--stop-on-failure</comment> option
202
exists for this case:
203

204
   <comment>%command.full_name% --stop-on-failure</comment>
205

206
<info>Format output</info>
207
<info>=============</info>
208
By default, all user-oriented output is printed as plain text to the console.
209
However, you can use other formatters by using the <comment>--format</comment> option:
210

211
   <comment>%command.full_name% --format json</comment>
212

213
Currently, the following formatters are available:
214

215
   * <comment>{$textFormatter}</comment> (default)
39✔
216
   * <comment>{$jsonFormatter}</comment>
39✔
217

218
<info>Logging</info>
219
<info>=======</info>
220
You can log the crawling results of each crawled URL to an external log file.
221
For this, the <comment>--log-file</comment> option exists:
222

223
   <comment>%command.full_name% --log-file crawling-errors.log</comment>
224

225
When logging is enabled, by default only crawling failures are logged.
226
You can increase the log level to log successful crawlings as well:
227

228
   * <comment>%command.full_name% --log-level error</comment> (default)
229
   * <comment>%command.full_name% --log-level info</comment>
230

231
The following log levels are currently available:
232

233
{$logLevels}
39✔
234

235
HELP);
39✔
236

237
        $this->addArgument(
39✔
238
            'sitemaps',
39✔
239
            Console\Input\InputArgument::OPTIONAL | Console\Input\InputArgument::IS_ARRAY,
39✔
240
            'URLs or local filenames of XML sitemaps to be used for cache warming',
39✔
241
        );
39✔
242
        $this->addOption(
39✔
243
            'urls',
39✔
244
            'u',
39✔
245
            Console\Input\InputOption::VALUE_REQUIRED | Console\Input\InputOption::VALUE_IS_ARRAY,
39✔
246
            'Custom additional URLs to be used for cache warming',
39✔
247
        );
39✔
248
        $this->addOption(
39✔
249
            'config',
39✔
250
            null,
39✔
251
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
252
            'Path to configuration file',
39✔
253
        );
39✔
254
        $this->addOption(
39✔
255
            'exclude',
39✔
256
            'e',
39✔
257
            Console\Input\InputOption::VALUE_REQUIRED | Console\Input\InputOption::VALUE_IS_ARRAY,
39✔
258
            'Patterns for URLs to be excluded from cache warming',
39✔
259
        );
39✔
260
        $this->addOption(
39✔
261
            'limit',
39✔
262
            'l',
39✔
263
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
264
            'Limit the number of URLs to be processed',
39✔
265
            0,
39✔
266
        );
39✔
267
        $this->addOption(
39✔
268
            'progress',
39✔
269
            'p',
39✔
270
            Console\Input\InputOption::VALUE_NONE,
39✔
271
            'Show progress bar during cache warmup',
39✔
272
        );
39✔
273
        $this->addOption(
39✔
274
            'crawler',
39✔
275
            'c',
39✔
276
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
277
            'FQCN of the crawler to be used for cache warming',
39✔
278
        );
39✔
279
        $this->addOption(
39✔
280
            'crawler-options',
39✔
281
            'o',
39✔
282
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
283
            'Additional config for configurable crawlers',
39✔
284
        );
39✔
285
        $this->addOption(
39✔
286
            'strategy',
39✔
287
            's',
39✔
288
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
289
            'Optional strategy to prepare URLs before crawling them',
39✔
290
        );
39✔
291
        $this->addOption(
39✔
292
            'allow-failures',
39✔
293
            null,
39✔
294
            Console\Input\InputOption::VALUE_NONE,
39✔
295
            'Allow failures during URL crawling and exit with zero',
39✔
296
        );
39✔
297
        $this->addOption(
39✔
298
            'stop-on-failure',
39✔
299
            null,
39✔
300
            Console\Input\InputOption::VALUE_NONE,
39✔
301
            'Cancel further cache warmup requests on failure',
39✔
302
        );
39✔
303
        $this->addOption(
39✔
304
            'format',
39✔
305
            'f',
39✔
306
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
307
            'Formatter used to print the cache warmup result',
39✔
308
            Formatter\TextFormatter::getType(),
39✔
309
        );
39✔
310
        $this->addOption(
39✔
311
            'log-file',
39✔
312
            null,
39✔
313
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
314
            'File where to log crawling results',
39✔
315
        );
39✔
316
        $this->addOption(
39✔
317
            'log-level',
39✔
318
            null,
39✔
319
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
320
            'Log level used to determine which crawling results to log (see help for more information)',
39✔
321
            LogLevel::ERROR,
39✔
322
        );
39✔
323
        $this->addOption(
39✔
324
            'repeat-after',
39✔
325
            null,
39✔
326
            Console\Input\InputOption::VALUE_REQUIRED,
39✔
327
            'Run cache warmup in endless loop and repeat x seconds after each run',
39✔
328
            0,
39✔
329
        );
39✔
330
    }
331

332
    /**
333
     * @throws Exception\UnsupportedConfigFileException
334
     * @throws Exception\UnsupportedFormatterException
335
     * @throws Exception\UnsupportedLogLevelException
336
     */
337
    protected function initialize(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): void
39✔
338
    {
339
        $configFile = $input->getOption('config');
39✔
340
        $configAdapters = [
39✔
341
            new Config\Adapter\ConsoleInputConfigAdapter($input),
39✔
342
            new Config\Adapter\EnvironmentVariablesConfigAdapter(),
39✔
343
        ];
39✔
344

345
        if (null !== $configFile) {
39✔
346
            array_unshift($configAdapters, $this->loadConfigFromFile($configFile));
8✔
347
        }
348

349
        $this->config = (new Config\Adapter\CompositeConfigAdapter($configAdapters))->get();
38✔
350
        $this->io = new Console\Style\SymfonyStyle($input, $output);
35✔
351
        $this->formatter = (new Formatter\FormatterFactory($this->io))->get($this->config->getFormat());
35✔
352

353
        $logFile = $this->config->getLogFile();
34✔
354
        $logLevel = $this->config->getLogLevel();
34✔
355
        $stopOnFailure = $this->config->shouldStopOnFailure();
34✔
356
        $logger = null;
34✔
357

358
        // Create logger
359
        if (is_string($logFile)) {
34✔
360
            $logger = new Log\FileLogger($logFile);
1✔
361
        }
362

363
        // Validate log level
364
        if (!in_array($logLevel, Log\LogLevel::getAll(), true)) {
34✔
365
            throw Exception\UnsupportedLogLevelException::create($logLevel);
1✔
366
        }
367

368
        // Use error output or disable output if formatter is non-verbose
369
        if (!$this->formatter->isVerbose()) {
33✔
370
            if ($output instanceof Console\Output\ConsoleOutputInterface) {
3✔
371
                $output = $output->getErrorOutput();
1✔
372

373
                $this->config->enableProgressBar();
1✔
374
            } else {
375
                $output = new Console\Output\NullOutput();
2✔
376
            }
377
        }
378

379
        $this->crawlerFactory = new Crawler\CrawlerFactory($output, $logger, $logLevel, $stopOnFailure);
33✔
380
    }
381

382
    protected function interact(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): void
32✔
383
    {
384
        // Early return if sitemaps or URLs are already specified
385
        if ([] !== $this->config->getSitemaps() || [] !== $this->config->getUrls()) {
32✔
386
            return;
30✔
387
        }
388

389
        // Get sitemaps from interactive user input
390
        $sitemaps = [];
2✔
391
        $helper = $this->getHelper('question');
2✔
392
        do {
393
            $question = new Console\Question\Question('Please enter the URL of a XML sitemap: ');
2✔
394
            $question->setValidator($this->validateSitemap(...));
2✔
395
            $sitemap = $helper->ask($input, $output, $question);
2✔
396
            if ($sitemap instanceof Sitemap\Sitemap) {
2✔
397
                $sitemaps[] = $sitemap;
1✔
398
                $output->writeln(sprintf('<info>Sitemap added: %s</info>', $sitemap));
1✔
399
            }
400
        } while ($sitemap instanceof Sitemap\Sitemap);
2✔
401

402
        // Throw exception if no sitemaps were added
403
        if ([] === $sitemaps) {
2✔
404
            throw new Console\Exception\RuntimeException('You must enter at least one sitemap URL.', 1604258903);
1✔
405
        }
406

407
        $this->config->setSitemaps($sitemaps);
1✔
408
    }
409

410
    /**
411
     * @throws Exception\UnsupportedConfigFileException
412
     */
413
    protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int
32✔
414
    {
415
        $sitemaps = $this->config->getSitemaps();
32✔
416
        $urls = $this->config->getUrls();
32✔
417
        $repeatAfter = $this->config->getRepeatAfter();
32✔
418

419
        // Throw exception if neither sitemaps nor URLs are defined
420
        if ([] === $sitemaps && [] === $urls) {
32✔
421
            throw new Console\Exception\RuntimeException('Neither sitemaps nor URLs are defined.', 1604261236);
1✔
422
        }
423

424
        // Show header
425
        if ($this->formatter->isVerbose()) {
31✔
426
            $this->printHeader();
28✔
427
        }
428

429
        // Show warning on endless runs
430
        if ($this->firstRun && $repeatAfter > 0) {
31✔
431
            $this->showEndlessModeWarning($repeatAfter);
1✔
432
            $this->firstRun = false;
1✔
433
        }
434

435
        // Initialize components
436
        $crawler = $this->initializeCrawler();
31✔
437
        $cacheWarmer = $this->timeTracker->track(fn () => $this->initializeCacheWarmer($crawler));
31✔
438

439
        // Print formatted parser result
440
        $this->formatter->formatParserResult(
30✔
441
            new Result\ParserResult($cacheWarmer->getSitemaps(), $cacheWarmer->getUrls()),
30✔
442
            new Result\ParserResult($cacheWarmer->getFailedSitemaps()),
30✔
443
            new Result\ParserResult($cacheWarmer->getExcludedSitemaps(), $cacheWarmer->getExcludedUrls()),
30✔
444
            $this->timeTracker->getLastDuration(),
30✔
445
        );
30✔
446

447
        // Start crawling
448
        $result = $this->timeTracker->track(
30✔
449
            fn () => $this->runCacheWarmup(
30✔
450
                $cacheWarmer,
30✔
451
                $crawler instanceof Crawler\VerboseCrawlerInterface,
30✔
452
            ),
30✔
453
        );
30✔
454

455
        // Print formatted cache warmup result
456
        $this->formatter->formatCacheWarmupResult($result, $this->timeTracker->getLastDuration());
30✔
457

458
        // Early return if parsing or crawling failed
459
        if (!$this->config->areFailuresAllowed()
30✔
460
            && ([] !== $cacheWarmer->getFailedSitemaps() || !$result->isSuccessful())
30✔
461
        ) {
462
            return self::FAILED;
21✔
463
        }
464

465
        // Repeat on endless mode
466
        if ($repeatAfter > 0) {
10✔
467
            sleep($repeatAfter);
1✔
468

469
            return $this->execute($input, $output);
1✔
470
        }
471

472
        return self::SUCCESSFUL;
9✔
473
    }
474

475
    private function runCacheWarmup(CacheWarmer $cacheWarmer, bool $isVerboseCrawler): Result\CacheWarmupResult
30✔
476
    {
477
        $urlCount = count($cacheWarmer->getUrls());
30✔
478

479
        if ($this->formatter->isVerbose()) {
30✔
480
            $this->io->write(sprintf('Crawling URL%s... ', 1 === $urlCount ? '' : 's'), $isVerboseCrawler);
27✔
481
        }
482

483
        $result = $cacheWarmer->run();
30✔
484

485
        if ($this->formatter->isVerbose() && !$isVerboseCrawler) {
30✔
486
            if ($result->wasCancelled()) {
17✔
487
                $this->io->writeln('<comment>Cancelled</comment>');
1✔
488
            } else {
489
                $this->io->writeln('<info>Done</info>');
16✔
490
            }
491
        }
492

493
        return $result;
30✔
494
    }
495

496
    private function initializeCacheWarmer(Crawler\CrawlerInterface $crawler): CacheWarmer
31✔
497
    {
498
        if ($this->formatter->isVerbose()) {
31✔
499
            $this->io->write('Parsing sitemaps... ');
28✔
500
        }
501

502
        // Initialize crawling strategy
503
        $strategy = $this->config->getStrategy();
31✔
504
        if (is_string($strategy)) {
31✔
NEW
UNCOV
505
            $strategy = match ($strategy) {
×
NEW
UNCOV
506
                Crawler\Strategy\SortByChangeFrequencyStrategy::getName() => new Crawler\Strategy\SortByChangeFrequencyStrategy(),
×
NEW
UNCOV
507
                Crawler\Strategy\SortByLastModificationDateStrategy::getName() => new Crawler\Strategy\SortByLastModificationDateStrategy(),
×
NEW
508
                Crawler\Strategy\SortByPriorityStrategy::getName() => new Crawler\Strategy\SortByPriorityStrategy(),
×
NEW
509
                default => throw new Console\Exception\RuntimeException('The given crawling strategy is invalid.', 1677618007),
×
NEW
510
            };
×
511
        }
512

513
        // Initialize cache warmer
514
        $cacheWarmer = new CacheWarmer(
31✔
515
            $this->config->getLimit(),
31✔
516
            $this->client,
31✔
517
            $crawler,
31✔
518
            $strategy,
31✔
519
            !$this->config->areFailuresAllowed(),
31✔
520
            $this->config->getExcludePatterns(),
31✔
521
        );
31✔
522

523
        // Add and parse XML sitemaps
524
        $cacheWarmer->addSitemaps($this->config->getSitemaps());
31✔
525

526
        // Add URLs
527
        foreach ($this->config->getUrls() as $url) {
30✔
528
            $cacheWarmer->addUrl($url);
1✔
529
        }
530

531
        if ($this->formatter->isVerbose()) {
30✔
532
            $this->io->writeln('<info>Done</info>');
27✔
533
        }
534

535
        return $cacheWarmer;
30✔
536
    }
537

538
    private function initializeCrawler(): Crawler\CrawlerInterface
31✔
539
    {
540
        $crawler = $this->config->getCrawler();
31✔
541
        $crawlerOptions = $this->crawlerFactory->parseCrawlerOptions($this->config->getCrawlerOptions());
31✔
542
        $stopOnFailure = $this->config->shouldStopOnFailure();
31✔
543

544
        // Select default crawler
545
        if (null === $crawler) {
31✔
546
            $crawler = $this->config->isProgressBarEnabled()
26✔
547
                ? Crawler\OutputtingCrawler::class
10✔
548
                : Crawler\ConcurrentCrawler::class
16✔
549
            ;
26✔
550
        }
551

552
        // Initialize crawler
553
        if (is_string($crawler)) {
31✔
554
            $crawler = $this->crawlerFactory->get($crawler, $crawlerOptions);
31✔
555
        }
556

557
        // Print crawler options
558
        if ($crawler instanceof Crawler\ConfigurableCrawlerInterface) {
31✔
559
            if ($this->formatter->isVerbose() && $this->io->isVerbose() && [] !== $crawlerOptions) {
26✔
560
                $this->io->section('Using custom crawler options:');
2✔
561
                $this->io->writeln((string) json_encode($crawlerOptions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
2✔
562
                $this->io->newLine();
2✔
563
            }
564
        } elseif ([] !== $crawlerOptions) {
5✔
565
            $this->formatter->logMessage(
1✔
566
                'You passed crawler options for a non-configurable crawler.',
1✔
567
                Formatter\MessageSeverity::Warning,
1✔
568
            );
1✔
569
        }
570

571
        // Show notice on unsupported stoppable crawler feature
572
        if ($stopOnFailure && !($crawler instanceof Crawler\StoppableCrawlerInterface)) {
31✔
573
            $this->formatter->logMessage(
1✔
574
                'You passed --stop-on-failure to a non-stoppable crawler.',
1✔
575
                Formatter\MessageSeverity::Warning,
1✔
576
            );
1✔
577
        }
578

579
        return $crawler;
31✔
580
    }
581

582
    /**
583
     * @throws Exception\UnsupportedConfigFileException
584
     */
585
    private function loadConfigFromFile(string $configFile): Config\Adapter\ConfigAdapter
8✔
586
    {
587
        $configFile = Helper\FilesystemHelper::resolveRelativePath($configFile);
8✔
588
        $extension = strtolower(pathinfo($configFile, PATHINFO_EXTENSION));
8✔
589

590
        return match ($extension) {
8✔
591
            'php' => new Config\Adapter\PhpConfigAdapter($configFile),
8✔
592
            'json', 'yaml', 'yml' => new Config\Adapter\FileConfigAdapter($configFile),
8✔
593
            default => throw Exception\UnsupportedConfigFileException::create($configFile),
8✔
594
        };
8✔
595
    }
596

597
    private function showEndlessModeWarning(int $interval): void
1✔
598
    {
599
        $this->formatter->logMessage(
1✔
600
            sprintf(
1✔
601
                'Command is scheduled to run forever. It will be repeated %d second%s after each run.',
1✔
602
                $interval,
1✔
603
                1 === $interval ? '' : 's',
1✔
604
            ),
1✔
605
            Formatter\MessageSeverity::Warning,
1✔
606
        );
1✔
607
    }
608

609
    private function validateSitemap(?string $input): ?Sitemap\Sitemap
2✔
610
    {
611
        if (null === $input) {
2✔
612
            return null;
2✔
613
        }
614

615
        return Sitemap\Sitemap::createFromString($input);
1✔
616
    }
617

618
    private function printHeader(): void
28✔
619
    {
620
        $currentVersion = Helper\VersionHelper::getCurrentVersion();
28✔
621

622
        $this->io->writeln(
28✔
623
            sprintf(
28✔
624
                'Running <info>cache warmup</info>%s by Elias Häußler and contributors.',
28✔
625
                null !== $currentVersion ? ' <comment>'.$currentVersion.'</comment>' : '',
28✔
626
            ),
28✔
627
        );
28✔
628
    }
629
}
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