diff --git a/src/Dotenv.php b/src/Dotenv.php index 761a6de8..66f922cf 100644 --- a/src/Dotenv.php +++ b/src/Dotenv.php @@ -36,16 +36,16 @@ public function __construct(Loader $loader) /** * Create a new dotenv instance. * - * @param string $path + * @param string|string[] $paths * @param string|null $file * @param \Dotenv\Environment\FactoryInterface|null $envFactory * * @return \Dotenv\Dotenv */ - public static function create($path, $file = null, FactoryInterface $envFactory = null) + public static function create($paths, $file = null, FactoryInterface $envFactory = null) { $loader = new Loader( - self::getFilePath($path, $file), + self::getFilePaths((array) $paths, $file ?: '.env'), $envFactory ?: new DotenvFactory(), true ); @@ -54,20 +54,18 @@ public static function create($path, $file = null, FactoryInterface $envFactory } /** - * Returns the full path to the file. + * Returns the full paths to the files. * - * @param string $path - * @param string|null $file + * @param string[] $paths + * @param string $file * * @return string */ - private static function getFilePath($path, $file) + private static function getFilePaths(array $paths, $file) { - if (!is_string($file)) { - $file = '.env'; - } - - return rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$file; + return array_map(function ($path) use ($file) { + return rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$file; + }, $paths); } /** diff --git a/src/Loader.php b/src/Loader.php index ca601047..64bebfa4 100644 --- a/src/Loader.php +++ b/src/Loader.php @@ -4,9 +4,10 @@ use Dotenv\Environment\FactoryInterface; use Dotenv\Exception\InvalidPathException; +use PhpOption\Option; /** - * This is the loaded class. + * This is the loader class. * * It's responsible for loading variables by reading a file from disk and: * - stripping comments beginning with a `#`, @@ -17,11 +18,11 @@ class Loader { /** - * The file path. + * The file paths. * - * @var string + * @var string[] */ - protected $filePath; + protected $filePaths; /** * The environment factory instance. @@ -47,15 +48,15 @@ class Loader /** * Create a new loader instance. * - * @param string $filePath + * @param string[] $filePaths * @param \Dotenv\Environment\FactoryInterface $envFactory * @param bool $immutable * * @return void */ - public function __construct($filePath, FactoryInterface $envFactory, $immutable = false) + public function __construct(array $filePaths, FactoryInterface $envFactory, $immutable = false) { - $this->filePath = $filePath; + $this->filePaths = $filePaths; $this->envFactory = $envFactory; $this->setImmutable($immutable); } @@ -86,33 +87,52 @@ public function setImmutable($immutable = false) public function load() { return $this->processEntries( - Lines::process(self::readLinesFromFile($this->filePath)) + Lines::process(self::readLines($this->filePaths)) ); } /** - * Read lines from the file, auto detecting line endings. + * Attempt to read the lines from the files in order. * - * @param string $filePath + * @param string[] $filePaths * * @throws \Dotenv\Exception\InvalidPathException * * @return string[] */ - private static function readLinesFromFile($filePath) + private static function readLines(array $filePaths) + { + if ($filePaths === []) { + throw new InvalidPathException('At least one environment file path must be provided.'); + } + + foreach ($filePaths as $filePath) { + $lines = self::readFromFile($filePath); + if ($lines->isDefined()) { + return $lines->get(); + } + } + + throw new InvalidPathException( + sprintf('Unable to read any of the environment file(s) at [%s].', implode(', ', $filePaths)) + ); + } + + /** + * Read from the file, auto detecting line endings. + * + * @param string $filePath + * + * @return \PhpOption\Option + */ + private static function readFromFile($filePath) { $autodetect = ini_get('auto_detect_line_endings'); ini_set('auto_detect_line_endings', '1'); $lines = @file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); ini_set('auto_detect_line_endings', $autodetect); - if ($lines === false) { - throw new InvalidPathException( - sprintf('Unable to read the environment file at %s.', $filePath) - ); - } - - return $lines; + return Option::fromValue($lines, false); } /** diff --git a/tests/Dotenv/DotenvTest.php b/tests/Dotenv/DotenvTest.php index 4152eebd..7164775b 100644 --- a/tests/Dotenv/DotenvTest.php +++ b/tests/Dotenv/DotenvTest.php @@ -17,7 +17,7 @@ public function setUp() /** * @expectedException \Dotenv\Exception\InvalidPathException - * @expectedExceptionMessage Unable to read the environment file at + * @expectedExceptionMessage Unable to read any of the environment file(s) at */ public function testDotenvThrowsExceptionIfUnableToLoadFile() { @@ -25,10 +25,26 @@ public function testDotenvThrowsExceptionIfUnableToLoadFile() $dotenv->load(); } + /** + * @expectedException \Dotenv\Exception\InvalidPathException + * @expectedExceptionMessage Unable to read any of the environment file(s) at + */ + public function testDotenvThrowsExceptionIfUnableToLoadFiles() + { + $dotenv = Dotenv::create([__DIR__, __DIR__.'/foo/bar']); + $dotenv->load(); + } + + public function testDotenvTriesPathsToLoad() + { + $dotenv = Dotenv::create([__DIR__, $this->fixturesFolder]); + $this->assertCount(4, $dotenv->load()); + } + public function testDotenvSkipsLoadingIfFileIsMissing() { $dotenv = Dotenv::create(__DIR__); - $this->assertEmpty($dotenv->safeLoad()); + $this->assertSame([], $dotenv->safeLoad()); } public function testDotenvLoadsEnvironmentVars() diff --git a/tests/Dotenv/EnvironmentVariablesTest.php b/tests/Dotenv/EnvironmentVariablesTest.php index dfcca674..fa145599 100644 --- a/tests/Dotenv/EnvironmentVariablesTest.php +++ b/tests/Dotenv/EnvironmentVariablesTest.php @@ -14,7 +14,7 @@ class EnvironmentVariablesTest extends TestCase protected function setUp() { $this->envFactory = new DotenvFactory(); - (new Loader(dirname(__DIR__).'/fixtures/env/.env', $this->envFactory))->load(); + (new Loader([dirname(__DIR__).'/fixtures/env/.env'], $this->envFactory))->load(); } public function testCheckingWhetherVariableExists() diff --git a/tests/Dotenv/LoaderTest.php b/tests/Dotenv/LoaderTest.php index 9a5e77db..3904a4dc 100644 --- a/tests/Dotenv/LoaderTest.php +++ b/tests/Dotenv/LoaderTest.php @@ -7,19 +7,21 @@ class LoaderTest extends TestCase { /** - * @var \Dotenv\Loader + * @var string */ - private $loader; + protected $folder; + + /** + * @var string|null + */ + protected $keyVal; public function setUp() { - $folder = dirname(__DIR__).'/fixtures/env'; + $this->folder = dirname(__DIR__).'/fixtures/env'; $this->keyVal(true); - $this->loader = new Loader($folder, new DotenvFactory(), false); } - protected $keyVal; - /** * Generates a new key/value pair or returns the previous one. * @@ -66,31 +68,60 @@ protected function value() public function testMutableLoaderClearsEnvironmentVars() { + $loader = new Loader(["{$this->folder}/.env"], new DotenvFactory(), false); + // Set an environment variable. - $this->loader->setEnvironmentVariable($this->key(), $this->value()); + $loader->setEnvironmentVariable($this->key(), $this->value()); // Clear the set environment variable. - $this->loader->clearEnvironmentVariable($this->key()); - $this->assertSame(null, $this->loader->getEnvironmentVariable($this->key())); + $loader->clearEnvironmentVariable($this->key()); + $this->assertSame(null, $loader->getEnvironmentVariable($this->key())); $this->assertSame(false, getenv($this->key())); $this->assertSame(false, isset($_ENV[$this->key()])); $this->assertSame(false, isset($_SERVER[$this->key()])); - $this->assertSame([$this->key()], $this->loader->getEnvironmentVariableNames()); + $this->assertSame([$this->key()], $loader->getEnvironmentVariableNames()); } public function testImmutableLoaderCannotClearEnvironmentVars() { - $this->loader->setImmutable(true); + $loader = new Loader(["{$this->folder}/.env"], new DotenvFactory(), false); + + $loader->setImmutable(true); // Set an environment variable. - $this->loader->setEnvironmentVariable($this->key(), $this->value()); + $loader->setEnvironmentVariable($this->key(), $this->value()); // Attempt to clear the environment variable, check that it fails. - $this->loader->clearEnvironmentVariable($this->key()); - $this->assertSame($this->value(), $this->loader->getEnvironmentVariable($this->key())); + $loader->clearEnvironmentVariable($this->key()); + $this->assertSame($this->value(), $loader->getEnvironmentVariable($this->key())); $this->assertSame($this->value(), getenv($this->key())); $this->assertSame(true, isset($_ENV[$this->key()])); $this->assertSame(true, isset($_SERVER[$this->key()])); - $this->assertSame([$this->key()], $this->loader->getEnvironmentVariableNames()); + $this->assertSame([$this->key()], $loader->getEnvironmentVariableNames()); + } + + /** + * @expectedException \Dotenv\Exception\InvalidPathException + * @expectedExceptionMessage At least one environment file path must be provided. + */ + public function testLoaderWithNoPaths() + { + (new Loader([], new DotenvFactory(), false))->load(); + } + + /** + * @expectedException \Dotenv\Exception\InvalidPathException + * @expectedExceptionMessage Unable to read any of the environment file(s) at + */ + public function testLoaderWithBadPaths() + { + (new Loader(["{$this->folder}/BAD1", "{$this->folder}/BAD2"], new DotenvFactory(), false))->load(); + } + + public function testLoaderWithOneGoodPath() + { + $loader = (new Loader(["{$this->folder}/BAD1", "{$this->folder}/.env"], new DotenvFactory(), false)); + + $this->assertCount(4, $loader->load()); } }