Completed
Push — master ( 0c8d1b...0c71fb )
by Tom
06:41 queued 02:14
created

ParameterHelper::websitesQuestion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 17
rs 9.4285
cc 2
eloc 11
nc 2
nop 1
1
<?php
2
3
namespace N98\Util\Console\Helper;
4
5
use Exception;
6
use InvalidArgumentException;
7
use Mage_Core_Model_Website;
8
use N98\Util\Validator\FakeMetadataFactory;
9
use RuntimeException;
10
use Symfony\Component\Console\Helper\DialogHelper;
11
use Symfony\Component\Console\Helper\Helper as AbstractHelper;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\Translation\Translator;
15
use Symfony\Component\Validator\Constraints;
16
use Symfony\Component\Validator\ConstraintValidatorFactory;
17
use Symfony\Component\Validator\ConstraintViolationInterface;
18
use Symfony\Component\Validator\ConstraintViolationListInterface;
19
use Symfony\Component\Validator\Validator;
20
21
/**
22
 * Helper to init some parameters
23
 */
24
class ParameterHelper extends AbstractHelper
25
{
26
    /**
27
     * @var Validator
28
     */
29
    private $validator;
30
31
    /**
32
     * Returns the canonical name of this helper.
33
     *
34
     * @return string The canonical name
35
     *
36
     * @api
37
     */
38
    public function getName()
39
    {
40
        return 'parameter';
41
    }
42
43
    /**
44
     * @param InputInterface $input
45
     * @param OutputInterface $output
46
     * @param string $argumentName
47
     * @param bool $withDefaultStore [optional]
48
     *
49
     * @return mixed
50
     *
51
     * @throws InvalidArgumentException
52
     */
53
    public function askStore(
54
        InputInterface $input,
55
        OutputInterface $output,
56
        $argumentName = 'store',
57
        $withDefaultStore = false
58
    ) {
59
        /* @var $storeManager \Mage_Core_Model_App */
60
        $storeManager = \Mage::app();
61
62
        try {
63
            if ($input->getArgument($argumentName) === null) {
64
                throw new RuntimeException('No store given');
65
            }
66
            /** @var $store \Mage_Core_Model_Store */
67
            $store = $storeManager->getStore($input->getArgument($argumentName));
68
        } catch (Exception $e) {
69
            $stores = array();
70
            $i = 0;
71
72
            foreach ($storeManager->getStores($withDefaultStore) as $store) {
73
                $stores[$i] = $store->getId();
74
                $question[] = sprintf(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$question was never initialized. Although not strictly required by PHP, it is generally a good practice to add $question = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
75
                    '<comment>[%d]</comment> %s - %s' . PHP_EOL,
76
                    ++$i,
77
                    $store->getCode(),
78
                    $store->getName()
79
                );
80
            }
81
82
            if (count($stores) > 1) {
83
                $question[] = '<question>Please select a store: </question>';
0 ignored issues
show
Bug introduced by
The variable $question does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
84
                $storeId = $this->askAndValidate(
85
                    $output,
86
                    $question,
87
                    function ($typeInput) use ($stores) {
88
                        if (!isset($stores[$typeInput - 1])) {
89
                            throw new InvalidArgumentException('Invalid store');
90
                        }
91
92
                        return $stores[$typeInput - 1];
93
                    }
94
                );
95
            } else {
96
                // only one store view available -> take it
97
                $storeId = $stores[0];
98
            }
99
100
            $store = $storeManager->getStore($storeId);
101
        }
102
103
        return $store;
104
    }
105
106
    /**
107
     * @param InputInterface $input
108
     * @param OutputInterface $output
109
     * @param string $argumentName
110
     *
111
     * @return mixed
112
     * @throws InvalidArgumentException
113
     */
114
    public function askWebsite(InputInterface $input, OutputInterface $output, $argumentName = 'website')
115
    {
116
        /* @var $storeManager \Mage_Core_Model_App */
117
        $storeManager = \Mage::app();
118
119
        $website = null;
120
        $argumentValue = $input->getArgument($argumentName);
121
        $hasArgument = $argumentValue !== null;
122
123
        if ($hasArgument) {
124
            try {
125
                /* @var $website Mage_Core_Model_Website */
126
                $website = $storeManager->getWebsite($argumentValue);
127
                return $website;
128
            } catch (Exception $e) {
129
                // catch all exceptions
130
            }
131
        }
132
133
        list($websites, $question) = $this->websitesQuestion($storeManager);
134
        if (count($websites) === 1) {
135
            return $storeManager->getWebsite($websites[0]);
136
        }
137
138
        $question[] = '<question>Please select a website: </question>';
139
        $callback = function ($typeInput) use ($websites) {
140
            if (!isset($websites[$typeInput - 1])) {
141
                throw new InvalidArgumentException('Invalid website');
142
            }
143
144
            return $websites[$typeInput - 1];
145
        };
146
147
        $websiteId = $this->askAndValidate($output, $question, $callback);
148
        $website = $storeManager->getWebsite($websiteId);
149
150
        return $website;
151
    }
152
153
    /**
154
     * @see askWebsite
155
     * @return array websites (integers if website IDs, 0-indexed) and question array (strings)
156
     */
157
    private function websitesQuestion($storeManager)
158
    {
159
        $i = 0;
160
        $websites = array();
161
        $question = array();
162
        foreach ($storeManager->getWebsites() as $website) {
163
            /* @var $website Mage_Core_Model_Website */
164
            $value = $website->getId();
165
            $label = sprintf('%s - %s', $website->getCode(), $website->getName());
166
            $position = $i + 1;
167
168
            $websites[$i] = $value;
169
            $question[$i] = sprintf('<comment>[%d]</comment> %s' . PHP_EOL, $position, $label);
170
        }
171
172
        return array($websites, $question);
173
    }
174
175
    /**
176
     * @param InputInterface $input
177
     * @param OutputInterface $output
178
     * @param string $argumentName
179
     *
180
     * @return string
181
     */
182
    public function askEmail(InputInterface $input, OutputInterface $output, $argumentName = 'email')
183
    {
184
        $constraints = new Constraints\Collection(
185
            array(
186
                'email' => array(
187
                    new Constraints\NotBlank(),
188
                    new Constraints\Email()
189
                )
190
            )
191
        );
192
193
        return $this->validateArgument($output, $argumentName, $input->getArgument($argumentName), $constraints);
194
    }
195
196
    /**
197
     * @param InputInterface $input
198
     * @param OutputInterface $output
199
     * @param string $argumentName
200
     *
201
     * @param bool $needDigits [optional]
202
     * @return string
203
     */
204
    public function askPassword(
205
        InputInterface $input,
206
        OutputInterface $output,
207
        $argumentName = 'password',
208
        $needDigits = true
209
    ) {
210
        $validators = array();
211
212
        if ($needDigits) {
213
            $regex = array(
214
                'pattern' => '/^(?=.*\d)(?=.*[a-zA-Z])/',
215
                'message' => 'Password must contain letters and at least one digit'
216
            );
217
            $validators[] = new Constraints\Regex($regex);
218
        }
219
220
        $validators[] = new Constraints\Length(array('min' => 6));
221
222
        $constraints = new Constraints\Collection(
223
            array(
224
                'password' => $validators
225
            )
226
        );
227
228
        return $this->validateArgument($output, $argumentName, $input->getArgument($argumentName), $constraints);
229
    }
230
231
    /**
232
     * @param OutputInterface $output
233
     * @param                 $question
234
     * @param callable $callback
235
     *
236
     * @return string
237
     */
238
    private function askAndValidate(OutputInterface $output, $question, $callback)
239
    {
240
        /** @var DialogHelper $dialog */
241
        $dialog = $this->getHelperSet()->get('dialog');
242
243
        return $dialog->askAndValidate($output, $question, $callback);
244
    }
245
246
    /**
247
     * @param OutputInterface $output
248
     * @param string $name
249
     * @param string $value
250
     * @param Constraints\Collection $constraints The constraint(s) to validate against.
251
     *
252
     * @return string
253
     */
254
    private function validateArgument(OutputInterface $output, $name, $value, $constraints)
255
    {
256
        if (strlen($value)) {
257
            $errors = $this->validateValue($name, $value, $constraints);
258
            if ($errors->count() > 0) {
259
                $output->writeln('<error>' . $errors[0]->getMessage() . '</error>');
260
            } else {
261
                return $value;
262
            }
263
        }
264
265
        $question = '<question>' . ucfirst($name) . ': </question>';
266
267
        $value = $this->askAndValidate(
268
            $output,
269
            $question,
270
            function ($inputValue) use ($constraints, $name) {
271
                $errors = $this->validateValue($name, $inputValue, $constraints);
272
                if ($errors->count() > 0) {
273
                    throw new InvalidArgumentException($errors[0]->getMessage());
274
                }
275
276
                return $inputValue;
277
            }
278
        );
279
280
        return $value;
281
    }
282
283
    /**
284
     * @param string $name
285
     * @param string $value
286
     * @param Constraints\Collection $constraints The constraint(s) to validate against.
287
     *
288
     * @return \Symfony\Component\Validator\ConstraintViolationInterface[]|ConstraintViolationListInterface
289
     */
290
    private function validateValue($name, $value, $constraints)
291
    {
292
        $validator = $this->getValidator();
293
        /** @var ConstraintViolationListInterface|ConstraintViolationInterface[] $errors */
294
        $errors = $validator->validateValue(array($name => $value), $constraints);
295
296
        return $errors;
297
    }
298
299
    /**
300
     * @return Validator
301
     */
302
    private function getValidator()
303
    {
304
        return $this->validator ?: $this->validator = $this->createValidator();
305
    }
306
307
    /**
308
     * @return Validator
309
     */
310
    private function createValidator()
311
    {
312
        $factory = new ConstraintValidatorFactory();
313
        $validator = new Validator(new FakeMetadataFactory(), $factory, new Translator('en'));
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Validator\Validator has been deprecated with message: since version 2.5, to be removed in 3.0. Use {@link Validator\RecursiveValidator} instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
314
315
        return $validator;
316
    }
317
}
318