FileCacheManager.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Cache;
  13. use PhpCsFixer\Hasher;
  14. /**
  15. * Class supports caching information about state of fixing files.
  16. *
  17. * Cache is supported only for phar version and version installed via composer.
  18. *
  19. * File will be processed by PHP CS Fixer only if any of the following conditions is fulfilled:
  20. * - cache is corrupt
  21. * - fixer version changed
  22. * - rules changed
  23. * - file is new
  24. * - file changed
  25. *
  26. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  27. *
  28. * @internal
  29. */
  30. final class FileCacheManager implements CacheManagerInterface
  31. {
  32. public const WRITE_FREQUENCY = 10;
  33. private FileHandlerInterface $handler;
  34. private SignatureInterface $signature;
  35. private bool $isDryRun;
  36. private DirectoryInterface $cacheDirectory;
  37. private int $writeCounter = 0;
  38. private bool $signatureWasUpdated = false;
  39. private CacheInterface $cache;
  40. public function __construct(
  41. FileHandlerInterface $handler,
  42. SignatureInterface $signature,
  43. bool $isDryRun = false,
  44. ?DirectoryInterface $cacheDirectory = null
  45. ) {
  46. $this->handler = $handler;
  47. $this->signature = $signature;
  48. $this->isDryRun = $isDryRun;
  49. $this->cacheDirectory = $cacheDirectory ?? new Directory('');
  50. $this->readCache();
  51. }
  52. public function __destruct()
  53. {
  54. if (true === $this->signatureWasUpdated || 0 !== $this->writeCounter) {
  55. $this->writeCache();
  56. }
  57. }
  58. /**
  59. * This class is not intended to be serialized,
  60. * and cannot be deserialized (see __wakeup method).
  61. */
  62. public function __sleep(): array
  63. {
  64. throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  65. }
  66. /**
  67. * Disable the deserialization of the class to prevent attacker executing
  68. * code by leveraging the __destruct method.
  69. *
  70. * @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection
  71. */
  72. public function __wakeup(): void
  73. {
  74. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  75. }
  76. public function needFixing(string $file, string $fileContent): bool
  77. {
  78. $file = $this->cacheDirectory->getRelativePathTo($file);
  79. return !$this->cache->has($file) || $this->cache->get($file) !== $this->calcHash($fileContent);
  80. }
  81. public function setFile(string $file, string $fileContent): void
  82. {
  83. $this->setFileHash($file, $this->calcHash($fileContent));
  84. }
  85. public function setFileHash(string $file, string $hash): void
  86. {
  87. $file = $this->cacheDirectory->getRelativePathTo($file);
  88. if ($this->isDryRun && $this->cache->has($file) && $this->cache->get($file) !== $hash) {
  89. $this->cache->clear($file);
  90. } else {
  91. $this->cache->set($file, $hash);
  92. }
  93. if (self::WRITE_FREQUENCY === ++$this->writeCounter) {
  94. $this->writeCounter = 0;
  95. $this->writeCache();
  96. }
  97. }
  98. private function readCache(): void
  99. {
  100. $cache = $this->handler->read();
  101. if (null === $cache || !$this->signature->equals($cache->getSignature())) {
  102. $cache = new Cache($this->signature);
  103. $this->signatureWasUpdated = true;
  104. }
  105. $this->cache = $cache;
  106. }
  107. private function writeCache(): void
  108. {
  109. $this->handler->write($this->cache);
  110. }
  111. private function calcHash(string $content): string
  112. {
  113. return Hasher::calculate($content);
  114. }
  115. }