Completed
Pull Request — master (#37)
by Julien
02:40
created

Question   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 74
dl 0
loc 155
rs 10
c 0
b 0
f 0
wmc 27

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setValidator() 0 4 1
A compulsory() 0 4 1
A setDefault() 0 4 1
A choiceQuestion() 0 6 1
A setHelpText() 0 4 1
A yesNoQuestion() 0 4 1
F ask() 0 66 20
1
<?php
2
3
namespace TheAentMachine\Helper;
4
5
use Symfony\Component\Console\Helper\QuestionHelper;
6
use Symfony\Component\Console\Input\InputInterface;
7
use Symfony\Component\Console\Output\OutputInterface;
8
9
/**
10
 * A helper class to easily create questions.
11
 */
12
class Question
13
{
14
    /** @var QuestionHelper */
15
    private $helper;
16
17
    /** @var InputInterface */
18
    private $input;
19
20
    /** @var OutputInterface */
21
    private $output;
22
23
    /** @var string */
24
    private $question;
25
26
    /** @var string|null */
27
    private $default;
28
29
    /** @var bool */
30
    private $compulsory = false;
31
32
    /** @var callable|null */
33
    private $validator;
34
35
    /** @var string|null */
36
    private $helpText;
37
38
    /** @var bool  */
39
    private $yesNoQuestion = false;
40
41
    /** @var bool */
42
    private $choiceQuestion = false;
43
44
    /** @var mixed[] */
45
    private $choices;
46
47
    /** @var bool */
48
    private $multiselectQuestion = false;
49
50
    public function __construct(QuestionHelper $helper, InputInterface $input, OutputInterface $output, string $question)
51
    {
52
        $this->helper = $helper;
53
        $this->input = $input;
54
        $this->output = $output;
55
        $this->question = $question;
56
    }
57
58
    public function compulsory(): self
59
    {
60
        $this->compulsory = true;
61
        return $this;
62
    }
63
64
    public function setValidator(callable $validator): self
65
    {
66
        $this->validator = $validator;
67
        return $this;
68
    }
69
70
    public function setHelpText(string $helpText): self
71
    {
72
        $this->helpText = $helpText;
73
        return $this;
74
    }
75
76
    public function setDefault(string $default): self
77
    {
78
        $this->default = $default;
79
        return $this;
80
    }
81
82
    public function yesNoQuestion(): self
83
    {
84
        $this->yesNoQuestion = true;
85
        return $this;
86
    }
87
88
    /**
89
     * @param mixed[] $choices
90
     * @param bool $multiselect
91
     * @return Question
92
     */
93
    public function choiceQuestion(array $choices, bool $multiselect = false): self
94
    {
95
        $this->choiceQuestion = true;
96
        $this->choices = $choices;
97
        $this->multiselectQuestion = $multiselect;
98
        return $this;
99
    }
100
101
    public function ask(): string
102
    {
103
        $text = $this->question;
104
        if ($this->helpText) {
105
            $text .= ' (? for help)';
106
        }
107
        if ($this->default) {
108
            if (!$this->yesNoQuestion) {
109
                $text .= ' [' . $this->default . ']';
110
            } elseif ($this->default === 'y') {
111
                $text .= ' [Y/n]';
112
            } elseif ($this->default === 'n') {
113
                $text .= ' [y/N]';
114
            } else {
115
                throw new \InvalidArgumentException('Default value must be "y" or "n".');
116
            }
117
        } elseif ($this->yesNoQuestion) {
118
            $text .= ' [y/n]';
119
        }
120
        $text .= ': ';
121
122
123
        $question = $this->choiceQuestion ? new \Symfony\Component\Console\Question\ChoiceQuestion($text, $this->choices, $this->default) : new \Symfony\Component\Console\Question\Question($text, $this->default);
124
125
        $validator = $this->validator;
126
127
        if ($this->yesNoQuestion) {
128
            $validator = function (?string $response) use ($validator) {
129
                $response = $response ?? '';
130
                $response = \strtolower(trim($response));
131
                if (!\in_array($response, ['y', 'n', 'yes', 'no'])) {
132
                    throw new \InvalidArgumentException('Answer must be "y" or "n"');
133
                }
134
                $response = \in_array($response, ['y', 'yes']) ? '1' : '';
135
                return $validator ? $validator($response) : $response;
136
            };
137
        }
138
139
        if ($this->helpText !== null) {
140
            $validator = function (?string $response) use ($validator) {
141
                $response = $response ?? '';
142
                if (trim($response) === '?') {
143
                    $this->output->writeln($this->helpText ?: '');
144
                    return '?';
145
                }
146
                return $validator ? $validator($response) : $response;
147
            };
148
        }
149
150
        if ($this->compulsory) {
151
            $validator = function (?string $response) use ($validator) {
152
                $response = $response ?? '';
153
                if (trim($response) === '') {
154
                    throw new \InvalidArgumentException('This field is compulsory.');
155
                }
156
                return $validator ? $validator($response) : $response;
157
            };
158
        }
159
160
        $question->setValidator($validator);
161
162
        do {
163
            $answer = $this->helper->ask($this->input, $this->output, $question);
164
        } while ($this->helpText !== null && $answer === '?');
165
166
        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...
167
    }
168
}
169