You are here

class DialogHelper in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 vendor/symfony/console/Helper/DialogHelper.php \Symfony\Component\Console\Helper\DialogHelper

The Dialog class provides helpers to interact with the user.

@author Fabien Potencier <fabien@symfony.com>

Hierarchy

Expanded class hierarchy of DialogHelper

Deprecated

since version 2.5, to be removed in 3.0. Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead.

2 files declare their use of DialogHelper
Application.php in vendor/symfony/console/Application.php
LegacyDialogHelperTest.php in vendor/symfony/console/Tests/Helper/LegacyDialogHelperTest.php

File

vendor/symfony/console/Helper/DialogHelper.php, line 25

Namespace

Symfony\Component\Console\Helper
View source
class DialogHelper extends InputAwareHelper {
  private $inputStream;
  private static $shell;
  private static $stty;
  public function __construct($triggerDeprecationError = true) {
    if ($triggerDeprecationError) {
      @trigger_error('"Symfony\\Component\\Console\\Helper\\DialogHelper" is deprecated since version 2.5 and will be removed in 3.0. Use "Symfony\\Component\\Console\\Helper\\QuestionHelper" instead.', E_USER_DEPRECATED);
    }
  }

  /**
   * Asks the user to select a value.
   *
   * @param OutputInterface $output       An Output instance
   * @param string|array    $question     The question to ask
   * @param array           $choices      List of choices to pick from
   * @param bool|string     $default      The default answer if the user enters nothing
   * @param bool|int        $attempts     Max number of times to ask before giving up (false by default, which means infinite)
   * @param string          $errorMessage Message which will be shown if invalid value from choice list would be picked
   * @param bool            $multiselect  Select more than one value separated by comma
   *
   * @return int|string|array The selected value or values (the key of the choices array)
   *
   * @throws \InvalidArgumentException
   */
  public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) {
    $width = max(array_map('strlen', array_keys($choices)));
    $messages = (array) $question;
    foreach ($choices as $key => $value) {
      $messages[] = sprintf("  [<info>%-{$width}s</info>] %s", $key, $value);
    }
    $output
      ->writeln($messages);
    $result = $this
      ->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) {

      // Collapse all spaces.
      $selectedChoices = str_replace(' ', '', $picked);
      if ($multiselect) {

        // Check for a separated comma values
        if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
          throw new \InvalidArgumentException(sprintf($errorMessage, $picked));
        }
        $selectedChoices = explode(',', $selectedChoices);
      }
      else {
        $selectedChoices = array(
          $picked,
        );
      }
      $multiselectChoices = array();
      foreach ($selectedChoices as $value) {
        if (empty($choices[$value])) {
          throw new \InvalidArgumentException(sprintf($errorMessage, $value));
        }
        $multiselectChoices[] = $value;
      }
      if ($multiselect) {
        return $multiselectChoices;
      }
      return $picked;
    }, $attempts, $default);
    return $result;
  }

  /**
   * Asks a question to the user.
   *
   * @param OutputInterface $output       An Output instance
   * @param string|array    $question     The question to ask
   * @param string          $default      The default answer if none is given by the user
   * @param array           $autocomplete List of values to autocomplete
   *
   * @return string The user answer
   *
   * @throws \RuntimeException If there is no data to read in the input stream
   */
  public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null) {
    if ($this->input && !$this->input
      ->isInteractive()) {
      return $default;
    }
    $output
      ->write($question);
    $inputStream = $this->inputStream ?: STDIN;
    if (null === $autocomplete || !$this
      ->hasSttyAvailable()) {
      $ret = fgets($inputStream, 4096);
      if (false === $ret) {
        throw new \RuntimeException('Aborted');
      }
      $ret = trim($ret);
    }
    else {
      $ret = '';
      $i = 0;
      $ofs = -1;
      $matches = $autocomplete;
      $numMatches = count($matches);
      $sttyMode = shell_exec('stty -g');

      // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
      shell_exec('stty -icanon -echo');

      // Add highlighted text style
      $output
        ->getFormatter()
        ->setStyle('hl', new OutputFormatterStyle('black', 'white'));

      // Read a keypress
      while (!feof($inputStream)) {
        $c = fread($inputStream, 1);

        // Backspace Character
        if ("" === $c) {
          if (0 === $numMatches && 0 !== $i) {
            --$i;

            // Move cursor backwards
            $output
              ->write("\33[1D");
          }
          if ($i === 0) {
            $ofs = -1;
            $matches = $autocomplete;
            $numMatches = count($matches);
          }
          else {
            $numMatches = 0;
          }

          // Pop the last character off the end of our string
          $ret = substr($ret, 0, $i);
        }
        elseif ("\33" === $c) {

          // Did we read an escape sequence?
          $c .= fread($inputStream, 2);

          // A = Up Arrow. B = Down Arrow
          if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
            if ('A' === $c[2] && -1 === $ofs) {
              $ofs = 0;
            }
            if (0 === $numMatches) {
              continue;
            }
            $ofs += 'A' === $c[2] ? -1 : 1;
            $ofs = ($numMatches + $ofs) % $numMatches;
          }
        }
        elseif (ord($c) < 32) {
          if ("\t" === $c || "\n" === $c) {
            if ($numMatches > 0 && -1 !== $ofs) {
              $ret = $matches[$ofs];

              // Echo out remaining chars for current match
              $output
                ->write(substr($ret, $i));
              $i = strlen($ret);
            }
            if ("\n" === $c) {
              $output
                ->write($c);
              break;
            }
            $numMatches = 0;
          }
          continue;
        }
        else {
          $output
            ->write($c);
          $ret .= $c;
          ++$i;
          $numMatches = 0;
          $ofs = 0;
          foreach ($autocomplete as $value) {

            // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
            if (0 === strpos($value, $ret) && $i !== strlen($value)) {
              $matches[$numMatches++] = $value;
            }
          }
        }

        // Erase characters from cursor to end of line
        $output
          ->write("\33[K");
        if ($numMatches > 0 && -1 !== $ofs) {

          // Save cursor position
          $output
            ->write("\0337");

          // Write highlighted text
          $output
            ->write('<hl>' . substr($matches[$ofs], $i) . '</hl>');

          // Restore cursor position
          $output
            ->write("\338");
        }
      }

      // Reset stty so it behaves normally again
      shell_exec(sprintf('stty %s', $sttyMode));
    }
    return strlen($ret) > 0 ? $ret : $default;
  }

  /**
   * Asks a confirmation to the user.
   *
   * The question will be asked until the user answers by nothing, yes, or no.
   *
   * @param OutputInterface $output   An Output instance
   * @param string|array    $question The question to ask
   * @param bool            $default  The default answer if the user enters nothing
   *
   * @return bool true if the user has confirmed, false otherwise
   */
  public function askConfirmation(OutputInterface $output, $question, $default = true) {
    $answer = 'z';
    while ($answer && !in_array(strtolower($answer[0]), array(
      'y',
      'n',
    ))) {
      $answer = $this
        ->ask($output, $question);
    }
    if (false === $default) {
      return $answer && 'y' == strtolower($answer[0]);
    }
    return !$answer || 'y' == strtolower($answer[0]);
  }

  /**
   * Asks a question to the user, the response is hidden.
   *
   * @param OutputInterface $output   An Output instance
   * @param string|array    $question The question
   * @param bool            $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
   *
   * @return string The answer
   *
   * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
   */
  public function askHiddenResponse(OutputInterface $output, $question, $fallback = true) {
    if ('\\' === DIRECTORY_SEPARATOR) {
      $exe = __DIR__ . '/../Resources/bin/hiddeninput.exe';

      // handle code running from a phar
      if ('phar:' === substr(__FILE__, 0, 5)) {
        $tmpExe = sys_get_temp_dir() . '/hiddeninput.exe';
        copy($exe, $tmpExe);
        $exe = $tmpExe;
      }
      $output
        ->write($question);
      $value = rtrim(shell_exec($exe));
      $output
        ->writeln('');
      if (isset($tmpExe)) {
        unlink($tmpExe);
      }
      return $value;
    }
    if ($this
      ->hasSttyAvailable()) {
      $output
        ->write($question);
      $sttyMode = shell_exec('stty -g');
      shell_exec('stty -echo');
      $value = fgets($this->inputStream ?: STDIN, 4096);
      shell_exec(sprintf('stty %s', $sttyMode));
      if (false === $value) {
        throw new \RuntimeException('Aborted');
      }
      $value = trim($value);
      $output
        ->writeln('');
      return $value;
    }
    if (false !== ($shell = $this
      ->getShell())) {
      $output
        ->write($question);
      $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword';
      $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
      $value = rtrim(shell_exec($command));
      $output
        ->writeln('');
      return $value;
    }
    if ($fallback) {
      return $this
        ->ask($output, $question);
    }
    throw new \RuntimeException('Unable to hide the response');
  }

  /**
   * Asks for a value and validates the response.
   *
   * The validator receives the data to validate. It must return the
   * validated data when the data is valid and throw an exception
   * otherwise.
   *
   * @param OutputInterface $output       An Output instance
   * @param string|array    $question     The question to ask
   * @param callable        $validator    A PHP callback
   * @param int|false       $attempts     Max number of times to ask before giving up (false by default, which means infinite)
   * @param string          $default      The default answer if none is given by the user
   * @param array           $autocomplete List of values to autocomplete
   *
   * @return mixed
   *
   * @throws \Exception When any of the validators return an error
   */
  public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null) {
    $that = $this;
    $interviewer = function () use ($output, $question, $default, $autocomplete, $that) {
      return $that
        ->ask($output, $question, $default, $autocomplete);
    };
    return $this
      ->validateAttempts($interviewer, $output, $validator, $attempts);
  }

  /**
   * Asks for a value, hide and validates the response.
   *
   * The validator receives the data to validate. It must return the
   * validated data when the data is valid and throw an exception
   * otherwise.
   *
   * @param OutputInterface $output    An Output instance
   * @param string|array    $question  The question to ask
   * @param callable        $validator A PHP callback
   * @param int|false       $attempts  Max number of times to ask before giving up (false by default, which means infinite)
   * @param bool            $fallback  In case the response can not be hidden, whether to fallback on non-hidden question or not
   *
   * @return string The response
   *
   * @throws \Exception        When any of the validators return an error
   * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
   */
  public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true) {
    $that = $this;
    $interviewer = function () use ($output, $question, $fallback, $that) {
      return $that
        ->askHiddenResponse($output, $question, $fallback);
    };
    return $this
      ->validateAttempts($interviewer, $output, $validator, $attempts);
  }

  /**
   * Sets the input stream to read from when interacting with the user.
   *
   * This is mainly useful for testing purpose.
   *
   * @param resource $stream The input stream
   */
  public function setInputStream($stream) {
    $this->inputStream = $stream;
  }

  /**
   * Returns the helper's input stream.
   *
   * @return resource|null The input stream or null if the default STDIN is used
   */
  public function getInputStream() {
    return $this->inputStream;
  }

  /**
   * {@inheritdoc}
   */
  public function getName() {
    return 'dialog';
  }

  /**
   * Return a valid Unix shell.
   *
   * @return string|bool The valid shell name, false in case no valid shell is found
   */
  private function getShell() {
    if (null !== self::$shell) {
      return self::$shell;
    }
    self::$shell = false;
    if (file_exists('/usr/bin/env')) {

      // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
      $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
      foreach (array(
        'bash',
        'zsh',
        'ksh',
        'csh',
      ) as $sh) {
        if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
          self::$shell = $sh;
          break;
        }
      }
    }
    return self::$shell;
  }
  private function hasSttyAvailable() {
    if (null !== self::$stty) {
      return self::$stty;
    }
    exec('stty 2>&1', $output, $exitcode);
    return self::$stty = $exitcode === 0;
  }

  /**
   * Validate an attempt.
   *
   * @param callable        $interviewer A callable that will ask for a question and return the result
   * @param OutputInterface $output      An Output instance
   * @param callable        $validator   A PHP callback
   * @param int|false       $attempts    Max number of times to ask before giving up ; false will ask infinitely
   *
   * @return string The validated response
   *
   * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
   */
  private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts) {
    $e = null;
    while (false === $attempts || $attempts--) {
      if (null !== $e) {
        $output
          ->writeln($this
          ->getHelperSet()
          ->get('formatter')
          ->formatBlock($e
          ->getMessage(), 'error'));
      }
      try {
        return call_user_func($validator, $interviewer());
      } catch (\Exception $e) {
      }
    }
    throw $e;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DialogHelper::$inputStream private property
DialogHelper::$shell private static property
DialogHelper::$stty private static property
DialogHelper::ask public function Asks a question to the user.
DialogHelper::askAndValidate public function Asks for a value and validates the response.
DialogHelper::askConfirmation public function Asks a confirmation to the user.
DialogHelper::askHiddenResponse public function Asks a question to the user, the response is hidden.
DialogHelper::askHiddenResponseAndValidate public function Asks for a value, hide and validates the response.
DialogHelper::getInputStream public function Returns the helper's input stream.
DialogHelper::getName public function Returns the canonical name of this helper. Overrides HelperInterface::getName
DialogHelper::getShell private function Return a valid Unix shell.
DialogHelper::hasSttyAvailable private function
DialogHelper::select public function Asks the user to select a value.
DialogHelper::setInputStream public function Sets the input stream to read from when interacting with the user.
DialogHelper::validateAttempts private function Validate an attempt.
DialogHelper::__construct public function
Helper::$helperSet protected property
Helper::formatMemory public static function
Helper::formatTime public static function
Helper::getHelperSet public function Gets the helper set associated with this helper. Overrides HelperInterface::getHelperSet
Helper::setHelperSet public function Sets the helper set associated with this helper. Overrides HelperInterface::setHelperSet
Helper::strlen public static function Returns the length of a string, using mb_strwidth if it is available.
Helper::strlenWithoutDecoration public static function
InputAwareHelper::$input protected property
InputAwareHelper::setInput public function Sets the Console Input. Overrides InputAwareInterface::setInput