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

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