diff --git a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php index b23689671d18..7b2e43c0c5be 100644 --- a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php +++ b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php @@ -5,12 +5,16 @@ use Composer\InstalledVersions; use Illuminate\Console\Command; use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Env; use Illuminate\Support\Facades\Process; use Symfony\Component\Console\Attribute\AsCommand; use function Illuminate\Support\artisan_binary; use function Illuminate\Support\php_binary; use function Laravel\Prompts\confirm; +use function Laravel\Prompts\password; +use function Laravel\Prompts\select; +use function Laravel\Prompts\text; #[AsCommand(name: 'install:broadcasting')] class BroadcastingInstallCommand extends Command @@ -26,6 +30,9 @@ class BroadcastingInstallCommand extends Command {--composer=global : Absolute path to the Composer binary which should be used to install packages} {--force : Overwrite any existing broadcasting routes file} {--without-reverb : Do not prompt to install Laravel Reverb} + {--reverb : Install Laravel Reverb as the default broadcaster} + {--pusher : Install Pusher as the default broadcaster} + {--ably : Install Ably as the default broadcaster} {--without-node : Do not prompt to install Node dependencies}'; /** @@ -35,6 +42,23 @@ class BroadcastingInstallCommand extends Command */ protected $description = 'Create a broadcasting channel routes file'; + /** + * The broadcasting driver to use. + * + * @var string|null + */ + protected $driver = null; + + /** + * The framework packages to install. + * + * @var array + */ + protected $frameworkPackages = [ + 'react' => '@laravel/echo-react', + 'vue' => '@laravel/echo-vue', + ]; + /** * Execute the console command. * @@ -54,25 +78,44 @@ public function handle() $this->uncommentChannelsRoutesFile(); $this->enableBroadcastServiceProvider(); - // Install bootstrapping... - if (! file_exists($echoScriptPath = $this->laravel->resourcePath('js/echo.js'))) { - if (! is_dir($directory = $this->laravel->resourcePath('js'))) { - mkdir($directory, 0755, true); - } + $this->driver = $this->resolveDriver(); - copy(__DIR__.'/stubs/echo-js.stub', $echoScriptPath); - } + Env::writeVariable('BROADCAST_CONNECTION', $this->driver, $this->laravel->basePath('.env'), true); - if (file_exists($bootstrapScriptPath = $this->laravel->resourcePath('js/bootstrap.js'))) { - $bootstrapScript = file_get_contents( - $bootstrapScriptPath - ); + $this->collectDriverConfig(); + $this->installDriverPackages(); + + if ($this->isUsingSupportedFramework()) { + // If this is a supported framework, we will use the framework-specific Echo helpers... + $this->injectFrameworkSpecificConfiguration(); + } else { + // Standard JavaScript implementation... + if (! file_exists($echoScriptPath = $this->laravel->resourcePath('js/echo.js'))) { + if (! is_dir($directory = $this->laravel->resourcePath('js'))) { + mkdir($directory, 0755, true); + } + + $stubPath = __DIR__.'/stubs/echo-js-'.$this->driver.'.stub'; + + if (! file_exists($stubPath)) { + $stubPath = __DIR__.'/stubs/echo-js-reverb.stub'; + } + + copy($stubPath, $echoScriptPath); + } - if (! str_contains($bootstrapScript, './echo')) { - file_put_contents( - $bootstrapScriptPath, - trim($bootstrapScript.PHP_EOL.file_get_contents(__DIR__.'/stubs/echo-bootstrap-js.stub')).PHP_EOL, + // Only add the bootstrap import for the standard JS implementation... + if (file_exists($bootstrapScriptPath = $this->laravel->resourcePath('js/bootstrap.js'))) { + $bootstrapScript = file_get_contents( + $bootstrapScriptPath ); + + if (! str_contains($bootstrapScript, './echo')) { + file_put_contents( + $bootstrapScriptPath, + trim($bootstrapScript.PHP_EOL.file_get_contents(__DIR__.'/stubs/echo-bootstrap-js.stub')).PHP_EOL, + ); + } } } @@ -118,8 +161,10 @@ protected function enableBroadcastServiceProvider() { $filesystem = new Filesystem; - if (! $filesystem->exists(app()->configPath('app.php')) || - ! $filesystem->exists('app/Providers/BroadcastServiceProvider.php')) { + if ( + ! $filesystem->exists(app()->configPath('app.php')) || + ! $filesystem->exists('app/Providers/BroadcastServiceProvider.php') + ) { return; } @@ -134,6 +179,171 @@ protected function enableBroadcastServiceProvider() } } + /** + * Collect the driver configuration. + * + * @return void + */ + protected function collectDriverConfig() + { + $envPath = $this->laravel->basePath('.env'); + + if (! file_exists($envPath)) { + return; + } + + match ($this->driver) { + 'pusher' => $this->collectPusherConfig(), + 'ably' => $this->collectAblyConfig(), + default => null, + }; + } + + /** + * Install the driver packages. + * + * @return void + */ + protected function installDriverPackages() + { + $package = match ($this->driver) { + 'pusher' => 'pusher/pusher-php-server', + 'ably' => 'ably/ably-php', + default => null, + }; + + if (! $package || InstalledVersions::isInstalled($package)) { + return; + } + + $this->requireComposerPackages($this->option('composer'), [$package]); + } + + /** + * Collect the Pusher configuration. + * + * @return void + */ + protected function collectPusherConfig() + { + $appId = text('Pusher App ID', 'Enter your Pusher app ID'); + $key = password('Pusher App Key', 'Enter your Pusher app key'); + $secret = password('Pusher App Secret', 'Enter your Pusher app secret'); + + $cluster = select('Pusher App Cluster', [ + 'mt1', + 'us2', + 'us3', + 'eu', + 'ap1', + 'ap2', + 'ap3', + 'ap4', + 'sa1', + ]); + + Env::writeVariables([ + 'PUSHER_APP_ID' => $appId, + 'PUSHER_APP_KEY' => $key, + 'PUSHER_APP_SECRET' => $secret, + 'PUSHER_APP_CLUSTER' => $cluster, + 'PUSHER_PORT' => 443, + 'PUSHER_SCHEME' => 'https', + 'VITE_PUSHER_APP_KEY' => '${PUSHER_APP_KEY}', + 'VITE_PUSHER_APP_CLUSTER' => '${PUSHER_APP_CLUSTER}', + 'VITE_PUSHER_HOST' => '${PUSHER_HOST}', + 'VITE_PUSHER_PORT' => '${PUSHER_PORT}', + 'VITE_PUSHER_SCHEME' => '${PUSHER_SCHEME}', + ], $this->laravel->basePath('.env')); + } + + /** + * Collect the Ably configuration. + * + * @return void + */ + protected function collectAblyConfig() + { + $this->components->warn('Make sure to enable "Pusher protocol support" in your Ably app settings.'); + + $key = password('Ably Key', 'Enter your Ably key'); + + $publicKey = explode(':', $key)[0] ?? $key; + + Env::writeVariables([ + 'ABLY_KEY' => $key, + 'ABLY_PUBLIC_KEY' => $publicKey, + 'VITE_ABLY_PUBLIC_KEY' => '${ABLY_PUBLIC_KEY}', + ], $this->laravel->basePath('.env')); + } + + /** + * Inject Echo configuration into the application's main file. + * + * @return void + */ + protected function injectFrameworkSpecificConfiguration() + { + if ($this->appUsesVue()) { + $importPath = $this->frameworkPackages['vue']; + + $filePaths = [ + $this->laravel->resourcePath('js/app.ts'), + $this->laravel->resourcePath('js/app.js'), + ]; + } else { + $importPath = $this->frameworkPackages['react']; + + $filePaths = [ + $this->laravel->resourcePath('js/app.tsx'), + $this->laravel->resourcePath('js/app.jsx'), + ]; + } + + $filePath = array_filter($filePaths, function ($path) { + return file_exists($path); + })[0] ?? null; + + if (! $filePath) { + $this->components->warn("Could not find file [{$filePaths[0]}]. Skipping automatic Echo configuration."); + + return; + } + + $contents = file_get_contents($filePath); + + $echoCode = <<driver}', + }); + JS; + + preg_match_all('/^import .+;$/m', $contents, $matches); + + if (empty($matches[0])) { + // Add the Echo configuration to the top of the file if no import statements are found... + $newContents = $echoCode.PHP_EOL.$contents; + + file_put_contents($filePath, $newContents); + } else { + // Add Echo configuration after the last import... + $lastImport = end($matches[0]); + + $positionOfLastImport = strrpos($contents, $lastImport); + + if ($positionOfLastImport !== false) { + $insertPosition = $positionOfLastImport + strlen($lastImport); + $newContents = substr($contents, 0, $insertPosition).PHP_EOL.$echoCode.substr($contents, $insertPosition); + + file_put_contents($filePath, $newContents); + } + } + + $this->components->info('Echo configuration added to ['.basename($filePath).'].'); + } + /** * Install Laravel Reverb into the application if desired. * @@ -141,7 +351,7 @@ protected function enableBroadcastServiceProvider() */ protected function installReverb() { - if ($this->option('without-reverb') || InstalledVersions::isInstalled('laravel/reverb')) { + if ($this->driver !== 'reverb' || $this->option('without-reverb') || InstalledVersions::isInstalled('laravel/reverb')) { return; } @@ -199,6 +409,12 @@ protected function installNodeDependencies() ]; } + if ($this->appUsesVue()) { + $commands[0] .= ' '.$this->frameworkPackages['vue']; + } elseif ($this->appUsesReact()) { + $commands[0] .= ' '.$this->frameworkPackages['react']; + } + $command = Process::command(implode(' && ', $commands)) ->path(base_path()); @@ -212,4 +428,79 @@ protected function installNodeDependencies() $this->components->info('Node dependencies installed successfully.'); } } + + /** + * Resolve the provider to use based on the user's choice. + * + * @return string + */ + protected function resolveDriver(): string + { + if ($this->option('reverb')) { + return 'reverb'; + } + + if ($this->option('pusher')) { + return 'pusher'; + } + + if ($this->option('ably')) { + return 'ably'; + } + + return select('Which broadcasting driver would you like to use?', [ + 'reverb' => 'Laravel Reverb', + 'pusher' => 'Pusher', + 'ably' => 'Ably', + ]); + } + + /** + * Detect if the user is using a supported framework (React or Vue). + * + * @return bool + */ + protected function isUsingSupportedFramework(): bool + { + return $this->appUsesReact() || $this->appUsesVue(); + } + + /** + * Detect if the user is using React. + * + * @return bool + */ + protected function appUsesReact(): bool + { + return $this->packageDependenciesInclude('react'); + } + + /** + * Detect if the user is using Vue. + * + * @return bool + */ + protected function appUsesVue(): bool + { + return $this->packageDependenciesInclude('vue'); + } + + /** + * Detect if the package is installed. + * + * @return bool + */ + protected function packageDependenciesInclude(string $package): bool + { + $packageJsonPath = $this->laravel->basePath('package.json'); + + if (! file_exists($packageJsonPath)) { + return false; + } + + $packageJson = json_decode(file_get_contents($packageJsonPath), true); + + return isset($packageJson['dependencies'][$package]) || + isset($packageJson['devDependencies'][$package]); + } } diff --git a/src/Illuminate/Foundation/Console/stubs/echo-js-ably.stub b/src/Illuminate/Foundation/Console/stubs/echo-js-ably.stub new file mode 100644 index 000000000000..ec518d214668 --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/echo-js-ably.stub @@ -0,0 +1,13 @@ +import Echo from 'laravel-echo'; + +import Pusher from 'pusher-js'; +window.Pusher = Pusher; + +window.Echo = new Echo({ + broadcaster: "pusher", + key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + wsHost: "realtime-pusher.ably.io", + wsPort: 443, + disableStats: true, + encrypted: true, +}); diff --git a/src/Illuminate/Foundation/Console/stubs/echo-js-pusher.stub b/src/Illuminate/Foundation/Console/stubs/echo-js-pusher.stub new file mode 100644 index 000000000000..5a8a7f7e31ef --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/echo-js-pusher.stub @@ -0,0 +1,15 @@ +import Echo from 'laravel-echo'; + +import Pusher from 'pusher-js'; +window.Pusher = Pusher; + +window.Echo = new Echo({ + broadcaster: "pusher", + key: import.meta.env.VITE_PUSHER_APP_KEY, + cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + forceTLS: true, + wsHost: import.meta.env.VITE_PUSHER_HOST, + wsPort: import.meta.env.VITE_PUSHER_PORT, + wssPort: import.meta.env.VITE_PUSHER_PORT, + enabledTransports: ["ws", "wss"], +}); diff --git a/src/Illuminate/Foundation/Console/stubs/echo-js.stub b/src/Illuminate/Foundation/Console/stubs/echo-js-reverb.stub similarity index 100% rename from src/Illuminate/Foundation/Console/stubs/echo-js.stub rename to src/Illuminate/Foundation/Console/stubs/echo-js-reverb.stub diff --git a/src/Illuminate/Support/Env.php b/src/Illuminate/Support/Env.php index 702f61d44f4c..a52c10bd4f2a 100644 --- a/src/Illuminate/Support/Env.php +++ b/src/Illuminate/Support/Env.php @@ -5,6 +5,8 @@ use Closure; use Dotenv\Repository\Adapter\PutenvAdapter; use Dotenv\Repository\RepositoryBuilder; +use Illuminate\Contracts\Filesystem\FileNotFoundException; +use Illuminate\Filesystem\Filesystem; use PhpOption\Option; use RuntimeException; @@ -114,6 +116,133 @@ public static function getOrFail($key) return self::getOption($key)->getOrThrow(new RuntimeException("Environment variable [$key] has no value.")); } + /** + * Write an array of key-value pairs to the environment file. + * + * @param array $variables + * @param string $pathToFile + * @param bool $overwrite + * @return void + * + * @throws RuntimeException + * @throws FileNotFoundException + */ + public static function writeVariables(array $variables, string $pathToFile, bool $overwrite = false): void + { + $filesystem = new Filesystem; + + if ($filesystem->missing($pathToFile)) { + throw new RuntimeException("The file [{$pathToFile}] does not exist."); + } + + $lines = explode(PHP_EOL, $filesystem->get($pathToFile)); + + foreach ($variables as $key => $value) { + $lines = self::addVariableToEnvContents($key, $value, $lines, $overwrite); + } + + $filesystem->put($pathToFile, implode(PHP_EOL, $lines)); + } + + /** + * Write a single key-value pair to the environment file. + * + * @param string $key + * @param mixed $value + * @param string $pathToFile + * @param bool $overwrite + * @return void + * + * @throws RuntimeException + * @throws FileNotFoundException + */ + public static function writeVariable(string $key, mixed $value, string $pathToFile, bool $overwrite = false): void + { + $filesystem = new Filesystem; + + if ($filesystem->missing($pathToFile)) { + throw new RuntimeException("The file [{$pathToFile}] does not exist."); + } + + $envContent = $filesystem->get($pathToFile); + + $lines = explode(PHP_EOL, $envContent); + $lines = self::addVariableToEnvContents($key, $value, $lines, $overwrite); + + $filesystem->put($pathToFile, implode(PHP_EOL, $lines)); + } + + /** + * Add a variable to the environment file contents. + * + * @param string $key + * @param mixed $value + * @param array $envLines + * @param bool $overwrite + * @return array + */ + protected static function addVariableToEnvContents(string $key, mixed $value, array $envLines, bool $overwrite): array + { + $prefix = explode('_', $key)[0].'_'; + $lastPrefixIndex = -1; + + $shouldQuote = preg_match('/^[a-zA-z0-9]+$/', $value) === 0; + + $lineToAddVariations = [ + $key.'='.(is_string($value) ? '"'.addslashes($value).'"' : $value), + $key.'='.(is_string($value) ? "'".addslashes($value)."'" : $value), + $key.'='.$value, + ]; + + $lineToAdd = $shouldQuote ? $lineToAddVariations[0] : $lineToAddVariations[2]; + + if ($value === '') { + $lineToAdd = $key.'='; + } + + foreach ($envLines as $index => $line) { + if (str_starts_with($line, $prefix)) { + $lastPrefixIndex = $index; + } + + if (in_array($line, $lineToAddVariations)) { + // This exact line already exists, so we don't need to add it again. + return $envLines; + } + + if ($line === $key.'=') { + // If the value is empty, we can replace it with the new value. + $envLines[$index] = $lineToAdd; + + return $envLines; + } + + if (str_starts_with($line, $key.'=')) { + if (! $overwrite) { + return $envLines; + } + + $envLines[$index] = $lineToAdd; + + return $envLines; + } + } + + if ($lastPrefixIndex === -1) { + if (count($envLines) && $envLines[count($envLines) - 1] !== '') { + $envLines[] = ''; + } + + return array_merge($envLines, [$lineToAdd]); + } + + return array_merge( + array_slice($envLines, 0, $lastPrefixIndex + 1), + [$lineToAdd], + array_slice($envLines, $lastPrefixIndex + 1) + ); + } + /** * Get the possible option for this environment variable. * diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index ee67f818a3cd..1074e97dd6d9 100644 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -8,6 +8,7 @@ use Error; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Env; use Illuminate\Support\Optional; use Illuminate\Support\Sleep; @@ -26,10 +27,21 @@ class SupportHelpersTest extends TestCase { + protected function setUp(): void + { + mkdir(__DIR__.'/tmp'); + + parent::setUp(); + } + protected function tearDown(): void { m::close(); + if (is_dir(__DIR__.'/tmp')) { + (new Filesystem)->deleteDirectory(__DIR__.'/tmp'); + } + parent::tearDown(); } @@ -747,11 +759,13 @@ class_uses_recursive(SupportTestClassThree::class) public function testTraitUsesRecursive() { - $this->assertSame([ - 'Illuminate\Tests\Support\SupportTestTraitTwo' => 'Illuminate\Tests\Support\SupportTestTraitTwo', - 'Illuminate\Tests\Support\SupportTestTraitOne' => 'Illuminate\Tests\Support\SupportTestTraitOne', - ], - trait_uses_recursive(SupportTestClassOne::class)); + $this->assertSame( + [ + 'Illuminate\Tests\Support\SupportTestTraitTwo' => 'Illuminate\Tests\Support\SupportTestTraitTwo', + 'Illuminate\Tests\Support\SupportTestTraitOne' => 'Illuminate\Tests\Support\SupportTestTraitOne', + ], + trait_uses_recursive(SupportTestClassOne::class) + ); $this->assertSame([], trait_uses_recursive(SupportTestClassTwo::class)); } @@ -1212,6 +1226,232 @@ public function testEnvEscapedString() $this->assertSame('x"null"x', env('foo')); } + public function testWriteArrayOfEnvVariablesToFile() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + 'DB_PORT' => 3306, + 'BRAND_NEW_PREFIX' => 'fresh value', + ], $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST="127:0:0:1"', + 'DB_PORT=3306', + '', + 'BRAND_NEW_PREFIX="fresh value"', + ]), + $filesystem->get($path) + ); + } + + public function testWriteArrayOfEnvVariablesToFileAndOverwrite() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + 'DB_CONNECTION' => 'sqlite', + ], $path, true); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=sqlite', + 'DB_HOST="127:0:0:1"', + ]), + $filesystem->get($path) + ); + } + + public function testWillNotOverwriteArrayOfVariables() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + ], $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST="127:0:0:1"', + ]), + $filesystem->get($path) + ); + } + + public function testWriteVariableToFile() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariable('APP_VIBE', 'chill', $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ]), + $filesystem->get($path) + ); + } + + public function testWillNotOverwriteVariable() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariable('APP_VIBE', 'chill', $path); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ]), + $filesystem->get($path) + ); + } + + public function testWriteVariableToFileAndOverwrite() + { + $filesystem = new Filesystem; + $path = __DIR__.'/tmp/env-test-file'; + $filesystem->put($path, implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=odd', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ])); + + Env::writeVariable('APP_VIBE', 'chill', $path, true); + + $this->assertSame( + implode(PHP_EOL, [ + 'APP_NAME=Laravel', + 'APP_ENV=local', + 'APP_KEY=base64:randomkey', + 'APP_DEBUG=true', + 'APP_URL=http://localhost', + 'APP_VIBE=chill', + '', + 'DB_CONNECTION=mysql', + 'DB_HOST=', + ]), + $filesystem->get($path) + ); + } + + public function testWillThrowAnExceptionIfFileIsMissingWhenTryingToWriteVariables(): void + { + $this->expectExceptionObject(new RuntimeException('The file [missing-file] does not exist.')); + + Env::writeVariables([ + 'APP_VIBE' => 'chill', + 'DB_HOST' => '127:0:0:1', + ], 'missing-file'); + } + public function testGetFromSERVERFirst() { $_ENV['foo'] = 'From $_ENV';