Chris@17: stdin()->contents()); Chris@17: * } Chris@17: * } Chris@17: * Chris@17: * Command that reads from stdin or file via an option: Chris@17: * Chris@17: * /** Chris@17: * * @command cat Chris@17: * * @param string $file Chris@17: * * @default $file - Chris@17: * * / Chris@17: * public function cat(InputInterface $input) Chris@17: * { Chris@17: * $data = $this->stdin()->select($input, 'file')->contents(); Chris@17: * } Chris@17: * Chris@17: * Command that reads from stdin or file via an option: Chris@17: * Chris@17: * /** Chris@17: * * @command cat Chris@17: * * @option string $file Chris@17: * * @default $file - Chris@17: * * / Chris@17: * public function cat(InputInterface $input) Chris@17: * { Chris@17: * $data = $this->stdin()->select($input, 'file')->contents(); Chris@17: * } Chris@17: * Chris@17: * It is also possible to inject the selected stream into the input object, Chris@17: * e.g. if you want the contents of the source file to be fed to any Question Chris@17: * helper et. al. that the $input object is used with. Chris@17: * Chris@17: * /** Chris@17: * * @command example Chris@17: * * @option string $file Chris@17: * * @default $file - Chris@17: * * / Chris@17: * public function example(InputInterface $input) Chris@17: * { Chris@17: * $this->stdin()->setStream($input, 'file'); Chris@17: * } Chris@17: * Chris@17: * Chris@17: * Inject an alternate source for standard input in tests. Presumes that Chris@17: * the object under test gets a reference to the StdinHandler via dependency Chris@17: * injection from the container. Chris@17: * Chris@17: * $container->get('stdinHandler')->redirect($pathToTestStdinFileFixture); Chris@17: * Chris@17: * You may also inject your stdin file fixture stream into the $input object Chris@17: * as usual, and then use it with 'select()' or 'setStream()' as shown above. Chris@17: * Chris@17: * Finally, this class may also be used in absence of a dependency injection Chris@17: * container by using the static 'selectStream()' method: Chris@17: * Chris@17: * /** Chris@17: * * @command example Chris@17: * * @option string $file Chris@17: * * @default $file - Chris@17: * * / Chris@17: * public function example(InputInterface $input) Chris@17: * { Chris@17: * $data = StdinHandler::selectStream($input, 'file')->contents(); Chris@17: * } Chris@17: * Chris@17: * To test a method that uses this technique, simply inject your stdin Chris@17: * fixture into the $input object in your test: Chris@17: * Chris@17: * $input->setStream(fopen($pathToFixture, 'r')); Chris@17: */ Chris@17: class StdinHandler Chris@17: { Chris@17: protected $path; Chris@17: protected $stream; Chris@17: Chris@17: public static function selectStream(InputInterface $input, $optionOrArg) Chris@17: { Chris@17: $handler = new Self(); Chris@17: Chris@17: return $handler->setStream($input, $optionOrArg); Chris@17: } Chris@17: Chris@17: /** Chris@17: * hasPath returns 'true' if the stdin handler has a path to a file. Chris@17: * Chris@17: * @return bool Chris@17: */ Chris@17: public function hasPath() Chris@17: { Chris@17: // Once the stream has been opened, we mask the existence of the path. Chris@17: return !$this->hasStream() && !empty($this->path); Chris@17: } Chris@17: Chris@17: /** Chris@17: * hasStream returns 'true' if the stdin handler has opened a stream. Chris@17: * Chris@17: * @return bool Chris@17: */ Chris@17: public function hasStream() Chris@17: { Chris@17: return !empty($this->stream); Chris@17: } Chris@17: Chris@17: /** Chris@17: * path returns the path to any file that was set as a redirection Chris@17: * source, or `php://stdin` if none have been. Chris@17: * Chris@17: * @return string Chris@17: */ Chris@17: public function path() Chris@17: { Chris@17: return $this->path ?: 'php://stdin'; Chris@17: } Chris@17: Chris@17: /** Chris@17: * close closes the input stream if it was opened. Chris@17: */ Chris@17: public function close() Chris@17: { Chris@17: if ($this->hasStream()) { Chris@17: fclose($this->stream); Chris@17: $this->stream = null; Chris@17: } Chris@17: return $this; Chris@17: } Chris@17: Chris@17: /** Chris@17: * redirect specifies a path to a file that should serve as the Chris@17: * source to read from. If the input path is '-' or empty, Chris@17: * then output will be taken from php://stdin (or whichever source Chris@17: * was provided via the 'redirect' method). Chris@17: * Chris@17: * @return $this Chris@17: */ Chris@17: public function redirect($path) Chris@17: { Chris@17: if ($this->pathProvided($path)) { Chris@17: $this->path = $path; Chris@17: } Chris@17: Chris@17: return $this; Chris@17: } Chris@17: Chris@17: /** Chris@17: * select chooses the source of the input stream based on whether or Chris@17: * not the user provided the specified option or argument on the commandline. Chris@17: * Stdin is selected if there is no user selection. Chris@17: * Chris@17: * @param InputInterface $input Chris@17: * @param string $optionOrArg Chris@17: * @return $this Chris@17: */ Chris@17: public function select(InputInterface $input, $optionOrArg) Chris@17: { Chris@17: $this->redirect($this->getOptionOrArg($input, $optionOrArg)); Chris@17: if (!$this->hasPath() && ($input instanceof StreamableInputInterface)) { Chris@17: $this->stream = $input->getStream(); Chris@17: } Chris@17: Chris@17: return $this; Chris@17: } Chris@17: Chris@17: /** Chris@17: * getStream opens and returns the stdin stream (or redirect file). Chris@17: */ Chris@17: public function getStream() Chris@17: { Chris@17: if (!$this->hasStream()) { Chris@17: $this->stream = fopen($this->path(), 'r'); Chris@17: } Chris@17: return $this->stream; Chris@17: } Chris@17: Chris@17: /** Chris@17: * setStream functions like 'select', and also sets up the $input Chris@17: * object to read from the selected input stream e.g. when used Chris@17: * with a question helper. Chris@17: */ Chris@17: public function setStream(InputInterface $input, $optionOrArg) Chris@17: { Chris@17: $this->select($input, $optionOrArg); Chris@17: if ($input instanceof StreamableInputInterface) { Chris@17: $stream = $this->getStream(); Chris@17: $input->setStream($stream); Chris@17: } Chris@17: return $this; Chris@17: } Chris@17: Chris@17: /** Chris@17: * contents reads the entire contents of the standard input stream. Chris@17: * Chris@17: * @return string Chris@17: */ Chris@17: public function contents() Chris@17: { Chris@17: // Optimization: use file_get_contents if we have a path to a file Chris@17: // and the stream has not been opened yet. Chris@17: if (!$this->hasStream()) { Chris@17: return file_get_contents($this->path()); Chris@17: } Chris@17: $stream = $this->getStream(); Chris@17: stream_set_blocking($stream, false); // TODO: We did this in backend invoke. Necessary here? Chris@17: $contents = stream_get_contents($stream); Chris@17: $this->close(); Chris@17: Chris@17: return $contents; Chris@17: } Chris@17: Chris@17: /** Chris@17: * Returns 'true' if a path was specfied, and that path was not '-'. Chris@17: */ Chris@17: protected function pathProvided($path) Chris@17: { Chris@17: return !empty($path) && ($path != '-'); Chris@17: } Chris@17: Chris@17: protected function getOptionOrArg(InputInterface $input, $optionOrArg) Chris@17: { Chris@17: if ($input->hasOption($optionOrArg)) { Chris@17: return $input->getOption($optionOrArg); Chris@17: } Chris@17: return $input->getArgument($optionOrArg); Chris@17: } Chris@17: }