vendor/symfony/dependency-injection/EnvVarProcessor.php line 189

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\DependencyInjection;
  11. use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
  12. use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
  13. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  14. /**
  15.  * @author Nicolas Grekas <p@tchwork.com>
  16.  */
  17. class EnvVarProcessor implements EnvVarProcessorInterface
  18. {
  19.     private ContainerInterface $container;
  20.     /** @var \Traversable<EnvVarLoaderInterface> */
  21.     private \Traversable $loaders;
  22.     private array $loadedVars = [];
  23.     /**
  24.      * @param \Traversable<EnvVarLoaderInterface>|null $loaders
  25.      */
  26.     public function __construct(ContainerInterface $container\Traversable $loaders null)
  27.     {
  28.         $this->container $container;
  29.         $this->loaders $loaders ?? new \ArrayIterator();
  30.     }
  31.     public static function getProvidedTypes(): array
  32.     {
  33.         return [
  34.             'base64' => 'string',
  35.             'bool' => 'bool',
  36.             'not' => 'bool',
  37.             'const' => 'bool|int|float|string|array',
  38.             'csv' => 'array',
  39.             'file' => 'string',
  40.             'float' => 'float',
  41.             'int' => 'int',
  42.             'json' => 'array',
  43.             'key' => 'bool|int|float|string|array',
  44.             'url' => 'array',
  45.             'query_string' => 'array',
  46.             'resolve' => 'string',
  47.             'default' => 'bool|int|float|string|array',
  48.             'string' => 'string',
  49.             'trim' => 'string',
  50.             'require' => 'bool|int|float|string|array',
  51.             'enum' => \BackedEnum::class,
  52.             'shuffle' => 'array',
  53.         ];
  54.     }
  55.     public function getEnv(string $prefixstring $name\Closure $getEnv): mixed
  56.     {
  57.         $i strpos($name':');
  58.         if ('key' === $prefix) {
  59.             if (false === $i) {
  60.                 throw new RuntimeException(sprintf('Invalid env "key:%s": a key specifier should be provided.'$name));
  61.             }
  62.             $next substr($name$i 1);
  63.             $key substr($name0$i);
  64.             $array $getEnv($next);
  65.             if (!\is_array($array)) {
  66.                 throw new RuntimeException(sprintf('Resolved value of "%s" did not result in an array value.'$next));
  67.             }
  68.             if (!isset($array[$key]) && !\array_key_exists($key$array)) {
  69.                 throw new EnvNotFoundException(sprintf('Key "%s" not found in %s (resolved from "%s").'$keyjson_encode($array), $next));
  70.             }
  71.             return $array[$key];
  72.         }
  73.         if ('enum' === $prefix) {
  74.             if (false === $i) {
  75.                 throw new RuntimeException(sprintf('Invalid env "enum:%s": a "%s" class-string should be provided.'$name\BackedEnum::class));
  76.             }
  77.             $next substr($name$i 1);
  78.             $backedEnumClassName substr($name0$i);
  79.             $backedEnumValue $getEnv($next);
  80.             if (!\is_string($backedEnumValue) && !\is_int($backedEnumValue)) {
  81.                 throw new RuntimeException(sprintf('Resolved value of "%s" did not result in a string or int value.'$next));
  82.             }
  83.             if (!is_subclass_of($backedEnumClassName\BackedEnum::class)) {
  84.                 throw new RuntimeException(sprintf('"%s" is not a "%s".'$backedEnumClassName\BackedEnum::class));
  85.             }
  86.             return $backedEnumClassName::tryFrom($backedEnumValue) ?? throw new RuntimeException(sprintf('Enum value "%s" is not backed by "%s".'$backedEnumValue$backedEnumClassName));
  87.         }
  88.         if ('default' === $prefix) {
  89.             if (false === $i) {
  90.                 throw new RuntimeException(sprintf('Invalid env "default:%s": a fallback parameter should be provided.'$name));
  91.             }
  92.             $next substr($name$i 1);
  93.             $default substr($name0$i);
  94.             if ('' !== $default && !$this->container->hasParameter($default)) {
  95.                 throw new RuntimeException(sprintf('Invalid env fallback in "default:%s": parameter "%s" not found.'$name$default));
  96.             }
  97.             try {
  98.                 $env $getEnv($next);
  99.                 if ('' !== $env && null !== $env) {
  100.                     return $env;
  101.                 }
  102.             } catch (EnvNotFoundException) {
  103.                 // no-op
  104.             }
  105.             return '' === $default null $this->container->getParameter($default);
  106.         }
  107.         if ('file' === $prefix || 'require' === $prefix) {
  108.             if (!\is_scalar($file $getEnv($name))) {
  109.                 throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.'$name));
  110.             }
  111.             if (!is_file($file)) {
  112.                 throw new EnvNotFoundException(sprintf('File "%s" not found (resolved from "%s").'$file$name));
  113.             }
  114.             if ('file' === $prefix) {
  115.                 return file_get_contents($file);
  116.             } else {
  117.                 return require $file;
  118.             }
  119.         }
  120.         if (false !== $i || 'string' !== $prefix) {
  121.             $env $getEnv($name);
  122.         } elseif (isset($_ENV[$name])) {
  123.             $env $_ENV[$name];
  124.         } elseif (isset($_SERVER[$name]) && !str_starts_with($name'HTTP_')) {
  125.             $env $_SERVER[$name];
  126.         } elseif (false === ($env getenv($name)) || null === $env) { // null is a possible value because of thread safety issues
  127.             foreach ($this->loadedVars as $vars) {
  128.                 if (false !== $env = ($vars[$name] ?? false)) {
  129.                     break;
  130.                 }
  131.             }
  132.             if (false === $env || null === $env) {
  133.                 $loaders $this->loaders;
  134.                 $this->loaders = new \ArrayIterator();
  135.                 try {
  136.                     $i 0;
  137.                     $ended true;
  138.                     $count $loaders instanceof \Countable $loaders->count() : 0;
  139.                     foreach ($loaders as $loader) {
  140.                         if (\count($this->loadedVars) > $i++) {
  141.                             continue;
  142.                         }
  143.                         $this->loadedVars[] = $vars $loader->loadEnvVars();
  144.                         if (false !== $env $vars[$name] ?? false) {
  145.                             $ended false;
  146.                             break;
  147.                         }
  148.                     }
  149.                     if ($ended || $count === $i) {
  150.                         $loaders $this->loaders;
  151.                     }
  152.                 } catch (ParameterCircularReferenceException) {
  153.                     // skip loaders that need an env var that is not defined
  154.                 } finally {
  155.                     $this->loaders $loaders;
  156.                 }
  157.             }
  158.             if (false === $env || null === $env) {
  159.                 if (!$this->container->hasParameter("env($name)")) {
  160.                     throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".'$name));
  161.                 }
  162.                 $env $this->container->getParameter("env($name)");
  163.             }
  164.         }
  165.         if (null === $env) {
  166.             if (!isset($this->getProvidedTypes()[$prefix])) {
  167.                 throw new RuntimeException(sprintf('Unsupported env var prefix "%s".'$prefix));
  168.             }
  169.             if (!\in_array($prefix, ['string''bool''not''int''float'], true)) {
  170.                 return null;
  171.             }
  172.         }
  173.         if ('shuffle' === $prefix) {
  174.             \is_array($env) ? shuffle($env) : throw new RuntimeException(sprintf('Env var "%s" cannot be shuffled, expected array, got "%s".'$nameget_debug_type($env)));
  175.             return $env;
  176.         }
  177.         if (null !== $env && !\is_scalar($env)) {
  178.             throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".'$name$prefix));
  179.         }
  180.         if ('string' === $prefix) {
  181.             return (string) $env;
  182.         }
  183.         if (\in_array($prefix, ['bool''not'], true)) {
  184.             $env = (bool) (filter_var($env\FILTER_VALIDATE_BOOL) ?: filter_var($env\FILTER_VALIDATE_INT) ?: filter_var($env\FILTER_VALIDATE_FLOAT));
  185.             return 'not' === $prefix ? !$env $env;
  186.         }
  187.         if ('int' === $prefix) {
  188.             if (null !== $env && false === $env filter_var($env\FILTER_VALIDATE_INT) ?: filter_var($env\FILTER_VALIDATE_FLOAT)) {
  189.                 throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to int.'$name));
  190.             }
  191.             return (int) $env;
  192.         }
  193.         if ('float' === $prefix) {
  194.             if (null !== $env && false === $env filter_var($env\FILTER_VALIDATE_FLOAT)) {
  195.                 throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to float.'$name));
  196.             }
  197.             return (float) $env;
  198.         }
  199.         if ('const' === $prefix) {
  200.             if (!\defined($env)) {
  201.                 throw new RuntimeException(sprintf('Env var "%s" maps to undefined constant "%s".'$name$env));
  202.             }
  203.             return \constant($env);
  204.         }
  205.         if ('base64' === $prefix) {
  206.             return base64_decode(strtr($env'-_''+/'));
  207.         }
  208.         if ('json' === $prefix) {
  209.             $env json_decode($envtrue);
  210.             if (\JSON_ERROR_NONE !== json_last_error()) {
  211.                 throw new RuntimeException(sprintf('Invalid JSON in env var "%s": '$name).json_last_error_msg());
  212.             }
  213.             if (null !== $env && !\is_array($env)) {
  214.                 throw new RuntimeException(sprintf('Invalid JSON env var "%s": array or null expected, "%s" given.'$nameget_debug_type($env)));
  215.             }
  216.             return $env;
  217.         }
  218.         if ('url' === $prefix) {
  219.             $parsedEnv parse_url($env);
  220.             if (false === $parsedEnv) {
  221.                 throw new RuntimeException(sprintf('Invalid URL in env var "%s".'$name));
  222.             }
  223.             if (!isset($parsedEnv['scheme'], $parsedEnv['host'])) {
  224.                 throw new RuntimeException(sprintf('Invalid URL env var "%s": schema and host expected, "%s" given.'$name$env));
  225.             }
  226.             $parsedEnv += [
  227.                 'port' => null,
  228.                 'user' => null,
  229.                 'pass' => null,
  230.                 'path' => null,
  231.                 'query' => null,
  232.                 'fragment' => null,
  233.             ];
  234.             // remove the '/' separator
  235.             $parsedEnv['path'] = '/' === ($parsedEnv['path'] ?? '/') ? '' substr($parsedEnv['path'], 1);
  236.             return $parsedEnv;
  237.         }
  238.         if ('query_string' === $prefix) {
  239.             $queryString parse_url($env\PHP_URL_QUERY) ?: $env;
  240.             parse_str($queryString$result);
  241.             return $result;
  242.         }
  243.         if ('resolve' === $prefix) {
  244.             return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($name$getEnv) {
  245.                 if (!isset($match[1])) {
  246.                     return '%';
  247.                 }
  248.                 if (str_starts_with($match[1], 'env(') && str_ends_with($match[1], ')') && 'env()' !== $match[1]) {
  249.                     $value $getEnv(substr($match[1], 4, -1));
  250.                 } else {
  251.                     $value $this->container->getParameter($match[1]);
  252.                 }
  253.                 if (!\is_scalar($value)) {
  254.                     throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.'$match[1], $nameget_debug_type($value)));
  255.                 }
  256.                 return $value;
  257.             }, $env);
  258.         }
  259.         if ('csv' === $prefix) {
  260.             return str_getcsv($env',''"''');
  261.         }
  262.         if ('trim' === $prefix) {
  263.             return trim($env);
  264.         }
  265.         throw new RuntimeException(sprintf('Unsupported env var prefix "%s" for env name "%s".'$prefix$name));
  266.     }
  267. }