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

eliashaeussler / cache-warmup / 8011103897

22 Feb 2024 09:17PM UTC coverage: 76.466% (-21.2%) from 97.674%
8011103897

Pull #333

github

web-flow
Merge ca3e396ce into 0442a6074
Pull Request #333: [FEATURE] Introduce Config API

44 of 355 new or added lines in 15 files covered. (12.39%)

6 existing lines in 1 file now uncovered.

1095 of 1432 relevant lines covered (76.47%)

6.73 hits per line

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

94.81
/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(
31✔
72
        private readonly ClientInterface $client = new Client(),
73
    ) {
74
        parent::__construct('cache-warmup');
31✔
75
        $this->timeTracker = new Time\TimeTracker();
31✔
76
    }
77

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

96
        $this->setDescription('Warms up caches of URLs provided by a given set of XML sitemaps.');
31✔
97
        $this->setHelp(<<<HELP
31✔
98
This command can be used to warm up website caches.
31✔
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>.
31✔
169

170
<info>Crawler options</info>
171
<info>===============</info>
172
For crawlers implementing <comment>{$configurableCrawlerInterface}</comment>,
31✔
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>
31✔
183

184
The following strategies are currently available:
185

186
   * <comment>{$sortByChangeFrequencyStrategy}</comment>
31✔
187
   * <comment>{$sortByLastModificationDateStrategy}</comment>
31✔
188
   * <comment>{$sortByPriorityStrategy}</comment>
31✔
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>,
31✔
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)
31✔
216
   * <comment>{$jsonFormatter}</comment>
31✔
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}
31✔
234

235
HELP);
31✔
236

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

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

344
        if (null !== $configFile) {
31✔
NEW
345
            array_unshift($configAdapters, $this->loadConfigFromFile($configFile));
×
346
        }
347

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

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

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

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

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

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

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

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

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

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

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

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

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

423
        // Show header
424
        if ($this->formatter->isVerbose()) {
26✔
425
            $this->printHeader();
23✔
426
        }
427

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

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

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

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

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

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

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

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

471
        return self::SUCCESSFUL;
8✔
472
    }
473

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

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

482
        $result = $cacheWarmer->run();
23✔
483

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

492
        return $result;
23✔
493
    }
494

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

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

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

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

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

530
        if ($this->formatter->isVerbose()) {
23✔
531
            $this->io->writeln('<info>Done</info>');
20✔
532
        }
533

534
        return $cacheWarmer;
23✔
535
    }
536

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

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

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

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

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

578
        return $crawler;
24✔
579
    }
580

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

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

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

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

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

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

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