Completed
Pull Request — master (#179)
by Greg
01:41
created

CommandData::injectInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
namespace Consolidation\AnnotatedCommand;
3
4
use Symfony\Component\Console\Input\ArgvInput;
5
use Symfony\Component\Console\Input\InputInterface;
6
use Symfony\Component\Console\Output\OutputInterface;
7
8
class CommandData
9
{
10
    /** var AnnotationData */
11
    protected $annotationData;
12
    /** var InputInterface */
13
    protected $input;
14
    /** var OutputInterface */
15
    protected $output;
16
    /** var boolean */
17
    protected $includeOptionsInArgs;
18
    /** var array */
19
    protected $specialDefaults = [];
20
    /** @var string[] */
21
    protected $injectedInstances = [];
22
23
    public function __construct(
24
        AnnotationData $annotationData,
25
        InputInterface $input,
26
        OutputInterface $output
27
    ) {
28
        $this->annotationData = $annotationData;
29
        $this->input = $input;
30
        $this->output = $output;
31
        $this->includeOptionsInArgs = true;
32
    }
33
34
    /**
35
     * For internal use only; inject an instance to be passed back
36
     * to the command callback as a parameter.
37
     */
38
    public function injectInstance($injectedInstance)
39
    {
40
        array_unshift($this->injectedInstances, $injectedInstance);
41
        return $this;
42
    }
43
44
    /**
45
     * Provide a reference to the instances that will be added to the
46
     * beginning of the parameter list when the command callback is invoked.
47
     */
48
    public function injectedInstances()
49
    {
50
        return $this->injectedInstances;
51
    }
52
53
    /**
54
     * For backwards-compatibility mode only: disable addition of
55
     * options on the end of the arguments list.
56
     */
57
    public function setIncludeOptionsInArgs($includeOptionsInArgs)
58
    {
59
        $this->includeOptionsInArgs = $includeOptionsInArgs;
60
        return $this;
61
    }
62
63
    public function annotationData()
64
    {
65
        return $this->annotationData;
66
    }
67
68
    public function input()
69
    {
70
        return $this->input;
71
    }
72
73
    public function output()
74
    {
75
        return $this->output;
76
    }
77
78
    public function arguments()
79
    {
80
        return $this->input->getArguments();
81
    }
82
83
    public function options()
84
    {
85
        // We cannot tell the difference between '--foo' (an option without
86
        // a value) and the absence of '--foo' when the option has an optional
87
        // value, and the current vallue of the option is 'null' using only
88
        // the public methods of InputInterface. We'll try to figure out
89
        // which is which by other means here.
90
        $options = $this->getAdjustedOptions();
91
92
        // Make two conversions here:
93
        // --foo=0 wil convert $value from '0' to 'false' for binary options.
94
        // --foo with $value of 'true' will be forced to 'false' if --no-foo exists.
95
        foreach ($options as $option => $value) {
96
            if ($this->shouldConvertOptionToFalse($options, $option, $value)) {
97
                $options[$option] = false;
98
            }
99
        }
100
101
        return $options;
102
    }
103
104
    /**
105
     * Use 'hasParameterOption()' to attempt to disambiguate option states.
106
     */
107
    protected function getAdjustedOptions()
108
    {
109
        $options = $this->input->getOptions();
110
111
        // If Input isn't an ArgvInput, then return the options as-is.
112
        if (!$this->input instanceof ArgvInput) {
113
            return $options;
114
        }
115
116
        // If we have an ArgvInput, then we can determine if options
117
        // are missing from the command line. If the option value is
118
        // missing from $input, then we will keep the value `null`.
119
        // If it is present, but has no explicit value, then change it its
120
        // value to `true`.
121
        foreach ($options as $option => $value) {
122
            if (($value === null) && ($this->input->hasParameterOption("--$option"))) {
123
                $options[$option] = true;
124
            }
125
        }
126
127
        return $options;
128
    }
129
130
    protected function shouldConvertOptionToFalse($options, $option, $value)
131
    {
132
        // If the value is 'true' (e.g. the option is '--foo'), then convert
133
        // it to false if there is also an option '--no-foo'. n.b. if the
134
        // commandline has '--foo=bar' then $value will not be 'true', and
135
        // --no-foo will be ignored.
136
        if ($value === true) {
137
            // Check if the --no-* option exists. Note that none of the other
138
            // alteration apply in the $value == true case, so we can exit early here.
139
            $negation_key = 'no-' . $option;
140
            return array_key_exists($negation_key, $options) && $options[$negation_key];
141
        }
142
143
        // If the option is '--foo=0', convert the '0' to 'false' when appropriate.
144
        if ($value !== '0') {
145
            return false;
146
        }
147
148
        // The '--foo=0' convertion is only applicable when the default value
149
        // is not in the special defaults list. i.e. you get a literal '0'
150
        // when your default is a string.
151
        return in_array($option, $this->specialDefaults);
152
    }
153
154
    public function cacheSpecialDefaults($definition)
155
    {
156
        foreach ($definition->getOptions() as $option => $inputOption) {
157
            $defaultValue = $inputOption->getDefault();
158
            if (($defaultValue === null) || ($defaultValue === true)) {
159
                $this->specialDefaults[] = $option;
160
            }
161
        }
162
    }
163
164
    public function getArgsWithoutAppName()
165
    {
166
        $args = $this->arguments();
167
168
        // When called via the Application, the first argument
169
        // will be the command name. The Application alters the
170
        // input definition to match, adding a 'command' argument
171
        // to the beginning.
172
        if ($this->input->hasArgument('command')) {
173
            array_shift($args);
174
        }
175
176
        return $args;
177
    }
178
179
    public function getArgsAndOptions()
180
    {
181
        // Get passthrough args, and add the options on the end.
182
        $args = $this->getArgsWithoutAppName();
183
        if ($this->includeOptionsInArgs) {
184
            $args['options'] = $this->options();
185
        }
186
        return $args;
187
    }
188
}
189