CreateWidgetCommand::interact()   F
last analyzed

Complexity

Conditions 13
Paths 1024

Size

Total Lines 168
Code Lines 103

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 168
rs 2
c 0
b 0
f 0
cc 13
eloc 103
nc 1024
nop 2

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 Victoire\Bundle\WidgetBundle\Command;
4
5
use Doctrine\DBAL\Types\Type;
6
use Sensio\Bundle\GeneratorBundle\Command\GenerateBundleCommand;
7
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
8
use Sensio\Bundle\GeneratorBundle\Command\Validators;
9
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineEntityGenerator;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Symfony\Component\Console\Question\ConfirmationQuestion;
14
use Symfony\Component\Console\Question\Question;
15
use Symfony\Component\DependencyInjection\Container;
16
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
17
use Victoire\Bundle\WidgetBundle\Generator\WidgetGenerator;
18
19
/**
20
 * Create a new Widget for VictoireCMS.
21
 */
22
class CreateWidgetCommand extends GenerateBundleCommand
23
{
24
    protected $skeletonDirs;
25
26
    /**
27
     * {@inheritdoc}
28
     */
29
    public function configure()
30
    {
31
        parent::configure();
32
33
        $this
34
            ->setName('victoire:generate:widget')
35
            ->setDefinition([
36
                new InputOption('namespace', '', InputOption::VALUE_REQUIRED, 'The namespace of the widget bundle to create'),
37
                new InputOption('dir', '', InputOption::VALUE_REQUIRED, 'The directory where to create the bundle'),
38
                new InputOption('bundle-name', '', InputOption::VALUE_REQUIRED, 'The optional bundle name'),
39
                new InputOption('orgname', '', InputOption::VALUE_REQUIRED, 'Your organisation name'),
40
                new InputOption('widget-name', '', InputOption::VALUE_REQUIRED, 'The widget name'),
41
                new InputOption('format', '', InputOption::VALUE_REQUIRED, 'Use the format for configuration files (php, xml, yml, or annotation)'),
42
                new InputOption('structure', '', InputOption::VALUE_NONE, 'Whether to generate the whole directory structure'),
43
                new InputOption('fields', '', InputOption::VALUE_REQUIRED, 'The fields to create with the new entity'),
44
                new InputOption('entity', '', InputOption::VALUE_REQUIRED, 'The entity class name to initialize (shortcut notation)'),
45
                new InputOption('parent', '', InputOption::VALUE_REQUIRED, 'The widget this widget will extends'),
46
                new InputOption('packagist-parent-name', '', InputOption::VALUE_REQUIRED, 'The packagist name of the widget you want to extends'),
47
                new InputOption('content-resolver', '', InputOption::VALUE_NONE, 'Whether to generate a blank ContentResolver to customize widget rendering logic'),
48
                new InputOption('cache', '', InputOption::VALUE_NONE, 'Use redis cache to store widgets until next modification'),
49
            ])
50
            ->setDescription('Generate a new widget')
51
            ->setHelp(<<<'EOT'
52
The <info>victoire:generate:widget</info> command helps you to generate new widgets.
53
54
By default, the command interacts with the developer to tweak the generation.
55
Any passed option will be used as a default value for the interaction
56
(<comment>--widget-name</comment> is the only one needed if you follow the
57
conventions):
58
59
<info>php app/console victoire:generate:widget --widget-name=myAwesomeWidget</info>
60
61
If you want to disable any user interaction, use <comment>--no-interaction</comment> but don't forget to pass all needed options:
62
63
Love you guys, you're awesome xxx
64
EOT
65
            );
66
    }
67
68
    /**
69
     * Take arguments and options defined in $this->interact() and generate a new Widget.
70
     *
71
     * @param InputInterface  $input
72
     * @param OutputInterface $output
73
     *
74
     * @see Command
75
     *
76
     * @throws \InvalidArgumentException When namespace doesn't end with Bundle
77
     * @throws \RuntimeException         When bundle can't be executed
78
     *
79
     * @return int|null
80
     */
81
    protected function execute(InputInterface $input, OutputInterface $output)
82
    {
83
        $questionHelper = $this->getQuestionHelper();
84
85
        if ($input->isInteractive()) {
86
            $question = new ConfirmationQuestion($questionHelper->getQuestion('Do you confirm generation', 'yes', '?'), true);
87
            if (!$questionHelper->ask($input, $output, $question)) {
88
                $output->writeln('<error>Command aborted</error>');
89
90
                return 1;
91
            }
92
        }
93
94
        foreach (['namespace', 'dir'] as $option) {
95
            if (null === $input->getOption($option)) {
96
                throw new \RuntimeException(sprintf('The "%s" option must be provided.', $option));
97
            }
98
        }
99
100
        $namespace = Validators::validateBundleNamespace($input->getOption('namespace'));
101
102
        if (!$bundle = $input->getOption('bundle-name')) {
103
            $bundle = strtr($namespace, ['\\' => '']);
104
        }
105
106
        $orgname = $input->getOption('orgname');
107
108
        if (null === $input->getOption('orgname')) {
109
            $orgname = $input->setOption('orgname', 'friendsofvictoire');
110
        }
111
112
        $parent = $input->getOption('parent');
113
114
        if (null === $input->getOption('parent')) {
115
            $parent = $input->setOption('parent', null);
116
        }
117
118
        $packagistParentName = $input->getOption('packagist-parent-name');
119
120
        if (null === $input->getOption('packagist-parent-name')) {
121
            $packagistParentName = $input->setOption('packagist-parent-name', null);
122
        }
123
124
        $bundle = Validators::validateBundleName($bundle);
125
        $dir = Validators::validateTargetDir($input->getOption('dir'), $bundle, $namespace);
126
127
        if (null === $input->getOption('format')) {
128
            $input->setOption('format', 'annotation');
129
        }
130
131
        $format = Validators::validateFormat($input->getOption('format'));
132
        $structure = $input->getOption('structure');
133
134
        $contentResolver = $input->getOption('content-resolver');
135
        $cache = $input->getOption('cache');
136
137
        $questionHelper->writeSection($output, 'Bundle generation');
138
139
        if (!$this->getContainer()->get('filesystem')->isAbsolutePath($dir)) {
140
            $dir = getcwd().'/'.$dir;
141
        }
142
143
        $fields = $this->parseFields($input->getOption('fields'));
144
145
        $parentContentResolver = $this->getContainer()->has('victoire_core.widget_'.strtolower($parent).'_content_resolver');
146
147
        $generator = $this->getGenerator();
148
        $generator->generate($namespace, $bundle, $dir, $format, $structure, $fields, $parent, $packagistParentName, $contentResolver, $parentContentResolver, $orgname, $cache);
149
150
        $output->writeln('Generating the bundle code: <info>OK</info>');
151
152
        $errors = [];
153
        $runner = $questionHelper->getRunner($output, $errors);
154
155
        // check that the namespace is already autoloaded
156
        $runner($this->checkAutoloader($output, $namespace, $bundle, $dir));
157
158
        // register the bundle in the Kernel class
159
        $runner($this->updateKernel($questionHelper, $input, $output, $this->getContainer()->get('kernel'), $namespace, $bundle));
160
161
        $questionHelper->writeGeneratorSummary($output, $errors);
162
    }
163
164
    /**
165
     * get a generator for given widget and type, and attach it skeleton dirs.
166
     *
167
     * @return $generator
0 ignored issues
show
Documentation introduced by
The doc-type $generator could not be parsed: Unknown type name "$generator" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
168
     */
169 View Code Duplication
    protected function getEntityGenerator()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
170
    {
171
        $dirs[] = $this->getContainer()->get('file_locator')->locate('@VictoireWidgetBundle/Resources/skeleton/');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dirs was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dirs = 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...
172
        $dirs[] = $this->getContainer()->get('file_locator')->locate('@VictoireWidgetBundle/Resources/');
173
174
        $generator = $this->createEntityGenerator();
175
176
        $this->skeletonDirs = array_merge($this->getSkeletonDirs(), $dirs);
177
        $generator->setSkeletonDirs($this->skeletonDirs);
178
        $this->setGenerator($generator);
179
180
        return $generator;
181
    }
182
183
    /**
184
     * get a generator for given widget and type, and attach it skeleton dirs.
185
     *
186
     * @return $generator
0 ignored issues
show
Documentation introduced by
The doc-type $generator could not be parsed: Unknown type name "$generator" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
187
     */
188 View Code Duplication
    protected function getGenerator(BundleInterface $bundle = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
    {
190
        $dirs[] = $this->getContainer()->get('file_locator')->locate('@VictoireWidgetBundle/Resources/skeleton/');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dirs was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dirs = 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...
191
        $dirs[] = $this->getContainer()->get('file_locator')->locate('@VictoireWidgetBundle/Resources/');
192
193
        $generator = $this->createWidgetGenerator();
194
195
        $this->skeletonDirs = array_merge($this->getSkeletonDirs(), $dirs);
196
        $generator->setSkeletonDirs($this->skeletonDirs);
197
        $this->setGenerator($generator);
198
199
        return $generator;
200
    }
201
202
    /**
203
     * Collect options and arguments.
204
     *
205
     * @param InputInterface  $input
206
     * @param OutputInterface $output
207
     *
208
     * @return void
209
     */
210
    protected function interact(InputInterface $input, OutputInterface $output)
211
    {
212
        $questionHelper = $this->getQuestionHelper();
213
        $questionHelper->writeSection($output, 'Welcome to the Victoire widget bundle generator');
214
215
        ///////////////////////
216
        //                   //
217
        //   Create Bundle   //
218
        //                   //
219
        ///////////////////////
220
221
        // namespace
222
        $namespace = null;
223
        try {
224
            $namespace = $input->getOption('namespace') ? Validators::validateBundleNamespace($input->getOption('namespace')) : null;
225
        } catch (\Exception $error) {
226
            $output->writeln($questionHelper->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
227
        }
228
229
        if (null === $namespace) {
230
            $output->writeln([
231
                '',
232
                'Your application code must be written in <comment>widget bundles</comment>. This command helps',
233
                'you generate them easily.',
234
                '',
235
                'Each widget is hosted under a namespace (like <comment>Victoire/Widget/YourAwesomeWidgetNameBundle</comment>).',
236
                '',
237
                'If you want for example a BlogWidget, the Widget Name should be Blog',
238
            ]);
239
240
            $question = new Question($questionHelper->getQuestion('Widget name', $input->getOption('bundle-name')));
241
            $question->setValidator(function ($answer) {
242
                return self::validateWidgetName($answer, false);
0 ignored issues
show
Unused Code introduced by
The call to CreateWidgetCommand::validateWidgetName() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
243
            });
244
245
            $name = $questionHelper->ask(
246
                $input,
247
                $output,
248
                $question
249
            );
250
251
            $bundle = 'VictoireWidget'.$name.'Bundle';
252
            $input->setOption('bundle-name', $bundle);
253
            $namespace = 'Victoire\\Widget\\'.$name.'Bundle';
254
            $input->setOption('namespace', $namespace);
255
        }
256
257
        $orgname = $input->getOption('orgname');
258
259
        if (null === $orgname) {
260
            $output->writeln([
261
                '',
262
                'A composer.json file will be generated, we need to know under which organisation you will publish the widget',
263
                '',
264
                'The default organisation will be friendsofvictoire',
265
            ]);
266
            $question = new Question($questionHelper->getQuestion('Under which organisation do you want to publish your widget ?', 'friendsofvictoire'), 'friendsofvictoire');
267
268
            $orgname = $questionHelper->ask($input, $output, $question);
269
        }
270
271
        $input->setOption('orgname', $orgname);
272
273
        $parent = $input->getOption('parent');
274
275
        $question = new ConfirmationQuestion($questionHelper->getQuestion('Does your widget extends another widget ?', 'no', '?'), false);
276
277
        if (null === $parent && $questionHelper->ask($input, $output, $question)) {
278
            $output->writeln([
279
                '',
280
                'A widget can extends another to reproduce its behavior',
281
                '',
282
                'If you wabt to do so, please give the name of the widget to extend',
283
                '',
284
                'If you want to extends the TestWidget, the widget name should be Test',
285
            ]);
286
287
            $question = new Question($questionHelper->getQuestion('Parent widget name', false));
288
            $question->setValidator(function ($answer) {
289
                return self::validateWidgetName($answer, false);
0 ignored issues
show
Unused Code introduced by
The call to CreateWidgetCommand::validateWidgetName() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
290
            });
291
            $parent = $questionHelper->ask($input, $output, $question);
292
293
            $input->setOption('parent', $parent);
294
295
            $packagistParentName = 'friendsofvictoire/'.strtolower($parent).'-widget';
296
            $question = new Question($questionHelper->getQuestion('Parent widget packagist name', $packagistParentName));
297
298
            $parent = $questionHelper->ask($input, $output, $question);
0 ignored issues
show
Unused Code introduced by
$parent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
299
300
            $input->setOption('packagist-parent-name', $packagistParentName);
301
        }
302
303
        $dir = dirname($this->getContainer()->getParameter('kernel.root_dir')).'/src';
304
305
        $output->writeln([
306
            '',
307
            'The bundle can be generated anywhere. The suggested default directory uses',
308
            'the standard conventions.',
309
            '',
310
        ]);
311
312
        $question = new Question($questionHelper->getQuestion('Target directory', $dir), $dir);
313
        $question->setValidator(function ($dir) use ($bundle, $namespace) {
0 ignored issues
show
Bug introduced by
The variable $bundle 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...
314
            return Validators::validateTargetDir($dir, $bundle, $namespace);
315
        });
316
        $dir = $questionHelper->ask($input, $output, $question);
317
        $input->setOption('dir', $dir);
318
319
        // format
320
        $format = null;
321
        try {
322
            $format = $input->getOption('format') ? Validators::validateFormat($input->getOption('format')) : null;
323
        } catch (\Exception $error) {
324
            $output->writeln($questionHelper->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
325
        }
326
327
        if (null === $format) {
328
            $output->writeln([
329
                '',
330
                'Determine the format to use for the generated configuration.',
331
                '',
332
            ]);
333
334
            $question = new Question($questionHelper->getQuestion('Configuration format (yml, xml, php, or annotation)', 'annotation'), 'annotation');
335
            $question->setValidator(
336
                ['Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateFormat']
337
            );
338
            $format = $questionHelper->ask($input, $output, $question);
339
            $input->setOption('format', $format);
340
        }
341
342
        $input->setOption('structure', false);
343
344
        $contentResolver = $input->getOption('content-resolver');
345
346
        $question = new ConfirmationQuestion($questionHelper->getQuestion('Do you want to customize widget rendering logic ?', 'no', '?'), false);
347
        if (!$contentResolver && $questionHelper->ask($input, $output, $question)) {
348
            $contentResolver = true;
349
        }
350
        $input->setOption('content-resolver', $contentResolver);
351
352
        ///////////////////////
353
        //                   //
354
        //   Create Entity   //
355
        //                   //
356
        ///////////////////////
357
358
        $input->setOption('fields', $this->addFields($input, $output, $questionHelper));
0 ignored issues
show
Documentation introduced by
$this->addFields($input,...utput, $questionHelper) is of type array, but the function expects a string|boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
359
        $entity = 'Widget'.$name;
0 ignored issues
show
Bug introduced by
The variable $name 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...
360
        $input->setOption('entity', $bundle.':'.$entity);
361
362
        $cache = $input->getOption('cache');
363
        $question = new ConfirmationQuestion($questionHelper->getQuestion('Do you want use cache for this widget ?', 'no', '?'), false);
364
        if (null !== $cache) {
365
            $cache = $questionHelper->ask($input, $output, $question);
366
        }
367
        $input->setOption('cache', $cache);
368
369
        // summary
370
        $output->writeln([
371
            '',
372
            $this->getHelper('formatter')->formatBlock('Summary before generation', 'bg=blue;fg=white', true),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method formatBlock() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\FormatterHelper.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
373
            '',
374
            sprintf("You are going to generate a \"<info>%s\\%s</info>\" widget bundle\nin \"<info>%s</info>\" using the \"<info>%s</info>\" format.", $namespace, $bundle, $dir, $format),
375
            '',
376
        ]);
377
    }
378
379
    /**
380
     * Check that provided widget name is correct.
381
     *
382
     * @param string $widget
383
     *
384
     * @return string $widget
385
     */
386
    public static function validateWidgetName($widget)
0 ignored issues
show
introduced by
Declare public methods first,then protected ones and finally private ones
Loading history...
387
    {
388
        if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $widget)) {
389
            throw new \InvalidArgumentException('The widget name contains invalid characters.');
390
        }
391
392
        if (!preg_match('/^([A-Z][a-z]+)+$/', $widget)) {
393
            throw new \InvalidArgumentException('The widget name must be PascalCased.');
394
        }
395
396
        return $widget;
397
    }
398
399
    /**
400
     * Instanciate a new WidgetGenerator.
401
     *
402
     * @return $generator
0 ignored issues
show
Documentation introduced by
The doc-type $generator could not be parsed: Unknown type name "$generator" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
403
     */
404
    protected function createWidgetGenerator()
405
    {
406
        $generator = new WidgetGenerator();
407
        $generator->setTemplating($this->getContainer()->get('twig'));
408
409
        return $generator;
410
    }
411
412
    /**
413
     * Instanciate a new Entity generator.
414
     *
415
     * @return $generator
0 ignored issues
show
Documentation introduced by
The doc-type $generator could not be parsed: Unknown type name "$generator" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
416
     */
417
    protected function createEntityGenerator()
418
    {
419
        return new DoctrineEntityGenerator($this->getContainer()->get('filesystem'), $this->getContainer()->get('doctrine'));
420
    }
421
422
    /**
423
     * transform console's output string fields into an array of fields.
424
     *
425
     * @param string $input
426
     *
427
     * @return array $fields
428
     */
429
    private function parseFields($input)
430
    {
431
        if (is_array($input)) {
432
            return $input;
433
        }
434
435
        $fields = [];
436
        foreach (explode(' ', $input) as $value) {
437
            $elements = explode(':', $value);
438
            $name = $elements[0];
439
            if (strlen($name)) {
440
                $type = isset($elements[1]) ? $elements[1] : 'string';
441
                preg_match_all('/(.*)\((.*)\)/', $type, $matches);
442
                $type = isset($matches[1][0]) ? $matches[1][0] : $type;
443
                $length = isset($matches[2][0]) ? $matches[2][0] : null;
444
445
                $fields[$name] = ['fieldName' => $name, 'type' => $type, 'length' => $length];
446
            }
447
        }
448
449
        return $fields;
450
    }
451
452
    /**
453
     * Interactively ask user to add field to his new Entity.
454
     *
455
     * @param InputInterface  $input
456
     * @param OutputInterface $output
457
     * @param QuestionHelper  $questionHelper
458
     *
459
     * @return $fields
0 ignored issues
show
Documentation introduced by
The doc-type $fields could not be parsed: Unknown type name "$fields" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
460
     */
461
    private function addFields(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper)
462
    {
463
        $fields = $this->parseFields($input->getOption('fields'));
464
        $output->writeln([
465
            '',
466
            'Instead of starting with a blank entity, you can add some fields now.',
467
            'Note that the primary key will be added automatically (named <comment>id</comment>).',
468
            '',
469
        ]);
470
        $output->write('<info>Available types:</info> ');
471
472
        $types = array_keys(Type::getTypesMap());
473
        $count = 20;
474
        foreach ($types as $i => $type) {
475
            if ($count > 50) {
476
                $count = 0;
477
                $output->writeln('');
478
            }
479
            $count += strlen($type);
480
            $output->write(sprintf('<comment>%s</comment>', $type));
481
            if (count($types) != $i + 1) {
482
                $output->write(', ');
483
            } else {
484
                $output->write('.');
485
            }
486
        }
487
        $output->writeln('');
488
489
        $fieldValidator = function ($type) use ($types) {
490
            if (!in_array($type, $types)) {
491
                throw new \InvalidArgumentException(sprintf('Invalid type "%s".', $type));
492
            }
493
494
            return $type;
495
        };
496
497
        $lengthValidator = function ($length) {
498
            if (!$length) {
499
                return $length;
500
            }
501
502
            $result = filter_var($length, FILTER_VALIDATE_INT, [
503
                'options' => ['min_range' => 1],
504
            ]);
505
506
            if (false === $result) {
507
                throw new \InvalidArgumentException(sprintf('Invalid length "%s".', $length));
508
            }
509
510
            return $length;
511
        };
512
513
        while (true) {
514
            $output->writeln('');
515
            $generator = $this->getEntityGenerator();
516
517
            $question = new Question($questionHelper->getQuestion('New field name (press <return> to stop adding fields)', null));
518
            $question->setValidator(
519
                function ($name) use ($fields, $generator) {
520
                    if (isset($fields[$name]) || 'id' == $name) {
521
                        throw new \InvalidArgumentException(sprintf('Field "%s" is already defined.', $name));
522
                    }
523
524
                    // check reserved words by database
525
                    if ($generator->isReservedKeyword($name)) {
526
                        throw new \InvalidArgumentException(sprintf('Name "%s" is a reserved word.', $name));
527
                    }
528
                    // check reserved words by victoire
529
                    if ($this->isReservedKeyword($name)) {
530
                        throw new \InvalidArgumentException(sprintf('Name "%s" is a Victoire reserved word.', $name));
531
                    }
532
533
                    return $name;
534
                }
535
            );
536
537
            $columnName = $questionHelper->ask($input, $output, $question);
538
            if (!$columnName) {
539
                break;
540
            }
541
542
            $defaultType = 'string';
543
544
            // try to guess the type by the column name prefix/suffix
545
            if (substr($columnName, -3) == '_at') {
546
                $defaultType = 'datetime';
547
            } elseif (substr($columnName, -3) == '_id') {
548
                $defaultType = 'integer';
549
            } elseif (substr($columnName, 0, 3) == 'is_') {
550
                $defaultType = 'boolean';
551
            } elseif (substr($columnName, 0, 4) == 'has_') {
552
                $defaultType = 'boolean';
553
            }
554
555
            $question = new Question($questionHelper->getQuestion('Field type', $defaultType), $defaultType);
556
            $question->setValidator($fieldValidator);
557
            $question->setAutocompleterValues($types);
558
            $type = $questionHelper->ask($input, $output, $question);
559
560
            $data = ['columnName' => $columnName, 'fieldName' => lcfirst(Container::camelize($columnName)), 'type' => $type];
561
562
            if ($type == 'string') {
563
                $question = new Question($questionHelper->getQuestion('Field length', 255), 255);
564
                $question->setValidator($lengthValidator);
565
                $data['length'] = $questionHelper->ask($input, $output, $question);
566
            }
567
568
            $fields[$columnName] = $data;
569
        }
570
571
        return $fields;
572
    }
573
574
    /**
575
     * Validate Entity short namepace.
576
     *
577
     * @param string $shortcut
578
     *
579
     * @return $shortcut
0 ignored issues
show
Documentation introduced by
The doc-type $shortcut could not be parsed: Unknown type name "$shortcut" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
580
     */
581
    protected function parseShortcutNotation($shortcut)
0 ignored issues
show
introduced by
Declare public methods first,then protected ones and finally private ones
Loading history...
582
    {
583
        $entity = str_replace('/', '\\', $shortcut);
584
585
        if (false === $pos = strpos($entity, ':')) {
586
            throw new \InvalidArgumentException(sprintf('The entity name must contain a : ("%s" given, expecting something like AcmeBlogBundle:Blog/Post)', $entity));
587
        }
588
589
        return [substr($entity, 0, $pos), substr($entity, $pos + 1)];
590
    }
591
592
    protected function isReservedKeyword($keyword)
593
    {
594
        return in_array($keyword, ['widget']);
595
    }
596
}
597