vendor/twig/twig/src/Loader/FilesystemLoader.php line 136

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Twig.
  4.  *
  5.  * (c) Fabien Potencier
  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 Twig\Loader;
  11. use Twig\Error\LoaderError;
  12. use Twig\Source;
  13. /**
  14.  * Loads template from the filesystem.
  15.  *
  16.  * @author Fabien Potencier <fabien@symfony.com>
  17.  */
  18. class FilesystemLoader implements LoaderInterface
  19. {
  20.     /** Identifier of the main namespace. */
  21.     public const MAIN_NAMESPACE '__main__';
  22.     /**
  23.      * @var array<string, list<string>>
  24.      */
  25.     protected $paths = [];
  26.     protected $cache = [];
  27.     protected $errorCache = [];
  28.     private $rootPath;
  29.     /**
  30.      * @param string|string[] $paths    A path or an array of paths where to look for templates
  31.      * @param string|null     $rootPath The root path common to all relative paths (null for getcwd())
  32.      */
  33.     public function __construct($paths = [], ?string $rootPath null)
  34.     {
  35.         $this->rootPath = ($rootPath ?? getcwd()).\DIRECTORY_SEPARATOR;
  36.         if (null !== $rootPath && false !== ($realPath realpath($rootPath))) {
  37.             $this->rootPath $realPath.\DIRECTORY_SEPARATOR;
  38.         }
  39.         if ($paths) {
  40.             $this->setPaths($paths);
  41.         }
  42.     }
  43.     /**
  44.      * Returns the paths to the templates.
  45.      *
  46.      * @return list<string>
  47.      */
  48.     public function getPaths(string $namespace self::MAIN_NAMESPACE): array
  49.     {
  50.         return $this->paths[$namespace] ?? [];
  51.     }
  52.     /**
  53.      * Returns the path namespaces.
  54.      *
  55.      * The main namespace is always defined.
  56.      *
  57.      * @return list<string>
  58.      */
  59.     public function getNamespaces(): array
  60.     {
  61.         return array_keys($this->paths);
  62.     }
  63.     /**
  64.      * @param string|string[] $paths A path or an array of paths where to look for templates
  65.      */
  66.     public function setPaths($pathsstring $namespace self::MAIN_NAMESPACE): void
  67.     {
  68.         if (!\is_array($paths)) {
  69.             $paths = [$paths];
  70.         }
  71.         $this->paths[$namespace] = [];
  72.         foreach ($paths as $path) {
  73.             $this->addPath($path$namespace);
  74.         }
  75.     }
  76.     /**
  77.      * @throws LoaderError
  78.      */
  79.     public function addPath(string $pathstring $namespace self::MAIN_NAMESPACE): void
  80.     {
  81.         // invalidate the cache
  82.         $this->cache $this->errorCache = [];
  83.         $checkPath $this->isAbsolutePath($path) ? $path $this->rootPath.$path;
  84.         if (!is_dir($checkPath)) {
  85.             throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").'$path$checkPath));
  86.         }
  87.         $this->paths[$namespace][] = rtrim($path'/\\');
  88.     }
  89.     /**
  90.      * @throws LoaderError
  91.      */
  92.     public function prependPath(string $pathstring $namespace self::MAIN_NAMESPACE): void
  93.     {
  94.         // invalidate the cache
  95.         $this->cache $this->errorCache = [];
  96.         $checkPath $this->isAbsolutePath($path) ? $path $this->rootPath.$path;
  97.         if (!is_dir($checkPath)) {
  98.             throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").'$path$checkPath));
  99.         }
  100.         $path rtrim($path'/\\');
  101.         if (!isset($this->paths[$namespace])) {
  102.             $this->paths[$namespace][] = $path;
  103.         } else {
  104.             array_unshift($this->paths[$namespace], $path);
  105.         }
  106.     }
  107.     public function getSourceContext(string $name): Source
  108.     {
  109.         if (null === $path $this->findTemplate($name)) {
  110.             return new Source(''$name'');
  111.         }
  112.         return new Source(file_get_contents($path), $name$path);
  113.     }
  114.     public function getCacheKey(string $name): string
  115.     {
  116.         if (null === $path $this->findTemplate($name)) {
  117.             return '';
  118.         }
  119.         $len \strlen($this->rootPath);
  120.         if (=== strncmp($this->rootPath$path$len)) {
  121.             return substr($path$len);
  122.         }
  123.         return $path;
  124.     }
  125.     /**
  126.      * @return bool
  127.      */
  128.     public function exists(string $name)
  129.     {
  130.         $name $this->normalizeName($name);
  131.         if (isset($this->cache[$name])) {
  132.             return true;
  133.         }
  134.         return null !== $this->findTemplate($namefalse);
  135.     }
  136.     public function isFresh(string $nameint $time): bool
  137.     {
  138.         // false support to be removed in 3.0
  139.         if (null === $path $this->findTemplate($name)) {
  140.             return false;
  141.         }
  142.         return filemtime($path) < $time;
  143.     }
  144.     /**
  145.      * @return string|null
  146.      */
  147.     protected function findTemplate(string $namebool $throw true)
  148.     {
  149.         $name $this->normalizeName($name);
  150.         if (isset($this->cache[$name])) {
  151.             return $this->cache[$name];
  152.         }
  153.         if (isset($this->errorCache[$name])) {
  154.             if (!$throw) {
  155.                 return null;
  156.             }
  157.             throw new LoaderError($this->errorCache[$name]);
  158.         }
  159.         try {
  160.             [$namespace$shortname] = $this->parseName($name);
  161.             $this->validateName($shortname);
  162.         } catch (LoaderError $e) {
  163.             if (!$throw) {
  164.                 return null;
  165.             }
  166.             throw $e;
  167.         }
  168.         if (!isset($this->paths[$namespace])) {
  169.             $this->errorCache[$name] = \sprintf('There are no registered paths for namespace "%s".'$namespace);
  170.             if (!$throw) {
  171.                 return null;
  172.             }
  173.             throw new LoaderError($this->errorCache[$name]);
  174.         }
  175.         foreach ($this->paths[$namespace] as $path) {
  176.             if (!$this->isAbsolutePath($path)) {
  177.                 $path $this->rootPath.$path;
  178.             }
  179.             if (is_file($path.'/'.$shortname)) {
  180.                 if (false !== $realpath realpath($path.'/'.$shortname)) {
  181.                     return $this->cache[$name] = $realpath;
  182.                 }
  183.                 return $this->cache[$name] = $path.'/'.$shortname;
  184.             }
  185.         }
  186.         $this->errorCache[$name] = \sprintf('Unable to find template "%s" (looked into: %s).'$nameimplode(', '$this->paths[$namespace]));
  187.         if (!$throw) {
  188.             return null;
  189.         }
  190.         throw new LoaderError($this->errorCache[$name]);
  191.     }
  192.     private function normalizeName(string $name): string
  193.     {
  194.         return preg_replace('#/{2,}#''/'str_replace('\\''/'$name));
  195.     }
  196.     private function parseName(string $namestring $default self::MAIN_NAMESPACE): array
  197.     {
  198.         if (isset($name[0]) && '@' == $name[0]) {
  199.             if (false === $pos strpos($name'/')) {
  200.                 throw new LoaderError(\sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").'$name));
  201.             }
  202.             $namespace substr($name1$pos 1);
  203.             $shortname substr($name$pos 1);
  204.             return [$namespace$shortname];
  205.         }
  206.         return [$default$name];
  207.     }
  208.     private function validateName(string $name): void
  209.     {
  210.         if (str_contains($name"\0")) {
  211.             throw new LoaderError('A template name cannot contain NUL bytes.');
  212.         }
  213.         $name ltrim($name'/');
  214.         $parts explode('/'$name);
  215.         $level 0;
  216.         foreach ($parts as $part) {
  217.             if ('..' === $part) {
  218.                 --$level;
  219.             } elseif ('.' !== $part) {
  220.                 ++$level;
  221.             }
  222.             if ($level 0) {
  223.                 throw new LoaderError(\sprintf('Looks like you try to load a template outside configured directories (%s).'$name));
  224.             }
  225.         }
  226.     }
  227.     private function isAbsolutePath(string $file): bool
  228.     {
  229.         return strspn($file'/\\'01)
  230.             || (\strlen($file) > && ctype_alpha($file[0])
  231.                 && ':' === $file[1]
  232.                 && strspn($file'/\\'21)
  233.             )
  234.             || null !== parse_url($file\PHP_URL_SCHEME)
  235.         ;
  236.     }
  237. }