Completed
Pull Request — master (#175)
by Greg
01:51
created

StdinHandler::getOptionOrArg()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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