GenerateCrudCommand::getRepositoryGenerator()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 9
loc 9
rs 9.6666
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Pgs\RestfonyBundle\Command;
13
14
use Pgs\RestfonyBundle\Generator\DoctrineCrudGenerator;
15
use Pgs\RestfonyBundle\Generator\DoctrineFormGenerator;
16
use Pgs\RestfonyBundle\Generator\DoctrineManagerGenerator;
17
use Pgs\RestfonyBundle\Generator\DoctrineRepositoryGenerator;
18
use Pgs\RestfonyBundle\Generator\DoctrineSerializationConfigGenerator;
19
use Pgs\RestfonyBundle\Manipulator\RestConfigManipulator;
20
use Pgs\RestfonyBundle\Manipulator\RoutingManipulator;
21
use Sensio\Bundle\GeneratorBundle\Command\Validators;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Input\InputArgument;
24
use Symfony\Component\Console\Input\InputInterface;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
27
use Symfony\Component\Console\Question\Question;
28
use Symfony\Component\Console\Question\ConfirmationQuestion;
29
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
30
31
/**
32
 * Generates a Restful CRUD for a Doctrine entity.
33
 *
34
 * @author Lech Groblewicz <[email protected]>
35
 */
36
class GenerateCrudCommand extends GeneratorCommand
37
{
38
    protected $serializationConfigGenerator;
39
    protected $managerGenerator;
40
    protected $repositoryGenerator;
41
    protected $entity;
42
    protected $bundle;
43
    private $formGenerator;
44
45
    /**
46
     * @see Command
47
     */
48
    protected function configure()
49
    {
50
        $this
51
            ->setDefinition(array(
52
                new InputArgument('entity', InputArgument::OPTIONAL, 'The entity class name to initialize (shortcut notation)'),
53
                new InputOption('entity', '', InputOption::VALUE_REQUIRED, 'The entity class name to initialize (shortcut notation)'),
54
                new InputOption('route-prefix', '', InputOption::VALUE_REQUIRED, 'The route prefix'),
55
                new InputOption('with-write', '', InputOption::VALUE_NONE, 'Whether or not to generate create, new and delete actions'),
56
                new InputOption('format', '', InputOption::VALUE_REQUIRED, 'Use the format for configuration files (php, xml, yml, or annotation)', 'annotation'),
57
                new InputOption('overwrite', '', InputOption::VALUE_NONE, 'Do not stop the generation if crud controller already exist, thus overwriting all generated files'),
58
            ))
59
            ->setDescription('Generates a CRUD based on a Doctrine entity')
60
            ->setHelp(<<<EOT
61
The <info>pgs:generate:crud</info> command generates a CRUD based on a Doctrine entity.
62
63
The default command only generates the list and show actions.
64
65
<info>php app/console pgs:generate:crud --entity=AcmeBlogBundle:Post --route-prefix=post_admin</info>
66
67
Using the --with-write option allows to generate the new, edit and delete actions.
68
69
<info>php app/console pgs:generate:crud --entity=AcmeBlogBundle:Post --route-prefix=post_admin --with-write</info>
70
71
Every generated file is based on a template. There are default templates but they can be overridden by placing custom templates in one of the following locations, by order of priority:
72
73
<info>BUNDLE_PATH/Resources/SensioGeneratorBundle/skeleton/crud
74
APP_PATH/Resources/SensioGeneratorBundle/skeleton/crud</info>
75
76
And
77
78
<info>__bundle_path__/Resources/SensioGeneratorBundle/skeleton/form
79
__project_root__/app/Resources/SensioGeneratorBundle/skeleton/form</info>
80
81
You can check https://github.com/sensio/SensioGeneratorBundle/tree/master/Resources/skeleton
82
in order to know the file structure of the skeleton
83
EOT
84
            )
85
            ->setName('pgs:generate:crud')
86
        ;
87
    }
88
89
    /**
90
     * @see Command
91
     *
92
     * @param InputInterface  $input
93
     * @param OutputInterface $output
94
     *
95
     * @return int|null
96
     */
97
    protected function execute(InputInterface $input, OutputInterface $output)
98
    {
99
        $questionHelper = $this->getQuestionHelper();
100
101 View Code Duplication
        if ($input->isInteractive()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
102
            $question = new ConfirmationQuestion($questionHelper->getQuestion('Do you confirm generation', 'yes', '?'), true);
103
            if (!$questionHelper->ask($input, $output, $question)) {
104
                $output->writeln('<error>Command aborted</error>');
105
106
                return 1;
107
            }
108
        }
109
110
        $entity = Validators::validateEntityName($input->getOption('entity'));
111
        list($bundle, $entity) = $this->parseShortcutNotation($entity);
112
        $this->entity = $entity;
113
114
        $format = Validators::validateFormat($input->getOption('format'));
115
        $prefix = $this->getRoutePrefix($input, $entity);
116
        $withWrite = $input->getOption('with-write');
117
        $forceOverwrite = $input->getOption('overwrite');
118
119
        $questionHelper->writeSection($output, 'CRUD generation');
120
121
        $entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle).'\\'.$entity;
122
        $metadata    = $this->getEntityMetadata($entityClass);
123
        $bundle      = $this->getContainer()->get('kernel')->getBundle($bundle);
124
        $this->bundle = $bundle;
125
126
        $generator = $this->getGenerator($bundle);
127
        $generator->generate($bundle, $entity, $metadata[0], $withWrite, $forceOverwrite);
128
129
        $output->writeln('Generating the CRUD code: <info>OK</info>');
130
131
        $errors = array();
132
        $runner = $questionHelper->getRunner($output, $errors);
133
134
        // form
135
        $output->write('Generating the Form code: ');
136
        if ($this->generateForm($bundle, $entity, $metadata, $forceOverwrite)) {
137
            $output->writeln('<info>OK</info>');
138
        } else {
139
            $output->writeln('<comment>Already exists, skipping</comment>');
140
        }
141
142
        $output->write('Generating the Repository code: ');
143
        if ($this->generateRepository($bundle, $entity, $forceOverwrite)) {
144
            $output->writeln('<info>OK</info>');
145
        } else {
146
            $output->writeln('<comment>Already exists, skipping</comment>');
147
        }
148
149
        $output->write('Generating the Manager code: ');
150
        if ($this->generateManager($bundle, $entity, $forceOverwrite)) {
151
            $output->writeln('<info>OK</info>');
152
        } else {
153
            $output->writeln('<comment>Already exists, skipping</comment>');
154
        }
155
156
        $runner($this->updateRouting($questionHelper, $input, $output, $bundle, $format, $entity, $prefix));
157
158
        $output->write('Generating the serialization config: ');
159
        if ($this->generateSerializationConfig($bundle, $entity, $metadata, $forceOverwrite)) {
160
            $output->writeln('<info>OK</info>');
161
        } else {
162
            $output->writeln('<comment>Already exists, skipping</comment>');
163
        }
164
165
        $runner($this->updateRestRouting($questionHelper, $input, $output, $bundle, $entity, $metadata));
166
167
        $questionHelper->writeGeneratorSummary($output, $errors);
168
    }
169
170
    protected function interact(InputInterface $input, OutputInterface $output)
171
    {
172
        $questionHelper = $this->getQuestionHelper();
173
        $questionHelper->writeSection($output, 'Welcome to the Doctrine2 CRUD generator');
174
175
        // namespace
176
        $output->writeln(array(
177
            '',
178
            'This command helps you generate CRUD controllers and templates.',
179
            '',
180
            'First, you need to give the entity for which you want to generate a CRUD.',
181
            'You can give an entity that does not exist yet and the wizard will help',
182
            'you defining it.',
183
            '',
184
            'You must use the shortcut notation like <comment>AcmeBlogBundle:Post</comment>.',
185
            '',
186
        ));
187
188
        if ($input->hasArgument('entity') && $input->getArgument('entity') !== '') {
189
            $input->setOption('entity', $input->getArgument('entity'));
190
        }
191
192
        $question = new Question($questionHelper->getQuestion('The Entity shortcut name', $input->getOption('entity')), $input->getOption('entity'));
193
        $question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateEntityName'));
194
        $entity = $questionHelper->ask($input, $output, $question);
195
        $input->setOption('entity', $entity);
196
        list($bundle, $entity) = $this->parseShortcutNotation($entity);
197
198
        // write?
199
        $withWrite = (bool) $input->getOption('with-write');
200
201
        $output->writeln(array(
202
            '',
203
            'By default, the generator creates two actions: list and show.',
204
            'You can also ask it to generate "write" actions: new, update, and delete.',
205
            '',
206
        ));
207
        $question = new ConfirmationQuestion($questionHelper->getQuestion('Do you want to generate the "write" actions', $withWrite ? 'yes' : 'no', '?', $withWrite), $withWrite);
208
209
        $withWrite = $questionHelper->ask($input, $output, $question);
210
        $input->setOption('with-write', $withWrite);
211
212
        // summary
213
        $output->writeln(array(
214
            '',
215
            $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...
216
            '',
217
            sprintf("You are going to generate a REST CRUD controller for \"<info>%s:%s</info>\"", $bundle, $entity),
218
            '',
219
        ));
220
    }
221
222
    /**
223
     * Tries to generate forms if they don't exist yet and if we need write operations on entities.
224
     */
225 View Code Duplication
    protected function generateSerializationConfig($bundle, $entity, $metadata, $forceOverwrite)
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...
226
    {
227
        try {
228
            $this->getSerializationConfigGenerator($bundle, $entity)->generate($metadata[0], $forceOverwrite);
229
        } catch (\RuntimeException $e) {
230
            return false;
231
        }
232
233
        return true;
234
    }
235
236
    /**
237
     * Tries to generate forms if they don't exist yet and if we need write operations on entities.
238
     */
239 View Code Duplication
    protected function generateForm($bundle, $entity, $metadata, $forceOverwrite)
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...
240
    {
241
        try {
242
            $this->getFormGenerator($bundle, $entity, $metadata[0])->generate($forceOverwrite);
243
        } catch (\RuntimeException $e) {
244
            return false;
245
        }
246
247
        return true;
248
    }
249
250
    protected function generateManager($bundle, $entity, $forceOverwrite)
251
    {
252
        try {
253
            $this->getManagerGenerator($bundle, $entity)->generate($forceOverwrite);
254
        } catch (\RuntimeException $e) {
255
            return false;
256
        }
257
258
        return true;
259
    }
260
261
    protected function generateRepository($bundle, $entity, $forceOverwrite)
262
    {
263
        try {
264
            $this->getRepositoryGenerator($bundle, $entity)->generate($forceOverwrite);
265
        } catch (\RuntimeException $e) {
266
            return false;
267
        }
268
269
        return true;
270
    }
271
272
    protected function updateRestRouting(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output, BundleInterface $bundle, $entity, $metadata)
0 ignored issues
show
Unused Code introduced by
The parameter $questionHelper is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
273
    {
274
        $output->write('Importing REST config: ');
275
        $this->getContainer()->get('filesystem')->mkdir($bundle->getPath().'/Resources/config/');
276
        $config = new RestConfigManipulator($this->getContainer()->get('kernel')->getRootDir().'/config/rest.yml');
277
        $config->addResource($bundle->getNamespace(), $entity, $metadata);
278
    }
279
280
    protected function updateRouting(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output, BundleInterface $bundle, $format, $entity, $prefix)
281
    {
282
        $auto = true;
283 View Code Duplication
        if ($input->isInteractive()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
284
            $question = new ConfirmationQuestion($questionHelper->getQuestion('Confirm automatic update of the Routing', 'yes', '?'), true);
285
            $auto = $questionHelper->ask($input, $output, $question);
286
        }
287
288
        $output->write('Importing the CRUD routes: ');
289
        $this->getContainer()->get('filesystem')->mkdir($bundle->getPath().'/Resources/config/');
290
        $routing = new RoutingManipulator($bundle->getPath().'/Resources/config/rest_routing.yml');
291
292
        $resourceAdded = false;
293
294
        if ($auto) {
295
            $resourceAdded = $routing->addResource($bundle->getName(), '/'.$prefix);
296
        }
297
298
        if (!$resourceAdded) {
299
            $help = sprintf("        <comment>resource: \"@%s/Resources/config/routing/%s.%s\"</comment>\n", $bundle->getName(), strtolower(str_replace('\\', '_', $entity)), $format);
300
            $help .= sprintf("        <comment>prefix:   /%s</comment>\n", $prefix);
301
302
            return array(
303
                '- Import the bundle\'s routing resource in the bundle routing file',
304
                sprintf('  (%s).', $bundle->getPath().'/Resources/config/rest_routing.yml'),
305
                '',
306
                sprintf('    <comment>%s:</comment>', $bundle->getName().('' !== $prefix ? '_'.str_replace('/', '_', $prefix) : '')),
307
                $help,
308
                '',
309
            );
310
        }
311
    }
312
313
    protected function getRoutePrefix(InputInterface $input, $entity)
314
    {
315
        $prefix = $input->getOption('route-prefix') ?: strtolower(str_replace(array('\\', '/'), '_', $entity));
316
317
        if ($prefix && '/' === $prefix[0]) {
318
            $prefix = substr($prefix, 1);
319
        }
320
321
        return $prefix;
322
    }
323
324
    protected function createGenerator()
325
    {
326
        return new DoctrineCrudGenerator($this->getContainer()->get('filesystem'), $this->bundle, $this->entity);
327
    }
328
329 View Code Duplication
    protected function getFormGenerator(BundleInterface $bundle, $entity, $metadata)
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...
330
    {
331
        if (null === $this->formGenerator) {
332
            $this->formGenerator = new DoctrineFormGenerator($bundle, $entity, $metadata);
333
            $this->formGenerator->setSkeletonDirs($this->getSkeletonDirs($bundle));
334
        }
335
336
        return $this->formGenerator;
337
    }
338
339 View Code Duplication
    protected function getSerializationConfigGenerator(BundleInterface $bundle, $entity)
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...
340
    {
341
        if (null === $this->serializationConfigGenerator) {
342
            $this->serializationConfigGenerator = new DoctrineSerializationConfigGenerator($bundle, $entity);
343
            $this->serializationConfigGenerator->setSkeletonDirs($this->getSkeletonDirs($bundle));
344
        }
345
346
        return $this->serializationConfigGenerator;
347
    }
348
349
    public function setFormGenerator(DoctrineFormGenerator $formGenerator)
350
    {
351
        $this->formGenerator = $formGenerator;
352
    }
353
354 View Code Duplication
    protected function getManagerGenerator(BundleInterface $bundle, $entity)
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...
355
    {
356
        if (null === $this->managerGenerator) {
357
            $this->managerGenerator = new DoctrineManagerGenerator($bundle, $entity);
358
            $this->managerGenerator->setSkeletonDirs($this->getSkeletonDirs($bundle));
359
        }
360
361
        return $this->managerGenerator;
362
    }
363
364 View Code Duplication
    protected function getRepositoryGenerator(BundleInterface $bundle, $entity)
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...
365
    {
366
        if (null === $this->repositoryGenerator) {
367
            $this->repositoryGenerator = new DoctrineRepositoryGenerator($bundle, $entity);
368
            $this->repositoryGenerator->setSkeletonDirs($this->getSkeletonDirs($bundle));
369
        }
370
371
        return $this->repositoryGenerator;
372
    }
373
}
374