Completed
Push — master ( 770316...74fc07 )
by Jeroen
09:08 queued 02:44
created

GeneratorBundle/Command/GenerateBundleCommand.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Kunstmaan\GeneratorBundle\Command;
4
5
use Kunstmaan\GeneratorBundle\Generator\BundleGenerator;
6
use Kunstmaan\GeneratorBundle\Helper\GeneratorUtils;
7
use Sensio\Bundle\GeneratorBundle\Command\GeneratorCommand;
8
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
9
use Sensio\Bundle\GeneratorBundle\Command\Validators;
10
use Sensio\Bundle\GeneratorBundle\Manipulator\KernelManipulator;
11
use Sensio\Bundle\GeneratorBundle\Manipulator\RoutingManipulator;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\Console\Question\ConfirmationQuestion;
16
use Symfony\Component\Console\Question\Question;
17
use Symfony\Component\HttpKernel\KernelInterface;
18
19
/**
20
 * Generates bundles.
21
 */
22
class GenerateBundleCommand extends GeneratorCommand
23
{
24
    /**
25
     * @see Command
26
     */
27 View Code Duplication
    protected function configure()
28
    {
29
        $this
30
            ->setDefinition(
31
                array(new InputOption('namespace', '', InputOption::VALUE_REQUIRED, 'The namespace of the bundle to create'),
32
                    new InputOption('dir', '', InputOption::VALUE_REQUIRED, 'The directory where to create the bundle'),
33
                    new InputOption('bundle-name', '', InputOption::VALUE_REQUIRED, 'The optional bundle name'), ))
34
            ->setHelp(
35
                <<<'EOT'
36
            The <info>generate:bundle</info> command helps you generates new bundles.
37
38
By default, the command interacts with the developer to tweak the generation.
39
Any passed option will be used as a default value for the interaction
40
(<comment>--namespace</comment> is the only one needed if you follow the
41
conventions):
42
43
<info>php bin/console kuma:generate:bundle --namespace=Acme/BlogBundle</info>
44
45
Note that you can use <comment>/</comment> instead of <comment>\</comment> for the namespace delimiter to avoid any
46
problem.
47
48
If you want to disable any user interaction, use <comment>--no-interaction</comment> but don't forget to pass all needed options:
49
50
<info>php bin/console kuma:generate:bundle --namespace=Acme/BlogBundle --dir=src [--bundle-name=...] --no-interaction</info>
51
52
Note that the bundle namespace must end with "Bundle".
53
EOT
54
            )
55
            ->setName('kuma:generate:bundle');
56
    }
57
58
    /**
59
     * Executes the command.
60
     *
61
     * @param InputInterface  $input  An InputInterface instance
62
     * @param OutputInterface $output An OutputInterface instance
63
     *
64
     * @throws \RuntimeException
65
     */
66
    protected function execute(InputInterface $input, OutputInterface $output)
67
    {
68
        $questionHelper = $this->getQuestionHelper();
69
70 View Code Duplication
        if ($input->isInteractive()) {
71
            $confirmationQuestion = new ConfirmationQuestion($questionHelper->getQuestion('Do you confirm generation', 'yes', '?'), true);
72
            if (!$questionHelper->ask($input, $output, $confirmationQuestion)) {
73
                $output->writeln('<error>Command aborted</error>');
74
75
                return 1;
76
            }
77
        }
78
79
        GeneratorUtils::ensureOptionsProvided($input, array('namespace', 'dir'));
80
81
        $namespace = Validators::validateBundleNamespace($input->getOption('namespace'));
82
        if (!$bundle = $input->getOption('bundle-name')) {
83
            $bundle = strtr($namespace, array('\\' => ''));
84
        }
85
        $bundle = Validators::validateBundleName($bundle);
86
        $dir = $this::validateTargetDir($input->getOption('dir'), $bundle, $namespace);
87
        $format = 'yml';
88
89
        $questionHelper->writeSection($output, 'Bundle generation');
90
91
        if (!$this
92
            ->getContainer()
93
            ->get('filesystem')
94
            ->isAbsolutePath($dir)
95
        ) {
96
            $dir = getcwd() . '/' . $dir;
97
        }
98
99
        $generator = $this->getGenerator($this->getApplication()->getKernel()->getBundle('KunstmaanGeneratorBundle'));
100
        $generator->generate($namespace, $bundle, $dir, $format);
101
102
        $output->writeln('Generating the bundle code: <info>OK</info>');
103
104
        $errors = array();
105
        $runner = $questionHelper->getRunner($output, $errors);
106
107
        // check that the namespace is already autoloaded
108
        $runner($this->checkAutoloader($output, $namespace, $bundle));
109
110
        // register the bundle in the Kernel class
111
        $runner($this->updateKernel($questionHelper, $input, $output, $this->getContainer()->get('kernel'), $namespace, $bundle));
112
113
        // routing
114
        $runner($this->updateRouting($questionHelper, $input, $output, $bundle, $format));
115
116
        $questionHelper->writeGeneratorSummary($output, $errors);
117
    }
118
119
    /**
120
     * Executes the command.
121
     *
122
     * @param InputInterface  $input  An InputInterface instance
123
     * @param OutputInterface $output An OutputInterface instance
124
     */
125
    protected function interact(InputInterface $input, OutputInterface $output)
126
    {
127
        $questionHelper = $this->getQuestionHelper();
128
        $questionHelper->writeSection($output, 'Welcome to the Kunstmaan bundle generator');
129
130
        // namespace
131
        $output
132
            ->writeln(
133
                array('', 'Your application code must be written in <comment>bundles</comment>. This command helps', 'you generate them easily.', '',
0 ignored issues
show
array('', 'Your applicat...oid any problems.', '') is of type array<integer,string,{"0...string","14":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

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...
134
                    'Each bundle is hosted under a namespace (like <comment>Acme/Bundle/BlogBundle</comment>).',
135
                    'The namespace should begin with a "vendor" name like your company name, your', 'project name, or your client name, followed by one or more optional category',
136
                    'sub-namespaces, and it should end with the bundle name itself', '(which must have <comment>Bundle</comment> as a suffix).', '',
137
                    'See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index-1 for more', 'details on bundle naming conventions.', '',
138
                    'Use <comment>/</comment> instead of <comment>\\ </comment>for the namespace delimiter to avoid any problems.', '', ));
139
140
        $question = new Question($questionHelper->getQuestion('Bundle namespace', $input->getOption('namespace')), $input->getOption('namespace'));
141
        $question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateBundleNamespace'));
142
        $namespace = $questionHelper->ask($input, $output, $question);
143
        $input->setOption('namespace', $namespace);
144
145
        // bundle name
146
        if ($input->getOption('bundle-name')) {
147
            $bundle = $input->getOption('bundle-name');
148
        } else {
149
            $bundle = strtr($namespace, array('\\Bundle\\' => '', '\\' => ''));
150
        }
151
        $output
152
            ->writeln(
153
                array('', 'In your code, a bundle is often referenced by its name. It can be the', 'concatenation of all namespace parts but it\'s really up to you to come',
0 ignored issues
show
array('', 'In your code,...le . '</comment>.', '') is of type array<integer,string,{"0..."string","5":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

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...
154
                    'up with a unique name (a good practice is to start with the vendor name).', 'Based on the namespace, we suggest <comment>' . $bundle . '</comment>.', '', ));
155
        $question = new Question($questionHelper->getQuestion('Bundle name', $bundle), $bundle);
156
        $question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateBundleName'));
157
        $bundle = $questionHelper->ask($input, $output, $question);
158
        $input->setOption('bundle-name', $bundle);
159
160
        // target dir
161
        $dir = $input->getOption('dir') ?: dirname($this
162
                ->getContainer()
163
                ->getParameter('kernel.root_dir')) . '/src';
164
        $output->writeln(array('', 'The bundle can be generated anywhere. The suggested default directory uses', 'the standard conventions.', ''));
165
        $question = new Question($questionHelper->getQuestion('Target directory', $dir), $dir);
166
        $question->setValidator(function ($dir) use ($bundle, $namespace) {
167
            return $this::validateTargetDir($dir, $bundle, $namespace);
168
        });
169
        $dir = $questionHelper->ask($input, $output, $question);
170
        $input->setOption('dir', $dir);
171
172
        // format
173
        $output->writeln(array('', 'Determine the format to use for the generated configuration.', ''));
174
        $output->writeln(array('', 'Determined \'yml\' to be used as the format for the generated configuration', ''));
175
        $format = 'yml';
176
177
        // summary
178
        $output
179
            ->writeln(
180
                array('', $this
0 ignored issues
show
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...
array('', $this->getHelp...le, $dir, $format), '') is of type array<integer,?,{"0":"st..."string","4":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

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...
181
                    ->getHelper('formatter')
182
                    ->formatBlock('Summary before generation', 'bg=blue;fg=white', true), '',
183
                    sprintf("You are going to generate a \"<info>%s\\%s</info>\" bundle\nin \"<info>%s</info>\" using the \"<info>%s</info>\" format.", $namespace, $bundle, $dir, $format),
184
                    '', ));
185
    }
186
187
    /**
188
     * @param OutputInterface $output    The output
189
     * @param string          $namespace The namespace
190
     * @param string          $bundle    The bundle name
191
     *
192
     * @return array
193
     */
194
    protected function checkAutoloader(OutputInterface $output, $namespace, $bundle)
195
    {
196
        $output->write('Checking that the bundle is autoloaded: ');
197
        if (!class_exists($namespace . '\\' . $bundle)) {
198
            return array('- Edit the <comment>composer.json</comment> file and register the bundle', '  namespace in the "autoload" section:', '');
199
        }
200
    }
201
202
    /**
203
     * @param QuestionHelper  $questionHelper The question helper
204
     * @param InputInterface  $input          The command input
205
     * @param OutputInterface $output         The command output
206
     * @param KernelInterface $kernel         The kernel
207
     * @param string          $namespace      The namespace
208
     * @param string          $bundle         The bundle
209
     *
210
     * @return array
211
     */
212
    protected function updateKernel(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output, KernelInterface $kernel, $namespace, $bundle)
213
    {
214
        $auto = true;
215 View Code Duplication
        if ($input->isInteractive()) {
216
            $confirmationQuestion = new ConfirmationQuestion($questionHelper->getQuestion('Confirm automatic update of your Kernel', 'yes', '?'), true);
217
            $auto = $questionHelper->ask($input, $output, $confirmationQuestion);
218
        }
219
220
        $output->write('Enabling the bundle inside the Kernel: ');
221
        $manip = new KernelManipulator($kernel);
222
223
        try {
224
            $ret = $auto ? $manip->addBundle($namespace . '\\' . $bundle) : false;
225
226
            if (!$ret) {
227
                $reflected = new \ReflectionObject($kernel);
228
229
                return array(sprintf('- Edit <comment>%s</comment>', $reflected->getFilename()), '  and add the following bundle in the <comment>AppKernel::registerBundles()</comment> method:', '',
230
                    sprintf('    <comment>new %s(),</comment>', $namespace . '\\' . $bundle), '', );
231
            }
232
        } catch (\RuntimeException $e) {
233
            return array(sprintf('Bundle <comment>%s</comment> is already defined in <comment>AppKernel::registerBundles()</comment>.', $namespace . '\\' . $bundle), '');
234
        }
235
    }
236
237
    /**
238
     * @param QuestionHelper  $questionHelper The question helper
239
     * @param InputInterface  $input          The command input
240
     * @param OutputInterface $output         The command output
241
     * @param string          $bundle         The bundle name
242
     * @param string          $format         The format
243
     *
244
     * @return array
245
     */
246
    protected function updateRouting(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output, $bundle, $format)
247
    {
248
        $auto = true;
249 View Code Duplication
        if ($input->isInteractive()) {
250
            $confirmationQuestion = new ConfirmationQuestion($questionHelper->getQuestion('Confirm automatic update of the Routing', 'yes', '?'), true);
251
            $auto = $questionHelper->ask($input, $output, $confirmationQuestion);
252
        }
253
254
        $output->write('Importing the bundle routing resource: ');
255
        $routing = new RoutingManipulator($this
256
                ->getContainer()
257
                ->getParameter('kernel.root_dir') . '/config/routing.yml');
258
259
        try {
260
            $ret = $auto ? $routing->addResource($bundle, $format) : false;
261
            if (!$ret) {
262
                $help = sprintf("        <comment>resource: \"@%s/Resources/config/routing.yml\"</comment>\n", $bundle);
263
                $help .= "        <comment>prefix:   /</comment>\n";
264
265
                return array('- Import the bundle\'s routing resource in the app main routing file:', '', sprintf('    <comment>%s:</comment>', $bundle), $help, '');
266
            }
267
        } catch (\RuntimeException $e) {
268
            return array(sprintf('Bundle <comment>%s</comment> is already imported.', $bundle), '');
269
        }
270
    }
271
272
    protected function createGenerator()
273
    {
274
        return new BundleGenerator();
275
    }
276
277
    /**
278
     * Validation function taken from <3.0 release of Sensio Generator bundle
279
     *
280
     * @param string $dir       The target directory
281
     * @param string $bundle    The bundle name
282
     * @param string $namespace The namespace
283
     *
284
     * @return string
285
     */
286
    public static function validateTargetDir($dir, $bundle, $namespace)
287
    {
288
        // add trailing / if necessary
289
        return '/' === substr($dir, -1, 1) ? $dir : $dir.'/';
290
    }
291
}
292