Completed
Push — master ( 9988ed...5c7b62 )
by Greg
01:53
created

StdinHandler::path()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
208
        }
209
        return $this;
210
    }
211
212
    /**
213
     * contents reads the entire contents of the standard input stream.
214
     *
215
     * @return string
216
     */
217
    public function contents()
218
    {
219
        // Optimization: use file_get_contents if we have a path to a file
220
        // and the stream has not been opened yet.
221
        if (!$this->hasStream()) {
222
            return file_get_contents($this->path());
223
        }
224
        $stream = $this->getStream();
225
        stream_set_blocking($stream, false); // TODO: We did this in backend invoke. Necessary here?
226
        $contents = stream_get_contents($stream);
227
        $this->close();
228
229
        return $contents;
230
    }
231
232
    /**
233
     * Returns 'true' if a path was specfied, and that path was not '-'.
234
     */
235
    protected function pathProvided($path)
236
    {
237
        return !empty($path) && ($path != '-');
238
    }
239
240
    protected function getOptionOrArg(InputInterface $input, $optionOrArg)
241
    {
242
        if ($input->hasOption($optionOrArg)) {
243
            return $input->getOption($optionOrArg);
244
        }
245
        return $input->getArgument($optionOrArg);
246
    }
247
}
248