vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php line 110

Open in your IDE?
  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\Cache\Adapter;
  11. use Symfony\Component\Cache\Marshaller\MarshallerInterface;
  12. use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
  13. use Symfony\Component\Cache\PruneableInterface;
  14. use Symfony\Component\Cache\Traits\FilesystemTrait;
  15. /**
  16.  * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
  17.  *
  18.  * @author Nicolas Grekas <p@tchwork.com>
  19.  * @author André Rømcke <andre.romcke+symfony@gmail.com>
  20.  */
  21. class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
  22. {
  23.     use FilesystemTrait {
  24.         doClear as private doClearCache;
  25.         doSave as private doSaveCache;
  26.     }
  27.     /**
  28.      * Folder used for tag symlinks.
  29.      */
  30.     private const TAG_FOLDER 'tags';
  31.     public function __construct(string $namespace ''int $defaultLifetime 0string $directory nullMarshallerInterface $marshaller null)
  32.     {
  33.         $this->marshaller = new TagAwareMarshaller($marshaller);
  34.         parent::__construct(''$defaultLifetime);
  35.         $this->init($namespace$directory);
  36.     }
  37.     /**
  38.      * {@inheritdoc}
  39.      */
  40.     protected function doClear(string $namespace)
  41.     {
  42.         $ok $this->doClearCache($namespace);
  43.         if ('' !== $namespace) {
  44.             return $ok;
  45.         }
  46.         set_error_handler(static function () {});
  47.         $chars '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  48.         try {
  49.             foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
  50.                 if (rename($dir$renamed substr_replace($dirbin2hex(random_bytes(4)), -8))) {
  51.                     $dir $renamed.\DIRECTORY_SEPARATOR;
  52.                 } else {
  53.                     $dir .= \DIRECTORY_SEPARATOR;
  54.                     $renamed null;
  55.                 }
  56.                 for ($i 0$i 38; ++$i) {
  57.                     if (!is_dir($dir.$chars[$i])) {
  58.                         continue;
  59.                     }
  60.                     for ($j 0$j 38; ++$j) {
  61.                         if (!is_dir($d $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
  62.                             continue;
  63.                         }
  64.                         foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) {
  65.                             if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) {
  66.                                 unlink($d.\DIRECTORY_SEPARATOR.$link);
  67.                             }
  68.                         }
  69.                         null === $renamed ?: rmdir($d);
  70.                     }
  71.                     null === $renamed ?: rmdir($dir.$chars[$i]);
  72.                 }
  73.                 null === $renamed ?: rmdir($renamed);
  74.             }
  75.         } finally {
  76.             restore_error_handler();
  77.         }
  78.         return $ok;
  79.     }
  80.     /**
  81.      * {@inheritdoc}
  82.      */
  83.     protected function doSave(array $valuesint $lifetime, array $addTagData = [], array $removeTagData = []): array
  84.     {
  85.         $failed $this->doSaveCache($values$lifetime);
  86.         // Add Tags as symlinks
  87.         foreach ($addTagData as $tagId => $ids) {
  88.             $tagFolder $this->getTagFolder($tagId);
  89.             foreach ($ids as $id) {
  90.                 if ($failed && \in_array($id$failedtrue)) {
  91.                     continue;
  92.                 }
  93.                 $file $this->getFile($id);
  94.                 if (!@symlink($file$tagLink $this->getFile($idtrue$tagFolder)) && !is_link($tagLink)) {
  95.                     @unlink($file);
  96.                     $failed[] = $id;
  97.                 }
  98.             }
  99.         }
  100.         // Unlink removed Tags
  101.         foreach ($removeTagData as $tagId => $ids) {
  102.             $tagFolder $this->getTagFolder($tagId);
  103.             foreach ($ids as $id) {
  104.                 if ($failed && \in_array($id$failedtrue)) {
  105.                     continue;
  106.                 }
  107.                 @unlink($this->getFile($idfalse$tagFolder));
  108.             }
  109.         }
  110.         return $failed;
  111.     }
  112.     /**
  113.      * {@inheritdoc}
  114.      */
  115.     protected function doDeleteYieldTags(array $ids): iterable
  116.     {
  117.         foreach ($ids as $id) {
  118.             $file $this->getFile($id);
  119.             if (!is_file($file) || !$h = @fopen($file'r')) {
  120.                 continue;
  121.             }
  122.             if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) {
  123.                 fclose($h);
  124.                 continue;
  125.             }
  126.             $meta explode("\n"fread($h4096), 3)[2] ?? '';
  127.             // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
  128.             if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) {
  129.                 $meta[9] = "\0";
  130.                 $tagLen unpack('Nlen'$meta9)['len'];
  131.                 $meta substr($meta13$tagLen);
  132.                 if ($tagLen -= \strlen($meta)) {
  133.                     $meta .= fread($h$tagLen);
  134.                 }
  135.                 try {
  136.                     yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta);
  137.                 } catch (\Exception $e) {
  138.                     yield $id => [];
  139.                 }
  140.             }
  141.             fclose($h);
  142.             if (\PHP_VERSION_ID 70300 && '\\' === \DIRECTORY_SEPARATOR) {
  143.                 @unlink($file);
  144.             }
  145.         }
  146.     }
  147.     /**
  148.      * {@inheritdoc}
  149.      */
  150.     protected function doDeleteTagRelations(array $tagData): bool
  151.     {
  152.         foreach ($tagData as $tagId => $idList) {
  153.             $tagFolder $this->getTagFolder($tagId);
  154.             foreach ($idList as $id) {
  155.                 @unlink($this->getFile($idfalse$tagFolder));
  156.             }
  157.         }
  158.         return true;
  159.     }
  160.     /**
  161.      * {@inheritdoc}
  162.      */
  163.     protected function doInvalidate(array $tagIds): bool
  164.     {
  165.         foreach ($tagIds as $tagId) {
  166.             if (!is_dir($tagFolder $this->getTagFolder($tagId))) {
  167.                 continue;
  168.             }
  169.             set_error_handler(static function () {});
  170.             try {
  171.                 if (rename($tagFolder$renamed substr_replace($tagFolderbin2hex(random_bytes(4)), -9))) {
  172.                     $tagFolder $renamed.\DIRECTORY_SEPARATOR;
  173.                 } else {
  174.                     $renamed null;
  175.                 }
  176.                 foreach ($this->scanHashDir($tagFolder) as $itemLink) {
  177.                     unlink(realpath($itemLink) ?: $itemLink);
  178.                     unlink($itemLink);
  179.                 }
  180.                 if (null === $renamed) {
  181.                     continue;
  182.                 }
  183.                 $chars '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  184.                 for ($i 0$i 38; ++$i) {
  185.                     for ($j 0$j 38; ++$j) {
  186.                         rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
  187.                     }
  188.                     rmdir($tagFolder.$chars[$i]);
  189.                 }
  190.                 rmdir($renamed);
  191.             } finally {
  192.                 restore_error_handler();
  193.             }
  194.         }
  195.         return true;
  196.     }
  197.     private function getTagFolder(string $tagId): string
  198.     {
  199.         return $this->getFile($tagIdfalse$this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
  200.     }
  201. }