CacheWarmerAggregate.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpKernel\CacheWarmer;
  11. use Symfony\Component\Console\Style\SymfonyStyle;
  12. /**
  13. * Aggregates several cache warmers into a single one.
  14. *
  15. * @author Fabien Potencier <fabien@symfony.com>
  16. *
  17. * @final
  18. */
  19. class CacheWarmerAggregate implements CacheWarmerInterface
  20. {
  21. private iterable $warmers;
  22. private bool $debug;
  23. private ?string $deprecationLogsFilepath;
  24. private bool $optionalsEnabled = false;
  25. private bool $onlyOptionalsEnabled = false;
  26. /**
  27. * @param iterable<mixed, CacheWarmerInterface> $warmers
  28. */
  29. public function __construct(iterable $warmers = [], bool $debug = false, ?string $deprecationLogsFilepath = null)
  30. {
  31. $this->warmers = $warmers;
  32. $this->debug = $debug;
  33. $this->deprecationLogsFilepath = $deprecationLogsFilepath;
  34. }
  35. public function enableOptionalWarmers(): void
  36. {
  37. $this->optionalsEnabled = true;
  38. }
  39. public function enableOnlyOptionalWarmers(): void
  40. {
  41. $this->onlyOptionalsEnabled = $this->optionalsEnabled = true;
  42. }
  43. /**
  44. * @param string|null $buildDir
  45. */
  46. public function warmUp(string $cacheDir, string|SymfonyStyle|null $buildDir = null, ?SymfonyStyle $io = null): array
  47. {
  48. if ($buildDir instanceof SymfonyStyle) {
  49. trigger_deprecation('symfony/http-kernel', '6.4', 'Passing a "%s" as second argument of "%s()" is deprecated, pass it as third argument instead, after the build directory.', SymfonyStyle::class, __METHOD__);
  50. $io = $buildDir;
  51. $buildDir = null;
  52. }
  53. if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
  54. $collectedLogs = [];
  55. $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) {
  56. if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) {
  57. return $previousHandler ? $previousHandler($type, $message, $file, $line) : false;
  58. }
  59. if (isset($collectedLogs[$message])) {
  60. ++$collectedLogs[$message]['count'];
  61. return null;
  62. }
  63. $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3);
  64. // Clean the trace by removing first frames added by the error handler itself.
  65. for ($i = 0; isset($backtrace[$i]); ++$i) {
  66. if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
  67. $backtrace = \array_slice($backtrace, 1 + $i);
  68. break;
  69. }
  70. }
  71. $collectedLogs[$message] = [
  72. 'type' => $type,
  73. 'message' => $message,
  74. 'file' => $file,
  75. 'line' => $line,
  76. 'trace' => $backtrace,
  77. 'count' => 1,
  78. ];
  79. return null;
  80. });
  81. }
  82. $preload = [];
  83. try {
  84. foreach ($this->warmers as $warmer) {
  85. if (!$this->optionalsEnabled && $warmer->isOptional()) {
  86. continue;
  87. }
  88. if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) {
  89. continue;
  90. }
  91. $start = microtime(true);
  92. foreach ((array) $warmer->warmUp($cacheDir, $buildDir) as $item) {
  93. if (is_dir($item) || (str_starts_with($item, \dirname($cacheDir)) && !is_file($item)) || ($buildDir && str_starts_with($item, \dirname($buildDir)) && !is_file($item))) {
  94. throw new \LogicException(\sprintf('"%s::warmUp()" should return a list of files or classes but "%s" is none of them.', $warmer::class, $item));
  95. }
  96. $preload[] = $item;
  97. }
  98. if ($io?->isDebug()) {
  99. $io->info(\sprintf('"%s" completed in %0.2fms.', $warmer::class, 1000 * (microtime(true) - $start)));
  100. }
  101. }
  102. } finally {
  103. if ($collectDeprecations) {
  104. restore_error_handler();
  105. if (is_file($this->deprecationLogsFilepath)) {
  106. $previousLogs = unserialize(file_get_contents($this->deprecationLogsFilepath));
  107. if (\is_array($previousLogs)) {
  108. $collectedLogs = array_merge($previousLogs, $collectedLogs);
  109. }
  110. }
  111. file_put_contents($this->deprecationLogsFilepath, serialize(array_values($collectedLogs)));
  112. }
  113. }
  114. return array_values(array_unique($preload));
  115. }
  116. public function isOptional(): bool
  117. {
  118. return false;
  119. }
  120. }