Env.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <?php
  2. namespace Illuminate\Support;
  3. use Closure;
  4. use Dotenv\Repository\Adapter\PutenvAdapter;
  5. use Dotenv\Repository\RepositoryBuilder;
  6. use Illuminate\Filesystem\Filesystem;
  7. use PhpOption\Option;
  8. use RuntimeException;
  9. class Env
  10. {
  11. /**
  12. * Indicates if the putenv adapter is enabled.
  13. *
  14. * @var bool
  15. */
  16. protected static $putenv = true;
  17. /**
  18. * The environment repository instance.
  19. *
  20. * @var \Dotenv\Repository\RepositoryInterface|null
  21. */
  22. protected static $repository;
  23. /**
  24. * The list of custom adapters for loading environment variables.
  25. *
  26. * @var array<Closure>
  27. */
  28. protected static $customAdapters = [];
  29. /**
  30. * Enable the putenv adapter.
  31. *
  32. * @return void
  33. */
  34. public static function enablePutenv()
  35. {
  36. static::$putenv = true;
  37. static::$repository = null;
  38. }
  39. /**
  40. * Disable the putenv adapter.
  41. *
  42. * @return void
  43. */
  44. public static function disablePutenv()
  45. {
  46. static::$putenv = false;
  47. static::$repository = null;
  48. }
  49. /**
  50. * Register a custom adapter creator Closure.
  51. */
  52. public static function extend(Closure $callback, ?string $name = null): void
  53. {
  54. if (! is_null($name)) {
  55. static::$customAdapters[$name] = $callback;
  56. } else {
  57. static::$customAdapters[] = $callback;
  58. }
  59. static::$repository = null;
  60. }
  61. /**
  62. * Get the environment repository instance.
  63. *
  64. * @return \Dotenv\Repository\RepositoryInterface
  65. */
  66. public static function getRepository()
  67. {
  68. if (static::$repository === null) {
  69. $builder = RepositoryBuilder::createWithDefaultAdapters();
  70. if (static::$putenv) {
  71. $builder = $builder->addAdapter(PutenvAdapter::class);
  72. }
  73. foreach (static::$customAdapters as $adapter) {
  74. $builder = $builder->addAdapter($adapter());
  75. }
  76. static::$repository = $builder->immutable()->make();
  77. }
  78. return static::$repository;
  79. }
  80. /**
  81. * Get the value of an environment variable.
  82. *
  83. * @param string $key
  84. * @param mixed $default
  85. * @return mixed
  86. */
  87. public static function get($key, $default = null)
  88. {
  89. return self::getOption($key)->getOrCall(fn () => value($default));
  90. }
  91. /**
  92. * Get the value of a required environment variable.
  93. *
  94. * @param string $key
  95. * @return mixed
  96. *
  97. * @throws \RuntimeException
  98. */
  99. public static function getOrFail($key)
  100. {
  101. return self::getOption($key)->getOrThrow(new RuntimeException("Environment variable [$key] has no value."));
  102. }
  103. /**
  104. * Write an array of key-value pairs to the environment file.
  105. *
  106. * @param array $variables
  107. * @param string $pathToFile
  108. * @param bool $overwrite
  109. * @return void
  110. *
  111. * @throws \RuntimeException
  112. * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
  113. */
  114. public static function writeVariables(array $variables, string $pathToFile, bool $overwrite = false): void
  115. {
  116. $filesystem = new Filesystem;
  117. if ($filesystem->missing($pathToFile)) {
  118. throw new RuntimeException("The file [{$pathToFile}] does not exist.");
  119. }
  120. $lines = explode(PHP_EOL, $filesystem->get($pathToFile));
  121. foreach ($variables as $key => $value) {
  122. $lines = self::addVariableToEnvContents($key, $value, $lines, $overwrite);
  123. }
  124. $filesystem->put($pathToFile, implode(PHP_EOL, $lines));
  125. }
  126. /**
  127. * Write a single key-value pair to the environment file.
  128. *
  129. * @param string $key
  130. * @param mixed $value
  131. * @param string $pathToFile
  132. * @param bool $overwrite
  133. * @return void
  134. *
  135. * @throws \RuntimeException
  136. * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
  137. */
  138. public static function writeVariable(string $key, mixed $value, string $pathToFile, bool $overwrite = false): void
  139. {
  140. $filesystem = new Filesystem;
  141. if ($filesystem->missing($pathToFile)) {
  142. throw new RuntimeException("The file [{$pathToFile}] does not exist.");
  143. }
  144. $envContent = $filesystem->get($pathToFile);
  145. $lines = explode(PHP_EOL, $envContent);
  146. $lines = self::addVariableToEnvContents($key, $value, $lines, $overwrite);
  147. $filesystem->put($pathToFile, implode(PHP_EOL, $lines));
  148. }
  149. /**
  150. * Add a variable to the environment file contents.
  151. *
  152. * @param string $key
  153. * @param mixed $value
  154. * @param array $envLines
  155. * @param bool $overwrite
  156. * @return array
  157. */
  158. protected static function addVariableToEnvContents(string $key, mixed $value, array $envLines, bool $overwrite): array
  159. {
  160. $prefix = explode('_', $key)[0].'_';
  161. $lastPrefixIndex = -1;
  162. $shouldQuote = preg_match('/^[a-zA-z0-9]+$/', $value) === 0;
  163. $lineToAddVariations = [
  164. $key.'='.(is_string($value) ? self::prepareQuotedValue($value) : $value),
  165. $key.'='.$value,
  166. ];
  167. $lineToAdd = $shouldQuote ? $lineToAddVariations[0] : $lineToAddVariations[1];
  168. if ($value === '') {
  169. $lineToAdd = $key.'=';
  170. }
  171. foreach ($envLines as $index => $line) {
  172. if (str_starts_with($line, $prefix)) {
  173. $lastPrefixIndex = $index;
  174. }
  175. if (in_array($line, $lineToAddVariations)) {
  176. // This exact line already exists, so we don't need to add it again.
  177. return $envLines;
  178. }
  179. if ($line === $key.'=') {
  180. // If the value is empty, we can replace it with the new value.
  181. $envLines[$index] = $lineToAdd;
  182. return $envLines;
  183. }
  184. if (str_starts_with($line, $key.'=')) {
  185. if (! $overwrite) {
  186. return $envLines;
  187. }
  188. $envLines[$index] = $lineToAdd;
  189. return $envLines;
  190. }
  191. }
  192. if ($lastPrefixIndex === -1) {
  193. if (count($envLines) && $envLines[count($envLines) - 1] !== '') {
  194. $envLines[] = '';
  195. }
  196. return array_merge($envLines, [$lineToAdd]);
  197. }
  198. return array_merge(
  199. array_slice($envLines, 0, $lastPrefixIndex + 1),
  200. [$lineToAdd],
  201. array_slice($envLines, $lastPrefixIndex + 1)
  202. );
  203. }
  204. /**
  205. * Get the possible option for this environment variable.
  206. *
  207. * @param string $key
  208. * @return \PhpOption\Option|\PhpOption\Some
  209. */
  210. protected static function getOption($key)
  211. {
  212. return Option::fromValue(static::getRepository()->get($key))
  213. ->map(function ($value) {
  214. switch (strtolower($value)) {
  215. case 'true':
  216. case '(true)':
  217. return true;
  218. case 'false':
  219. case '(false)':
  220. return false;
  221. case 'empty':
  222. case '(empty)':
  223. return '';
  224. case 'null':
  225. case '(null)':
  226. return;
  227. }
  228. if (preg_match('/\A([\'"])(.*)\1\z/', $value, $matches)) {
  229. return $matches[2];
  230. }
  231. return $value;
  232. });
  233. }
  234. /**
  235. * Wrap a string in quotes, choosing double or single quotes.
  236. *
  237. * @param string $input
  238. * @return string
  239. */
  240. protected static function prepareQuotedValue(string $input)
  241. {
  242. return str_contains($input, '"')
  243. ? "'".self::addSlashesExceptFor($input, ['"'])."'"
  244. : '"'.self::addSlashesExceptFor($input, ["'"]).'"';
  245. }
  246. /**
  247. * Escape a string using addslashes, excluding the specified characters from being escaped.
  248. *
  249. * @param string $value
  250. * @param array $except
  251. * @return string
  252. */
  253. protected static function addSlashesExceptFor(string $value, array $except = [])
  254. {
  255. $escaped = addslashes($value);
  256. foreach ($except as $character) {
  257. $escaped = str_replace('\\'.$character, $character, $escaped);
  258. }
  259. return $escaped;
  260. }
  261. }