Completed
Pull Request — master (#55)
by Julien
07:42
created

ChoiceQuestion::getDefaultValidator()   D

Complexity

Conditions 19
Paths 1

Size

Total Lines 72
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 44
nc 1
nop 0
dl 0
loc 72
rs 4.5166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace TheAentMachine\Helper;
4
5
use Symfony\Component\Console\Exception\InvalidArgumentException;
6
use Symfony\Component\Console\Helper\QuestionHelper;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Symfony\Component\Console\Question\ChoiceQuestion as SymfonyChoiceQuestion;
10
11
/**
12
 * A helper class to easily create choice questions.
13
 */
14
class ChoiceQuestion extends BaseQuestion
15
{
16
    /** @var string[] */
17
    private $choices;
18
19
    /** @var bool */
20
    private $multiselect = false;
21
22
23
    public function __construct(QuestionHelper $helper, InputInterface $input, OutputInterface $output, string $question, array $choices, bool $printAnswer = true)
24
    {
25
        parent::__construct($helper, $input, $output, $question, $printAnswer);
26
        $this->choices = $choices;
27
    }
28
29
    public function setDefault(string $default): self
30
    {
31
        $this->default = $default;
32
        return $this;
33
    }
34
35
    public function setHelpText(string $helpText): self
36
    {
37
        $this->helpText = $helpText;
38
        return $this;
39
    }
40
41
    public function ask(): string
42
    {
43
        $question = $this->initQuestion(false);
44
        do {
45
            $answer = $this->helper->ask($this->input, $this->output, $question);
46
        } while ($this->helpText !== null && $answer === '?');
47
48
        return $answer;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $answer could return the type null|boolean which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
49
    }
50
51
    /** @return string[] */
52
    public function askWithMultipleChoices(): array
53
    {
54
        $question = $this->initQuestion(true);
55
56
        do {
57
            $answer = $this->helper->ask($this->input, $this->output, $question);
58
        } while ($this->helpText !== null && $answer === '?');
59
60
        return $answer;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $answer could return the type null|string|boolean which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
61
    }
62
63
    private function initQuestion(bool $multiselect): SymfonyChoiceQuestion
64
    {
65
        $text = $this->question;
66
        if ($this->helpText) {
67
            $text .= ' (? for help)';
68
        }
69
        if ($this->default) {
70
            $defaultText = (\is_array($this->default) ? implode(', ', $this->default) : $this->default);
0 ignored issues
show
introduced by
The condition is_array($this->default) is always false.
Loading history...
71
            $text .= ' [' . $defaultText . ']';
72
        }
73
        $text .= ': ';
74
        $this->question = $text;
75
76
        $question = new SymfonyChoiceQuestion($this->question, $this->choices, $this->default);
77
        $this->multiselect = $multiselect;
78
        $question->setMultiselect($this->multiselect);
79
        $question->setValidator($this->getDefaultValidator());
80
        return $question;
81
    }
82
83
    /** Greatly inspired from the default Symfony's ChoiceQuestion validator*/
84
    private function getDefaultValidator(): callable
85
    {
86
        $choices = $this->choices;
87
        $errorMessage = 'Value "%s" is invalid';
88
        $multiselect = $this->multiselect;
89
        $isAssoc = (bool)count(array_filter(array_keys($this->choices), '\is_string'));
90
91
        return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
92
93
            // Collapse all spaces.
94
            $selectedChoices = str_replace(' ', '', $selected);
95
96
            if ($this->helpText !== null && $selectedChoices === '?') {
97
                $this->output->writeln($this->helpText ?: '');
98
                return '?';
99
            }
100
101
            if ($multiselect) {
102
                // Check for a separated comma values
103
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) {
104
                    throw new InvalidArgumentException(sprintf($errorMessage, $selected));
105
                }
106
                $selectedChoices = explode(',', $selectedChoices);
107
            } else {
108
                $selectedChoices = array($selected);
109
            }
110
111
            $multiselectChoices = array();
112
            foreach ($selectedChoices as $value) {
113
                $results = array();
114
                foreach ($choices as $key => $choice) {
115
                    if ($choice === $value) {
116
                        $results[] = $key;
117
                    }
118
                }
119
120
                if (count($results) > 1) {
121
                    throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
122
                }
123
124
                $result = array_search($value, $choices, true);
125
126
                if (!$isAssoc) {
127
                    if (false !== $result) {
128
                        $result = $choices[$result];
129
                    } elseif (isset($choices[$value])) {
130
                        $result = $choices[$value];
131
                    }
132
                } elseif (false === $result && isset($choices[$value])) {
133
                    $result = $value;
134
                }
135
136
                if (false === $result) {
137
                    throw new InvalidArgumentException(sprintf($errorMessage, $value));
138
                }
139
                $multiselectChoices[] = (string)$result;
140
            }
141
            if ($multiselect) {
142
                if ($this->printAnswer) {
143
                    $this->output->writeln('<info>You selected: ' . \implode(", ", $multiselectChoices) . '</info>');
144
                    $this->spacer();
145
                }
146
                return $multiselectChoices;
147
            }
148
149
            $answer = current($multiselectChoices);
150
            if ($this->printAnswer) {
151
                $this->output->writeln("<info>You selected: $answer</info>");
152
                $this->spacer();
153
            }
154
155
            return $answer;
156
        };
157
    }
158
}
159