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