Просмотр файла vendor/vlucas/phpdotenv/src/Loader.php

Размер файла: 10.38Kb
  1. <?php
  2.  
  3. namespace Dotenv;
  4.  
  5. use Dotenv\Exception\InvalidFileException;
  6. use Dotenv\Exception\InvalidPathException;
  7.  
  8. /**
  9. * This is the loaded class.
  10. *
  11. * It's responsible for loading variables by reading a file from disk and:
  12. * - stripping comments beginning with a `#`,
  13. * - parsing lines that look shell variable setters, e.g `export key = value`, `key="value"`.
  14. */
  15. class Loader
  16. {
  17. /**
  18. * The file path.
  19. *
  20. * @var string
  21. */
  22. protected $filePath;
  23.  
  24. /**
  25. * Are we immutable?
  26. *
  27. * @var bool
  28. */
  29. protected $immutable;
  30.  
  31. /**
  32. * Create a new loader instance.
  33. *
  34. * @param string $filePath
  35. * @param bool $immutable
  36. *
  37. * @return void
  38. */
  39. public function __construct($filePath, $immutable = false)
  40. {
  41. $this->filePath = $filePath;
  42. $this->immutable = $immutable;
  43. }
  44.  
  45. /**
  46. * Load `.env` file in given directory.
  47. *
  48. * @return array
  49. */
  50. public function load()
  51. {
  52. $this->ensureFileIsReadable();
  53.  
  54. $filePath = $this->filePath;
  55. $lines = $this->readLinesFromFile($filePath);
  56. foreach ($lines as $line) {
  57. if (!$this->isComment($line) && $this->looksLikeSetter($line)) {
  58. $this->setEnvironmentVariable($line);
  59. }
  60. }
  61.  
  62. return $lines;
  63. }
  64.  
  65. /**
  66. * Ensures the given filePath is readable.
  67. *
  68. * @throws \Dotenv\Exception\InvalidPathException
  69. *
  70. * @return void
  71. */
  72. protected function ensureFileIsReadable()
  73. {
  74. if (!is_readable($this->filePath) || !is_file($this->filePath)) {
  75. throw new InvalidPathException(sprintf('Unable to read the environment file at %s.', $this->filePath));
  76. }
  77. }
  78.  
  79. /**
  80. * Normalise the given environment variable.
  81. *
  82. * Takes value as passed in by developer and:
  83. * - ensures we're dealing with a separate name and value, breaking apart the name string if needed,
  84. * - cleaning the value of quotes,
  85. * - cleaning the name of quotes,
  86. * - resolving nested variables.
  87. *
  88. * @param string $name
  89. * @param string $value
  90. *
  91. * @return array
  92. */
  93. protected function normaliseEnvironmentVariable($name, $value)
  94. {
  95. list($name, $value) = $this->splitCompoundStringIntoParts($name, $value);
  96. list($name, $value) = $this->sanitiseVariableName($name, $value);
  97. list($name, $value) = $this->sanitiseVariableValue($name, $value);
  98.  
  99. $value = $this->resolveNestedVariables($value);
  100.  
  101. return array($name, $value);
  102. }
  103.  
  104. /**
  105. * Process the runtime filters.
  106. *
  107. * Called from the `VariableFactory`, passed as a callback in `$this->loadFromFile()`.
  108. *
  109. * @param string $name
  110. * @param string $value
  111. *
  112. * @return array
  113. */
  114. public function processFilters($name, $value)
  115. {
  116. list($name, $value) = $this->splitCompoundStringIntoParts($name, $value);
  117. list($name, $value) = $this->sanitiseVariableName($name, $value);
  118. list($name, $value) = $this->sanitiseVariableValue($name, $value);
  119.  
  120. return array($name, $value);
  121. }
  122.  
  123. /**
  124. * Read lines from the file, auto detecting line endings.
  125. *
  126. * @param string $filePath
  127. *
  128. * @return array
  129. */
  130. protected function readLinesFromFile($filePath)
  131. {
  132. // Read file into an array of lines with auto-detected line endings
  133. $autodetect = ini_get('auto_detect_line_endings');
  134. ini_set('auto_detect_line_endings', '1');
  135. $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  136. ini_set('auto_detect_line_endings', $autodetect);
  137.  
  138. return $lines;
  139. }
  140.  
  141. /**
  142. * Determine if the line in the file is a comment, e.g. begins with a #.
  143. *
  144. * @param string $line
  145. *
  146. * @return bool
  147. */
  148. protected function isComment($line)
  149. {
  150. return strpos(ltrim($line), '#') === 0;
  151. }
  152.  
  153. /**
  154. * Determine if the given line looks like it's setting a variable.
  155. *
  156. * @param string $line
  157. *
  158. * @return bool
  159. */
  160. protected function looksLikeSetter($line)
  161. {
  162. return strpos($line, '=') !== false;
  163. }
  164.  
  165. /**
  166. * Split the compound string into parts.
  167. *
  168. * If the `$name` contains an `=` sign, then we split it into 2 parts, a `name` & `value`
  169. * disregarding the `$value` passed in.
  170. *
  171. * @param string $name
  172. * @param string $value
  173. *
  174. * @return array
  175. */
  176. protected function splitCompoundStringIntoParts($name, $value)
  177. {
  178. if (strpos($name, '=') !== false) {
  179. list($name, $value) = array_map('trim', explode('=', $name, 2));
  180. }
  181.  
  182. return array($name, $value);
  183. }
  184.  
  185. /**
  186. * Strips quotes from the environment variable value.
  187. *
  188. * @param string $name
  189. * @param string $value
  190. *
  191. * @throws \Dotenv\Exception\InvalidFileException
  192. *
  193. * @return array
  194. */
  195. protected function sanitiseVariableValue($name, $value)
  196. {
  197. $value = trim($value);
  198. if (!$value) {
  199. return array($name, $value);
  200. }
  201.  
  202. if ($this->beginsWithAQuote($value)) { // value starts with a quote
  203. $quote = $value[0];
  204. $regexPattern = sprintf(
  205. '/^
  206. %1$s # match a quote at the start of the value
  207. ( # capturing sub-pattern used
  208. (?: # we do not need to capture this
  209. [^%1$s\\\\] # any character other than a quote or backslash
  210. |\\\\\\\\ # or two backslashes together
  211. |\\\\%1$s # or an escaped quote e.g \"
  212. )* # as many characters that match the previous rules
  213. ) # end of the capturing sub-pattern
  214. %1$s # and the closing quote
  215. .*$ # and discard any string after the closing quote
  216. /mx',
  217. $quote
  218. );
  219. $value = preg_replace($regexPattern, '$1', $value);
  220. $value = str_replace("\\$quote", $quote, $value);
  221. $value = str_replace('\\\\', '\\', $value);
  222. } else {
  223. $parts = explode(' #', $value, 2);
  224. $value = trim($parts[0]);
  225.  
  226. // Unquoted values cannot contain whitespace
  227. if (preg_match('/\s+/', $value) > 0) {
  228. throw new InvalidFileException('Dotenv values containing spaces must be surrounded by quotes.');
  229. }
  230. }
  231.  
  232. return array($name, trim($value));
  233. }
  234.  
  235. /**
  236. * Resolve the nested variables.
  237. *
  238. * Look for {$varname} patterns in the variable value and replace with an
  239. * existing environment variable.
  240. *
  241. * @param string $value
  242. *
  243. * @return mixed
  244. */
  245. protected function resolveNestedVariables($value)
  246. {
  247. if (strpos($value, '$') !== false) {
  248. $loader = $this;
  249. $value = preg_replace_callback(
  250. '/\${([a-zA-Z0-9_]+)}/',
  251. function ($matchedPatterns) use ($loader) {
  252. $nestedVariable = $loader->getEnvironmentVariable($matchedPatterns[1]);
  253. if ($nestedVariable === null) {
  254. return $matchedPatterns[0];
  255. } else {
  256. return $nestedVariable;
  257. }
  258. },
  259. $value
  260. );
  261. }
  262.  
  263. return $value;
  264. }
  265.  
  266. /**
  267. * Strips quotes and the optional leading "export " from the environment variable name.
  268. *
  269. * @param string $name
  270. * @param string $value
  271. *
  272. * @return array
  273. */
  274. protected function sanitiseVariableName($name, $value)
  275. {
  276. $name = trim(str_replace(array('export ', '\'', '"'), '', $name));
  277.  
  278. return array($name, $value);
  279. }
  280.  
  281. /**
  282. * Determine if the given string begins with a quote.
  283. *
  284. * @param string $value
  285. *
  286. * @return bool
  287. */
  288. protected function beginsWithAQuote($value)
  289. {
  290. return strpbrk($value[0], '"\'') !== false;
  291. }
  292.  
  293. /**
  294. * Search the different places for environment variables and return first value found.
  295. *
  296. * @param string $name
  297. *
  298. * @return string|null
  299. */
  300. public function getEnvironmentVariable($name)
  301. {
  302. switch (true) {
  303. case array_key_exists($name, $_ENV):
  304. return $_ENV[$name];
  305. case array_key_exists($name, $_SERVER):
  306. return $_SERVER[$name];
  307. default:
  308. $value = getenv($name);
  309. return $value === false ? null : $value; // switch getenv default to null
  310. }
  311. }
  312.  
  313. /**
  314. * Set an environment variable.
  315. *
  316. * This is done using:
  317. * - putenv,
  318. * - $_ENV,
  319. * - $_SERVER.
  320. *
  321. * The environment variable value is stripped of single and double quotes.
  322. *
  323. * @param string $name
  324. * @param string|null $value
  325. *
  326. * @return void
  327. */
  328. public function setEnvironmentVariable($name, $value = null)
  329. {
  330. list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);
  331.  
  332. // Don't overwrite existing environment variables if we're immutable
  333. // Ruby's dotenv does this with `ENV[key] ||= value`.
  334. if ($this->immutable && $this->getEnvironmentVariable($name) !== null) {
  335. return;
  336. }
  337.  
  338. // If PHP is running as an Apache module and an existing
  339. // Apache environment variable exists, overwrite it
  340. if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
  341. apache_setenv($name, $value);
  342. }
  343.  
  344. if (function_exists('putenv')) {
  345. putenv("$name=$value");
  346. }
  347.  
  348. $_ENV[$name] = $value;
  349. $_SERVER[$name] = $value;
  350. }
  351.  
  352. /**
  353. * Clear an environment variable.
  354. *
  355. * This is not (currently) used by Dotenv but is provided as a utility
  356. * method for 3rd party code.
  357. *
  358. * This is done using:
  359. * - putenv,
  360. * - unset($_ENV, $_SERVER).
  361. *
  362. * @param string $name
  363. *
  364. * @see setEnvironmentVariable()
  365. *
  366. * @return void
  367. */
  368. public function clearEnvironmentVariable($name)
  369. {
  370. // Don't clear anything if we're immutable.
  371. if ($this->immutable) {
  372. return;
  373. }
  374.  
  375. if (function_exists('putenv')) {
  376. putenv($name);
  377. }
  378.  
  379. unset($_ENV[$name], $_SERVER[$name]);
  380. }
  381. }