Chris@17
|
1 <?php
|
Chris@17
|
2
|
Chris@17
|
3 namespace Consolidation\AnnotatedCommand\Input;
|
Chris@17
|
4
|
Chris@17
|
5 use Symfony\Component\Console\Input\StreamableInputInterface;
|
Chris@17
|
6 use Symfony\Component\Console\Input\InputInterface;
|
Chris@17
|
7
|
Chris@17
|
8 /**
|
Chris@17
|
9 * StdinHandler is a thin wrapper around php://stdin. It provides
|
Chris@17
|
10 * methods for redirecting input from a file, possibly conditionally
|
Chris@17
|
11 * under the control of an Input object.
|
Chris@17
|
12 *
|
Chris@17
|
13 * Example trivial usage (always reads from stdin):
|
Chris@17
|
14 *
|
Chris@17
|
15 * class Example implements StdinAwareInterface
|
Chris@17
|
16 * {
|
Chris@17
|
17 * /**
|
Chris@17
|
18 * * @command cat
|
Chris@17
|
19 * * @param string $file
|
Chris@17
|
20 * * @default $file -
|
Chris@17
|
21 * * /
|
Chris@17
|
22 * public function cat()
|
Chris@17
|
23 * {
|
Chris@17
|
24 * print($this->stdin()->contents());
|
Chris@17
|
25 * }
|
Chris@17
|
26 * }
|
Chris@17
|
27 *
|
Chris@17
|
28 * Command that reads from stdin or file via an option:
|
Chris@17
|
29 *
|
Chris@17
|
30 * /**
|
Chris@17
|
31 * * @command cat
|
Chris@17
|
32 * * @param string $file
|
Chris@17
|
33 * * @default $file -
|
Chris@17
|
34 * * /
|
Chris@17
|
35 * public function cat(InputInterface $input)
|
Chris@17
|
36 * {
|
Chris@17
|
37 * $data = $this->stdin()->select($input, 'file')->contents();
|
Chris@17
|
38 * }
|
Chris@17
|
39 *
|
Chris@17
|
40 * Command that reads from stdin or file via an option:
|
Chris@17
|
41 *
|
Chris@17
|
42 * /**
|
Chris@17
|
43 * * @command cat
|
Chris@17
|
44 * * @option string $file
|
Chris@17
|
45 * * @default $file -
|
Chris@17
|
46 * * /
|
Chris@17
|
47 * public function cat(InputInterface $input)
|
Chris@17
|
48 * {
|
Chris@17
|
49 * $data = $this->stdin()->select($input, 'file')->contents();
|
Chris@17
|
50 * }
|
Chris@17
|
51 *
|
Chris@17
|
52 * It is also possible to inject the selected stream into the input object,
|
Chris@17
|
53 * e.g. if you want the contents of the source file to be fed to any Question
|
Chris@17
|
54 * helper et. al. that the $input object is used with.
|
Chris@17
|
55 *
|
Chris@17
|
56 * /**
|
Chris@17
|
57 * * @command example
|
Chris@17
|
58 * * @option string $file
|
Chris@17
|
59 * * @default $file -
|
Chris@17
|
60 * * /
|
Chris@17
|
61 * public function example(InputInterface $input)
|
Chris@17
|
62 * {
|
Chris@17
|
63 * $this->stdin()->setStream($input, 'file');
|
Chris@17
|
64 * }
|
Chris@17
|
65 *
|
Chris@17
|
66 *
|
Chris@17
|
67 * Inject an alternate source for standard input in tests. Presumes that
|
Chris@17
|
68 * the object under test gets a reference to the StdinHandler via dependency
|
Chris@17
|
69 * injection from the container.
|
Chris@17
|
70 *
|
Chris@17
|
71 * $container->get('stdinHandler')->redirect($pathToTestStdinFileFixture);
|
Chris@17
|
72 *
|
Chris@17
|
73 * You may also inject your stdin file fixture stream into the $input object
|
Chris@17
|
74 * as usual, and then use it with 'select()' or 'setStream()' as shown above.
|
Chris@17
|
75 *
|
Chris@17
|
76 * Finally, this class may also be used in absence of a dependency injection
|
Chris@17
|
77 * container by using the static 'selectStream()' method:
|
Chris@17
|
78 *
|
Chris@17
|
79 * /**
|
Chris@17
|
80 * * @command example
|
Chris@17
|
81 * * @option string $file
|
Chris@17
|
82 * * @default $file -
|
Chris@17
|
83 * * /
|
Chris@17
|
84 * public function example(InputInterface $input)
|
Chris@17
|
85 * {
|
Chris@17
|
86 * $data = StdinHandler::selectStream($input, 'file')->contents();
|
Chris@17
|
87 * }
|
Chris@17
|
88 *
|
Chris@17
|
89 * To test a method that uses this technique, simply inject your stdin
|
Chris@17
|
90 * fixture into the $input object in your test:
|
Chris@17
|
91 *
|
Chris@17
|
92 * $input->setStream(fopen($pathToFixture, 'r'));
|
Chris@17
|
93 */
|
Chris@17
|
94 class StdinHandler
|
Chris@17
|
95 {
|
Chris@17
|
96 protected $path;
|
Chris@17
|
97 protected $stream;
|
Chris@17
|
98
|
Chris@17
|
99 public static function selectStream(InputInterface $input, $optionOrArg)
|
Chris@17
|
100 {
|
Chris@17
|
101 $handler = new Self();
|
Chris@17
|
102
|
Chris@17
|
103 return $handler->setStream($input, $optionOrArg);
|
Chris@17
|
104 }
|
Chris@17
|
105
|
Chris@17
|
106 /**
|
Chris@17
|
107 * hasPath returns 'true' if the stdin handler has a path to a file.
|
Chris@17
|
108 *
|
Chris@17
|
109 * @return bool
|
Chris@17
|
110 */
|
Chris@17
|
111 public function hasPath()
|
Chris@17
|
112 {
|
Chris@17
|
113 // Once the stream has been opened, we mask the existence of the path.
|
Chris@17
|
114 return !$this->hasStream() && !empty($this->path);
|
Chris@17
|
115 }
|
Chris@17
|
116
|
Chris@17
|
117 /**
|
Chris@17
|
118 * hasStream returns 'true' if the stdin handler has opened a stream.
|
Chris@17
|
119 *
|
Chris@17
|
120 * @return bool
|
Chris@17
|
121 */
|
Chris@17
|
122 public function hasStream()
|
Chris@17
|
123 {
|
Chris@17
|
124 return !empty($this->stream);
|
Chris@17
|
125 }
|
Chris@17
|
126
|
Chris@17
|
127 /**
|
Chris@17
|
128 * path returns the path to any file that was set as a redirection
|
Chris@17
|
129 * source, or `php://stdin` if none have been.
|
Chris@17
|
130 *
|
Chris@17
|
131 * @return string
|
Chris@17
|
132 */
|
Chris@17
|
133 public function path()
|
Chris@17
|
134 {
|
Chris@17
|
135 return $this->path ?: 'php://stdin';
|
Chris@17
|
136 }
|
Chris@17
|
137
|
Chris@17
|
138 /**
|
Chris@17
|
139 * close closes the input stream if it was opened.
|
Chris@17
|
140 */
|
Chris@17
|
141 public function close()
|
Chris@17
|
142 {
|
Chris@17
|
143 if ($this->hasStream()) {
|
Chris@17
|
144 fclose($this->stream);
|
Chris@17
|
145 $this->stream = null;
|
Chris@17
|
146 }
|
Chris@17
|
147 return $this;
|
Chris@17
|
148 }
|
Chris@17
|
149
|
Chris@17
|
150 /**
|
Chris@17
|
151 * redirect specifies a path to a file that should serve as the
|
Chris@17
|
152 * source to read from. If the input path is '-' or empty,
|
Chris@17
|
153 * then output will be taken from php://stdin (or whichever source
|
Chris@17
|
154 * was provided via the 'redirect' method).
|
Chris@17
|
155 *
|
Chris@17
|
156 * @return $this
|
Chris@17
|
157 */
|
Chris@17
|
158 public function redirect($path)
|
Chris@17
|
159 {
|
Chris@17
|
160 if ($this->pathProvided($path)) {
|
Chris@17
|
161 $this->path = $path;
|
Chris@17
|
162 }
|
Chris@17
|
163
|
Chris@17
|
164 return $this;
|
Chris@17
|
165 }
|
Chris@17
|
166
|
Chris@17
|
167 /**
|
Chris@17
|
168 * select chooses the source of the input stream based on whether or
|
Chris@17
|
169 * not the user provided the specified option or argument on the commandline.
|
Chris@17
|
170 * Stdin is selected if there is no user selection.
|
Chris@17
|
171 *
|
Chris@17
|
172 * @param InputInterface $input
|
Chris@17
|
173 * @param string $optionOrArg
|
Chris@17
|
174 * @return $this
|
Chris@17
|
175 */
|
Chris@17
|
176 public function select(InputInterface $input, $optionOrArg)
|
Chris@17
|
177 {
|
Chris@17
|
178 $this->redirect($this->getOptionOrArg($input, $optionOrArg));
|
Chris@17
|
179 if (!$this->hasPath() && ($input instanceof StreamableInputInterface)) {
|
Chris@17
|
180 $this->stream = $input->getStream();
|
Chris@17
|
181 }
|
Chris@17
|
182
|
Chris@17
|
183 return $this;
|
Chris@17
|
184 }
|
Chris@17
|
185
|
Chris@17
|
186 /**
|
Chris@17
|
187 * getStream opens and returns the stdin stream (or redirect file).
|
Chris@17
|
188 */
|
Chris@17
|
189 public function getStream()
|
Chris@17
|
190 {
|
Chris@17
|
191 if (!$this->hasStream()) {
|
Chris@17
|
192 $this->stream = fopen($this->path(), 'r');
|
Chris@17
|
193 }
|
Chris@17
|
194 return $this->stream;
|
Chris@17
|
195 }
|
Chris@17
|
196
|
Chris@17
|
197 /**
|
Chris@17
|
198 * setStream functions like 'select', and also sets up the $input
|
Chris@17
|
199 * object to read from the selected input stream e.g. when used
|
Chris@17
|
200 * with a question helper.
|
Chris@17
|
201 */
|
Chris@17
|
202 public function setStream(InputInterface $input, $optionOrArg)
|
Chris@17
|
203 {
|
Chris@17
|
204 $this->select($input, $optionOrArg);
|
Chris@17
|
205 if ($input instanceof StreamableInputInterface) {
|
Chris@17
|
206 $stream = $this->getStream();
|
Chris@17
|
207 $input->setStream($stream);
|
Chris@17
|
208 }
|
Chris@17
|
209 return $this;
|
Chris@17
|
210 }
|
Chris@17
|
211
|
Chris@17
|
212 /**
|
Chris@17
|
213 * contents reads the entire contents of the standard input stream.
|
Chris@17
|
214 *
|
Chris@17
|
215 * @return string
|
Chris@17
|
216 */
|
Chris@17
|
217 public function contents()
|
Chris@17
|
218 {
|
Chris@17
|
219 // Optimization: use file_get_contents if we have a path to a file
|
Chris@17
|
220 // and the stream has not been opened yet.
|
Chris@17
|
221 if (!$this->hasStream()) {
|
Chris@17
|
222 return file_get_contents($this->path());
|
Chris@17
|
223 }
|
Chris@17
|
224 $stream = $this->getStream();
|
Chris@17
|
225 stream_set_blocking($stream, false); // TODO: We did this in backend invoke. Necessary here?
|
Chris@17
|
226 $contents = stream_get_contents($stream);
|
Chris@17
|
227 $this->close();
|
Chris@17
|
228
|
Chris@17
|
229 return $contents;
|
Chris@17
|
230 }
|
Chris@17
|
231
|
Chris@17
|
232 /**
|
Chris@17
|
233 * Returns 'true' if a path was specfied, and that path was not '-'.
|
Chris@17
|
234 */
|
Chris@17
|
235 protected function pathProvided($path)
|
Chris@17
|
236 {
|
Chris@17
|
237 return !empty($path) && ($path != '-');
|
Chris@17
|
238 }
|
Chris@17
|
239
|
Chris@17
|
240 protected function getOptionOrArg(InputInterface $input, $optionOrArg)
|
Chris@17
|
241 {
|
Chris@17
|
242 if ($input->hasOption($optionOrArg)) {
|
Chris@17
|
243 return $input->getOption($optionOrArg);
|
Chris@17
|
244 }
|
Chris@17
|
245 return $input->getArgument($optionOrArg);
|
Chris@17
|
246 }
|
Chris@17
|
247 }
|