Skip to content
Snippets Groups Projects
Dotenv.php 7.16 KiB
Newer Older
<?php

namespace LakeDrops\Component\Composer;

use Composer\IO\IOInterface;
use Symfony\Component\Dotenv\Dotenv as SymfonyDotenv;

/**
 * Manages .env files.
 */
final class Dotenv {

  /**
   * Name of the plugin "owning" this instance, used as a prefix.
   *
   * @var string
   */
jurgenhaas's avatar
jurgenhaas committed
  private string $name;

  /**
   * The input-output object of the composer session.
   *
   * @var \Composer\IO\IOInterface
   */
jurgenhaas's avatar
jurgenhaas committed
  private IOInterface $io;

  /**
   * The Symfony Dotenv object.
   *
   * @var \Symfony\Component\Dotenv\Dotenv
   */
jurgenhaas's avatar
jurgenhaas committed
  private SymfonyDotenv $dotenv;
jurgenhaas's avatar
jurgenhaas committed
   * Array with all known environment variables.
   *
jurgenhaas's avatar
jurgenhaas committed
  private array $userEnv;
  /**
   * Dotenv constructor.
   *
   * @param string $name
   *   Name of the plugin "owning" this instance, used as a prefix.
   * @param \Composer\IO\IOInterface $io
   *   The input-output object of the composer session.
   */
  public function __construct(string $name, IOInterface $io) {
    $this->name = strtoupper($name);
    $this->io = $io;
    $this->dotenv = new SymfonyDotenv();
    $this->load();
  }

  /**
   * Receive a value from the environment with the project name as prefix.
   *
   * First of all, it prefixes the key with the plugin name and converts the
   * result into upper case. Then looking for the value with PHP's getenv and if
   * nothing found, it prompts the user for input if the session is interactive.
   * Finally, if the value is still not available, the default value is being
   * used.
   *
   * @param string $key
   *   The key of the environment variable.
   * @param string $prompt
   *   The text being displayed when prompting the user. Leave empty to use
   *   default value and never prompt the user.
   * @param string $default
   *   The default value.
   *
   * @return string
   *   The value.
   */
jurgenhaas's avatar
jurgenhaas committed
  public function receive(string $key, string $prompt, string $default = ''): string {
    return $this->receiveGlobal($this->name . '_' . $key, $prompt, $default);
  }

  /**
   * Receive a value from the environment.
   *
   * @param string $key
   *   The key of the environment variable.
   * @param string $prompt
   *   The text being displayed when prompting the user. Leave empty to use
   *   default value and never prompt the user.
   * @param string $default
   *   The default value.
   *
   * @return string
   *   The value.
   */
jurgenhaas's avatar
jurgenhaas committed
  public function receiveGlobal(string $key, string $prompt, string $default = ''): string {
    $key = strtoupper($key);
    $value = $_ENV[$key] ?? getenv($key);
      if (!empty($prompt) && $this->io->isInteractive()) {
        $value = $this->io->ask($prompt . ': ');
      }
        $value = $default;
      }
      $this->put($key, $value, TRUE);
    }
    else {
      $this->put($key, $value);
    }

    return $value;
  }

  /**
   * Set an environment variable.
   *
   * Sets the environment variable into a .env file only if there isn't an
   * environment variable present yet. The .env file will also be added to the
   * .gitignore file.
   *
   * @param string $key
   *   The name of the environment variable.
   * @param string $value
   *   The value of the environment variable.
   * @param bool $overwrite
jurgenhaas's avatar
jurgenhaas committed
   *   Whether an existing value should be overwritten.
jurgenhaas's avatar
jurgenhaas committed
  public function put(string $key, string $value, bool $overwrite = FALSE): void {
    $data = $this->parse();
    $key = strtoupper($key);
    if (!$overwrite && isset($data[$key])) {
      return;
    }
jurgenhaas's avatar
jurgenhaas committed
    if (isset($this->userEnv[$key]) && (string) $this->userEnv[$key] === $value) {
    $envContent = '';
    foreach ($data as $k => $v) {
      $envContent .= $k . '=' . $v . PHP_EOL;
    }
    file_put_contents('.env', $envContent);
    $this->load();
    Utils::gitIgnore('.env');
  }

  /**
   * Replace environment variables in keys and values of a given array.
   *
   * Credit: https://github.com/composer/composer/pull/5837
   *
   * @param mixed $data
   *   The keyed array in which to replace environment variables.
   *
   * @return array
   *   The same data with environment variables being replaced.
   */
  public function replaceEnvironmentVariables($data): array {
    foreach ($data as $key => $value) {
      unset($data[$key]);
      if (is_array($value)) {
        $data[$key] = $this->replaceEnvironmentVariables($value);
      }
      else {
        $this->findEnvironmentVariables($value, $key);
        $data[$key] = $value;
      }
    }
jurgenhaas's avatar
jurgenhaas committed
   * Identify and replace environment variables in a key pair.
jurgenhaas's avatar
jurgenhaas committed
   *   String in which to replace environment variable placeholders.
jurgenhaas's avatar
jurgenhaas committed
   *   String in which to replace environment variable placeholders.
   */
  private function findEnvironmentVariables(string &$item, string &$key): void {
jurgenhaas's avatar
jurgenhaas committed
    $pattern = '@\{\$env:([\w_]*)\}@i';
    preg_match_all($pattern, $item, $item_matches);
    preg_match_all($pattern, $key, $key_matches);

    if (!empty($item_matches[1])) {
      $item = $this->replaceItemEnvironmentVariables($item, $item_matches[1]);
    }
    if (!empty($key_matches[1])) {
      $key = $this->replaceItemEnvironmentVariables($key, $key_matches[1]);
    }
  }

  /**
jurgenhaas's avatar
jurgenhaas committed
   * Replace environment variables in a string.
jurgenhaas's avatar
jurgenhaas committed
   *   String in which to replace environment variable placeholders.
   * @param array $matches
jurgenhaas's avatar
jurgenhaas committed
   *   List of environment vairables to replace in the given string.
jurgenhaas's avatar
jurgenhaas committed
   *   The string with replaced environment variables.
   */
  private function replaceItemEnvironmentVariables(string $item, array $matches): string {
    foreach ($matches as $var) {
      $value = $_ENV[$var] ?? getenv($var);
jurgenhaas's avatar
jurgenhaas committed
      $item = str_replace('{$env:' . $var . '}', $value, $item);
    }
    return $item;
  }

  /**
   * Parse the local .env file and return the content as an array.
   *
jurgenhaas's avatar
jurgenhaas committed
   *   WHether to also include the environment variable from the user's home
   *   directory.
jurgenhaas's avatar
jurgenhaas committed
   *   List of all environment variables.
jurgenhaas's avatar
jurgenhaas committed
  private function parse(bool $include_user = FALSE): array {
    $userEnv = getenv("HOME") . '/.env';
    if ($include_user && file_exists($userEnv)) {
      $userContent = file_get_contents($userEnv);
      $this->userEnv = $this->dotenv->parse($userContent, $userEnv);
      $envContent .= PHP_EOL . $userContent;
    $envContent .= file_exists('.env') ? file_get_contents('.env') : '';
    return $this->dotenv->parse($envContent);
  }

  /**
   * Load the local .env file.
   */
  private function load(): void {
    try {
      foreach ($this->parse(TRUE) as $key => $value) {
      $userEnv = getenv("HOME") . '/.env';
      if (file_exists($userEnv) && file_exists('.env')) {
        $this->dotenv->load($userEnv, '.env');
      }
      elseif (file_exists($userEnv)) {
        $this->dotenv->load($userEnv);
      }
      elseif (file_exists('.env')) {
        $this->dotenv->load('.env');
      }
jurgenhaas's avatar
jurgenhaas committed
    catch (\Exception $ex) {