Completed
Push — master ( c12d12...719a5f )
by Kristof
32:38 queued 19:20
created

KunstmaanGenerateCommand::getEntityFields()   D

Complexity

Conditions 15
Paths 13

Size

Total Lines 197

Duplication

Lines 59
Ratio 29.95 %

Importance

Changes 0
Metric Value
dl 59
loc 197
rs 4.7333
c 0
b 0
f 0
cc 15
nc 13
nop 12

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Kunstmaan\GeneratorBundle\Command;
4
5
use Kunstmaan\AdminBundle\Form\WysiwygType;
6
use Kunstmaan\GeneratorBundle\Helper\CommandAssistant;
7
use Kunstmaan\GeneratorBundle\Helper\GeneratorUtils;
8
use Kunstmaan\GeneratorBundle\Helper\Sf4AppBundle;
9
use Kunstmaan\MediaBundle\Form\Type\MediaType;
10
use Kunstmaan\NodeBundle\Form\Type\URLChooserType;
11
use Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineCommand;
12
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\DependencyInjection\Container;
16
use Symfony\Component\Finder\Finder;
17
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
18
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
19
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
20
use Symfony\Component\Form\Extension\Core\Type\NumberType;
21
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
22
use Symfony\Component\Form\Extension\Core\Type\TextType;
23
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
24
use Symfony\Component\HttpKernel\Kernel;
25
use Symfony\Component\Yaml\Exception\ParseException;
26
use Symfony\Component\Yaml\Yaml;
27
28
abstract class KunstmaanGenerateCommand extends GenerateDoctrineCommand
29
{
30
    /**
31
     * @var CommandAssistant
32
     */
33
    protected $assistant;
34
35
    /**
36
     * Interacts with the user.
37
     *
38
     * @param InputInterface  $input  An InputInterface instance
39
     * @param OutputInterface $output An OutputInterface instance
40
     */
41
    protected function interact(InputInterface $input, OutputInterface $output)
42
    {
43
        $this->setInputAndOutput($input, $output);
44
45
        $welcomeText = $this->getWelcomeText();
46
        if (!empty($welcomeText)) {
47
            $this->assistant->writeSection($this->getWelcomeText());
48
        }
49
50
        $this->doInteract();
51
    }
52
53
    /**
54
     * @param InputInterface  $input
55
     * @param OutputInterface $output
56
     *
57
     * @return int|null
58
     */
59
    protected function execute(InputInterface $input, OutputInterface $output)
60
    {
61
        $this->setInputAndOutput($input, $output);
62
63
        return $this->doExecute();
64
    }
65
66
    /**
67
     * Create the CommandAssistant.
68
     *
69
     * @param InputInterface  $input
70
     * @param OutputInterface $output
71
     */
72
    private function setInputAndOutput(InputInterface $input, OutputInterface $output)
73
    {
74
        if (is_null($this->assistant)) {
75
            $this->assistant = new CommandAssistant();
76
            $this->assistant->setQuestionHelper($this->getQuestionHelper());
77
            $this->assistant->setKernel($this->getApplication()->getKernel());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Console\Application as the method getKernel() does only exist in the following sub-classes of Symfony\Component\Console\Application: Symfony\Bundle\FrameworkBundle\Console\Application. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
78
        }
79
        $this->assistant->setOutput($output);
80
        $this->assistant->setInput($input);
81
    }
82
83
    /**
84
     * Do the interaction with the end user.
85
     */
86
    abstract protected function doInteract();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
87
88
    /**
89
     * This function implements the final execution of the Generator.
90
     * It calls the execute function with the correct parameters.
91
     */
92
    abstract protected function doExecute();
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
93
94
    /**
95
     * The text to be displayed on top of the generator.
96
     *
97
     * @return string|array
98
     */
99
    abstract protected function getWelcomeText();
100
101
    /**
102
     * Get an array with all the bundles the user has created.
103
     *
104
     * @return array
105
     */
106
    protected function getOwnBundles()
107
    {
108
        $bundles = array();
109
        $counter = 1;
110
        $dir = dirname($this->getContainer()->getParameter('kernel.root_dir').'/').'/src/';
111
        $finder = new Finder();
112
        $finder->in($dir)->name('*Bundle.php');
113
114
        foreach ($finder as $file) {
115
            $bundles[$counter++] = array(
116
                'name' => basename($file->getFilename(), '.php'),
117
                'namespace' => $file->getRelativePath(),
118
                'dir' => $file->getPath(),
119
            );
120
        }
121
122
        return $bundles;
123
    }
124
125
    /**
126
     * Check that a bundle is available (loaded in AppKernel)
127
     *
128
     * @param string $bundleName
129
     *
130
     * @return bool
131
     */
132
    protected function isBundleAvailable($bundleName)
133
    {
134
        $allBundles = array_keys($this->assistant->getKernel()->getBundles());
135
136
        return in_array($bundleName, $allBundles);
137
    }
138
139
    /**
140
     * Asks for the prefix and sets it on the InputInterface as the 'prefix' option, if this option is not set yet.
141
     * Will set the default to a snake_cased namespace when the namespace has been set on the InputInterface.
142
     *
143
     * @param array  $text      What you want printed before the prefix is asked. If null is provided it'll write a default text.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $text not be null|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
144
     * @param string $namespace An optional namespace. If this is set it'll create the default based on this prefix.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $namespace not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
145
     *                          If it's not provided it'll check if the InputInterface already has the namespace option.
146
     *
147
     * @return string The prefix. But it's also been set on the InputInterface.
148
     */
149
    protected function askForPrefix(array $text = null, $namespace = null)
150
    {
151
        $prefix = $this->assistant->getOptionOrDefault('prefix', null);
152
153
        if (is_null($text)) {
154
            $text = array(
155
                'You can add a prefix to the table names of the generated entities for example: '.
156
                '<comment>projectname_bundlename_</comment>',
157
                'Enter an underscore \'_\' if you don\'t want a prefix.',
158
                '',
159
            );
160
        }
161
162
        while (is_null($prefix)) {
163
            if (count($text) > 0) {
164
                $this->assistant->writeLine($text);
165
            }
166
167
            if (is_null($namespace) || empty($namespace)) {
168
                $namespace = $this->assistant->getOption('namespace');
169
            } else {
170
                $namespace = $this->fixNamespace($namespace);
171
            }
172
            $defaultPrefix = GeneratorUtils::cleanPrefix($this->convertNamespaceToSnakeCase($namespace));
173
            $prefix = GeneratorUtils::cleanPrefix($this->assistant->ask('Tablename prefix', $defaultPrefix));
174
175
            if ($prefix == '') {
176
                break;
177
            }
178
179
            $output = $this->assistant->getOutput();
180 View Code Duplication
            if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $prefix)) {
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...
181
                $output->writeln(sprintf('<bg=red> "%s" contains invalid characters</>', $prefix));
182
                $prefix = $text = null;
183
184
                continue;
185
            }
186
187
            $this->assistant->setOption('prefix', $prefix);
188
        }
189
190
        return $prefix;
191
    }
192
193
    /**
194
     * Converts something like Namespace\BundleNameBundle to namespace_bundlenamebundle.
195
     *
196
     * @param string $namespace
197
     *
198
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
199
     */
200 View Code Duplication
    private function convertNamespaceToSnakeCase($namespace)
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...
201
    {
202
        if (is_null($namespace)) {
203
            return null;
204
        }
205
206
        return str_replace('/', '_', strtolower($this->fixNamespace($namespace)));
207
    }
208
209
    /**
210
     * Replaces '\' with '/'.
211
     *
212
     * @param $namespace
213
     *
214
     * @return mixed
215
     */
216
    private function fixNamespace($namespace)
217
    {
218
        return str_replace('\\', '/', $namespace);
219
    }
220
221
    /**
222
     * Ask for which bundle we need to generate something. It there is only one custom bundle
223
     * created by the user, we don't ask anything and just use that bundle. If the user provided
224
     * a namespace as input option, we try to get that bundle first.
225
     *
226
     * @param string      $objectName          The thing we are going to create (pagepart, bundle, layout, ...)
227
     * @param string|null $namespace           The namespace provided as input option
228
     * @param string      $questionMoreBundles
229
     * @param string      $questionOneBundle
230
     *
231
     * @return BundleInterface
232
     */
233
    protected function askForBundleName($objectName, $namespace = null, $questionMoreBundles = "\nIn which bundle do you want to create the %s", $questionOneBundle = "The %s will be created for the <comment>%s</comment> bundle.\n")
234
    {
235
        if (Kernel::VERSION_ID >= 40000) {
236
            return new Sf4AppBundle($this->getContainer()->getParameter('kernel.project_dir'));
237
        }
238
239
        $ownBundles = $this->getOwnBundles();
240
        if (count($ownBundles) <= 0) {
241
            $this->assistant->writeError("Looks like you don't have created any bundles for your project...", true);
242
        }
243
244
        // If the user provided the namespace as input option
245
        if (!is_null($namespace)) {
246
            foreach ($ownBundles as $key => $bundleInfo) {
247
                if (GeneratorUtils::fixNamespace($namespace) == GeneratorUtils::fixNamespace(
248
                        $bundleInfo['namespace']
249
                    )
250
                ) {
251
                    $bundleName = $bundleInfo['name'];
252
253
                    break;
254
                }
255
            }
256
257
            // When the provided namespace was not found, we show an error on the screen and ask the bundle again
258
            if (empty($bundleName)) {
259
                $this->assistant->writeError("The provided bundle namespace '$namespace' was not found...");
260
            }
261
        }
262
263
        if (empty($bundleName)) {
264
            // If we only have 1 bundle, we don't need to ask
265
            if (count($ownBundles) > 1) {
266
                $bundleSelect = array();
267
                foreach ($ownBundles as $key => $bundleInfo) {
268
                    $bundleSelect[$key] = $bundleInfo['name'];
269
                }
270
                $bundleId = $this->assistant->askSelect(
271
                    sprintf($questionMoreBundles, $objectName),
272
                    $bundleSelect
273
                );
274
                $bundleName = $ownBundles[$bundleId]['name'];
275
276
                $this->assistant->writeLine('');
277
            } else {
278
                $bundleName = $ownBundles[1]['name'];
279
                $this->assistant->writeLine(
280
                    array(sprintf($questionOneBundle, $objectName, $bundleName))
281
                );
282
            }
283
        }
284
285
        $bundle = $this->assistant->getKernel()->getBundle($bundleName);
286
287
        return $bundle;
288
    }
289
290
    /**
291
     * Ask the end user to select one (or more) section configuration(s).
292
     *
293
     * @param string          $question
294
     * @param BundleInterface $bundle
295
     * @param bool            $multiple
296
     * @param string|null     $context
297
     * @param array           $defaultSections
298
     *
299
     * @return array|null
300
     */
301
    protected function askForSections(
302
        $question,
303
        BundleInterface $bundle,
304
        $multiple = false,
305
        $context = null,
306
        $defaultSections = array()
307
    ) {
308
        $allSections = $this->getAvailableSections($bundle, $context, $defaultSections);
309
        $sections = array();
310
311
        // If there are more options to choose from, we ask the end user
312
        if (count($allSections) > 0) {
313
            $sectionSelect = array();
314
            foreach ($allSections as $key => $sectionInfo) {
315
                $sectionSelect[$key] = $sectionInfo['name'].' ('.$sectionInfo['file'].')';
316
            }
317
            $this->assistant->writeLine('');
318
            $sectionIds = $this->assistant->askSelect($question, $sectionSelect, null, $multiple);
319
            if (is_array($sectionIds)) {
320
                foreach ($sectionIds as $id) {
321
                    $sections[] = $allSections[$id]['file'];
322
                }
323
            } else {
324
                $sections[] = $allSections[$sectionIds]['file'];
325
            }
326
        }
327
328
        if ($multiple) {
329
            return $sections;
330
        } else {
331
            return count($sections) > 0 ? $sections[0] : null;
332
        }
333
    }
334
335
    /**
336
     * Get an array with the available page sections. We also parse the yaml files to get more information about
337
     * the sections.
338
     *
339
     * @param BundleInterface $bundle          The bundle for which we want to get the section configuration
340
     * @param string|null     $context         If provided, only return configurations with this context
341
     * @param array           $defaultSections The default section configurations that are always available
342
     *
343
     * @return array
344
     */
345
    protected function getAvailableSections(BundleInterface $bundle, $context = null, $defaultSections = array())
346
    {
347
        $configs = array();
348
        $counter = 1;
349
350
        // Get the available sections from disc
351
        $dir = Kernel::VERSION_ID >= 40000 ? $this->getContainer()->getParameter('kernel.project_dir') . '/config/kunstmaancms/pageparts/' : $bundle->getPath() . '/Resources/config/pageparts/';
352
        if (file_exists($dir) && is_dir($dir)) {
353
            $finder = new Finder();
354
            $finder->files()->in($dir)->depth('== 0');
355
            foreach ($finder as $file) {
356
                $info = $this->getSectionInfo($dir, $file->getFileName());
357
358
                if (is_array($info) && (is_null($context) || $info['context'] == $context)) {
359
                    $configs[$counter++] = $info;
360
                    if (array_key_exists($info['file'], $defaultSections)) {
361
                        unset($defaultSections[$info['file']]);
362
                    }
363
                }
364
            }
365
        }
366
367
        // Add the default sections
368
        foreach ($defaultSections as $file => $info) {
369
            if (is_null($context) || $info['context'] == $context) {
370
                $configs[$counter++] = $info;
371
            }
372
        }
373
374
        return $configs;
375
    }
376
377
    /**
378
     * Get the information about a pagepart section configuration file.
379
     *
380
     * @param string $dir
381
     * @param string $file
382
     *
383
     * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
384
     */
385
    private function getSectionInfo($dir, $file)
386
    {
387
        $info = null;
388
389
        try {
390
            $data = Yaml::parse(file_get_contents($dir.$file));
391
392
            if (array_key_exists('kunstmaan_page_part', $data)) {
393
                //Get rid of the bundle config lines
394
                $data = array_values(array_values(array_values($data)[0])[0])[0];
395
            }
396
397
            $info = array(
398
                'name' => $data['name'],
399
                'context' => $data['context'],
400
                'file' => $file,
401
                //'file_clean' => substr($file, 0, strlen($file)-4)
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
402
            );
403
        } catch (ParseException $e) {
404
        }
405
406
        return $info;
407
    }
408
409
    /**
410
     * Get an array of fields that need to be added to the entity.
411
     *
412
     * @param BundleInterface $bundle
413
     * @param array           $reservedFields
414
     *
415
     * @return array
416
     */
417
    protected function askEntityFields(BundleInterface $bundle, array $reservedFields = array('id'))
418
    {
419
        $this->assistant->writeLine('<info>Available field types:</info> ');
420
        $typeSelect = $this->getTypes(true);
421
        foreach ($typeSelect as $type) {
422
            $this->assistant->writeLine(sprintf('<comment>- %s</comment>', $type));
423
        }
424
425
        $fields = array();
426
        $typeStrings = $this->getTypes();
427
        $mediaTypeSelect = $this->getMediaTypes();
428
        $generator = $this->getGenerator();
429
        $container = $this->getContainer();
430
431
        while (true) {
432
            $this->assistant->writeLine('');
433
434
            $fieldName = $this->assistant->askAndValidate(
435
                'New field name (press <return> to stop adding fields)',
436
                function ($name) use ($fields, $reservedFields, $generator) {
437
                    // The fields cannot exist in the reserved field list
438
                    if (in_array($name, $reservedFields)) {
439
                        throw new \InvalidArgumentException(sprintf(
440
                            'Field "%s" is already defined in the parent class',
441
                            $name
442
                        ));
443
                    }
444
445
                    // The fields cannot exist already
446
                    if (isset($fields[$name])) {
447
                        throw new \InvalidArgumentException(sprintf('Field "%s" is already defined', $name));
448
                    }
449
450
                    // Check reserved words
451
                    if ($generator->isReservedKeyword($name)) {
452
                        throw new \InvalidArgumentException(sprintf('Name "%s" is a reserved word', $name));
453
                    }
454
455
                    // Only accept a-z
456
                    if (!preg_match('/^[a-zA-Z][a-zA-Z_0-9]+$/', $name) && $name != '') {
457
                        throw new \InvalidArgumentException(sprintf('Name "%s" is invalid', $name));
458
                    }
459
460
                    return $name;
461
                }
462
            );
463
464
            // When <return> is entered
465
            if (!$fieldName) {
466
                break;
467
            }
468
469
            $typeId = $this->assistant->askSelect('Field type', $typeSelect);
470
471
            // If single -or multipe entity reference in chosen, we need to ask for the entity name
472
            if (in_array($typeStrings[$typeId], array('single_ref', 'multi_ref'))) {
473
                $bundleName = $bundle->getName();
474
                $question = "Reference entity name (eg. $bundleName:FaqItem, $bundleName:Blog/Comment)";
475
                $name = $this->assistant->askAndValidate(
476
                    $question,
477
                    function ($name) use ($generator, $container) {
478
                        /**
479
                         * Replace slash to backslash. Eg: CmsBundle:Blog/Comment --> CmsBundle:Blog\Comment
480
                         *
481
                         * @see \Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory::getMetadataFor()
482
                         * @see \Doctrine\ORM\Mapping\ClassMetadataFactory::getFqcnFromAlias()
483
                         */
484
                        $name = strtr($name, '/', '\\');
485
486
                        $parts = explode(':', $name);
487
488
                        // Should contain colon
489
                        if (count($parts) != 2) {
490
                            throw new \InvalidArgumentException(sprintf('"%s" is an invalid entity name', $name));
491
                        }
492
493
                        // Check reserved words
494
                        if ($generator->isReservedKeyword($parts[1])) {
495
                            throw new \InvalidArgumentException(sprintf('"%s" contains a reserved word', $name));
496
                        }
497
498
                        $em = $container->get('doctrine')->getManager();
499
500
                        try {
501
                            $em->getClassMetadata($name);
502
                        } catch (\Exception $e) {
503
                            throw new \InvalidArgumentException(sprintf('Entity "%s" not found', $name));
504
                        }
505
506
                        return $name;
507
                    },
508
                    null,
509
                    array($bundleName)
510
                );
511
512
                $extra = $name;
513
            } else {
514
                $extra = null;
515
            }
516
517
            // If image type, force image media filter
518
            if ($typeStrings[$typeId] == 'image') {
519
                $extra = 'image';
520
            }
521
522
            // If media type, ask for media filter
523
            if ($typeStrings[$typeId] == 'media') {
524
                $mediaTypeId = $this->assistant->askSelect('Media filter', $mediaTypeSelect);
525
                $extra = strtolower($mediaTypeSelect[$mediaTypeId]);
526
            }
527
528
            if ($typeStrings[$typeId] == 'image' || $typeStrings[$typeId] == 'media') {
529
                // Ask the allowed mimetypes for the media ojbect
530
                $mimeTypes = $this->assistant->ask('Do you want to limit the possible file types? Then specify a comma-seperated list of types (example: image/png,image/svg+xml), otherwise press ENTER', null);
531
                if (isset($mimeTypes)) {
532
                    $mimeTypes = explode(',', $mimeTypes);
533
                }
534
                $data = array(
535
                    'name' => $fieldName,
536
                    'type' => $typeStrings[$typeId],
537
                    'extra' => $extra,
538
                    'mimeTypes' => $mimeTypes,
539
                    'minHeight' => null,
540
                    'maxHeight' => null,
541
                    'minWidth' => null,
542
                    'maxWidth' => null,
543
                );
544
545
                if ($extra == 'image') {
546
                    $minHeight = $maxHeight = $minWidth = $maxWidth = null;
547
                    if ($this->assistant->askConfirmation('Do you want to add validation of the dimensions of the media object? (y/n)', 'n', '?', false)) {
548
                        // Ask the minimum height allowed for the image
549
                        $lengthValidation = function ($length) {
550
                            if ((is_numeric($length) && $length < 0) || (!is_numeric($length) && !empty($length))) {
551
                                throw new \InvalidArgumentException(sprintf('"%s" is not a valid length', $length));
552
                            } else {
553
                                return $length;
554
                            }
555
                        };
556
557
                        $minHeight = $this->assistant->askAndValidate('What is the minimum height for the media object? (in pixels)', $lengthValidation);
558
559
                        // Ask the maximum height allowed for the image
560
                        $maxHeight = $this->assistant->askAndValidate('What is the maximum height for the media object? (in pixels)', $lengthValidation);
561
562
                        // Ask the minimum width allowed for the image
563
                        $minWidth = $this->assistant->askAndValidate('What is the minimum width for the media object? (in pixels)', $lengthValidation);
564
565
                        //Ask the maximum width allowed for the image
566
                        $maxWidth = $this->assistant->askAndValidate('What is the maximum width for the media object? (in pixels)', $lengthValidation);
567
                    }
568
                    $data = array('name' => $fieldName, 'type' => 'image', 'extra' => $extra,
569
                        'minHeight' => $minHeight, 'maxHeight' => $maxHeight, 'minWidth' => $minWidth, 'maxWidth' => $maxWidth, 'mimeTypes' => $mimeTypes, );
570
                }
571
            } else {
572
                $data = array('name' => $fieldName, 'type' => $typeStrings[$typeId], 'extra' => $extra,
573
                    'minHeight' => null, 'maxHeight' => null, 'minWidth' => null, 'maxWidth' => null, 'mimeTypes' => null, );
574
            }
575
576
            $fields[$fieldName] = $data;
577
        }
578
579
        return $fields;
580
    }
581
582
    /**
583
     * Get all the available types.
584
     *
585
     * @param bool $niceNames
586
     *
587
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
588
     */
589
    private function getTypes($niceNames = false)
590
    {
591
        $counter = 1;
592
593
        $types = array();
594
        $types[$counter++] = $niceNames ? 'Single line text' : 'single_line';
595
        $types[$counter++] = $niceNames ? 'Multi line text' : 'multi_line';
596
        $types[$counter++] = $niceNames ? 'Wysiwyg' : 'wysiwyg';
597
        $types[$counter++] = $niceNames ? 'Link (url, text, new window)' : 'link';
598
        if ($this->isBundleAvailable('KunstmaanMediaPagePartBundle')) {
599
            $types[$counter++] = $niceNames ? 'Image (media, alt text)' : 'image';
600
            $types[$counter++] = $niceNames ? 'Media (File or Video or Slideshow)' : 'media';
601
        }
602
        $types[$counter++] = $niceNames ? 'Single entity reference' : 'single_ref';
603
        $types[$counter++] = $niceNames ? 'Multi entity reference' : 'multi_ref';
604
        $types[$counter++] = $niceNames ? 'Boolean' : 'boolean';
605
        $types[$counter++] = $niceNames ? 'Integer' : 'integer';
606
        $types[$counter++] = $niceNames ? 'Decimal number' : 'decimal';
607
        $types[$counter++] = $niceNames ? 'DateTime' : 'datetime';
608
609
        return $types;
610
    }
611
612
    /**
613
     * Get all available media types.
614
     *
615
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
616
     */
617
    private function getMediaTypes()
618
    {
619
        $counter = 1;
620
621
        $types = array();
622
        $types[$counter++] = 'None';
623
        $types[$counter++] = 'File';
624
        $types[$counter++] = 'Image';
625
        $types[$counter++] = 'Video';
626
627
        return $types;
628
    }
629
630
    /**
631
     * Get all the entity fields for a specific type.
632
     *
633
     * @param BundleInterface $bundle
634
     * @param                 $objectName
635
     * @param                 $prefix
636
     * @param                 $name
637
     * @param                 $type
638
     * @param null            $extra
639
     * @param bool            $allNullable
640
     * @param null            $minHeight
641
     * @param null            $maxHeight
642
     * @param null            $minWidth
643
     * @param null            $maxWidth
644
     * @param null            $mimeTypes
645
     *
646
     * @return array
647
     */
648
    protected function getEntityFields(
649
        BundleInterface $bundle,
650
        $objectName,
651
        $prefix,
652
        $name,
653
        $type,
654
        $extra = null,
655
        $allNullable = false,
656
        $minHeight = null,
657
        $maxHeight = null,
658
        $minWidth = null,
659
        $maxWidth = null,
660
        $mimeTypes = null
661
    ) {
662
        $fields = array();
663
        switch ($type) {
664 View Code Duplication
            case 'single_line':
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...
665
                $fields[$type][] = array(
666
                    'fieldName' => lcfirst(Container::camelize($name)),
667
                    'type' => 'string',
668
                    'length' => '255',
669
                    'formType' => TextType::class,
670
                    'nullable' => $allNullable,
671
                );
672
673
                break;
674 View Code Duplication
            case 'multi_line':
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...
675
                $fields[$type][] = array(
676
                    'fieldName' => lcfirst(Container::camelize($name)),
677
                    'type' => 'text',
678
                    'formType' => TextareaType::class,
679
                    'nullable' => $allNullable,
680
                );
681
682
                break;
683 View Code Duplication
            case 'wysiwyg':
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...
684
                $fields[$type][] = array(
685
                    'fieldName' => lcfirst(Container::camelize($name)),
686
                    'type' => 'text',
687
                    'formType' => WysiwygType::class,
688
                    'nullable' => $allNullable,
689
                );
690
691
                break;
692
            case 'link':
693
                foreach (array('url', 'text') as $subField) {
694
                    $fields[$type][$subField] = array(
695
                        'fieldName' => lcfirst(Container::camelize($name.'_'.$subField)),
696
                        'type' => 'string',
697
                        'formType' => $subField == 'url' ? URLChooserType::class : TextType::class,
698
                        'nullable' => $allNullable,
699
                    );
700
                }
701
                $fields[$type]['new_window'] = array(
702
                    'fieldName' => lcfirst(Container::camelize($name.'_new_window')),
703
                    'type' => 'boolean',
704
                    'nullable' => true,
705
                    'formType' => CheckboxType::class,
706
                );
707
708
                break;
709
            case 'image':
710
                $fields[$type]['image'] = array(
711
                    'fieldName' => lcfirst(Container::camelize($name)),
712
                    'type' => 'image',
713
                    'formType' => MediaType::class,
714
                    'mediaType' => $extra,
715
                    'minHeight' => $minHeight,
716
                    'maxHeight' => $maxHeight,
717
                    'minWidth' => $minWidth,
718
                    'maxWidth' => $maxWidth,
719
                    'mimeTypes' => $mimeTypes,
720
                    'targetEntity' => 'Kunstmaan\MediaBundle\Entity\Media',
721
                    'joinColumn' => array(
722
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
723
                        'referencedColumnName' => 'id',
724
                    ),
725
                    'nullable' => $allNullable,
726
                );
727
                $fields[$type]['alt_text'] = array(
728
                    'fieldName' => lcfirst(Container::camelize($name.'_alt_text')),
729
                    'type' => 'text',
730
                    'nullable' => true,
731
                    'formType' => TextType::class,
732
                );
733
734
                break;
735
            case 'media':
736
                $fields[$type][] = array(
737
                    'fieldName' => lcfirst(Container::camelize($name)),
738
                    'type' => 'media',
739
                    'formType' => MediaType::class,
740
                    'mediaType' => $extra,
741
                    'mimeTypes' => $mimeTypes,
742
                    'targetEntity' => 'Kunstmaan\MediaBundle\Entity\Media',
743
                    'joinColumn' => array(
744
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
745
                        'referencedColumnName' => 'id',
746
                    ),
747
                    'nullable' => $allNullable,
748
                );
749
750
                break;
751
            case 'single_ref':
752
                $em = $this->getContainer()->get('doctrine')->getManager();
753
                $entityName = $em->getClassMetadata($extra)->getName();
754
                $fields[$type][] = array(
755
                    'fieldName' => lcfirst(Container::camelize($name)),
756
                    'type' => 'entity',
757
                    'formType' => EntityType::class,
758
                    'targetEntity' => $entityName,
759
                    'joinColumn' => array(
760
                        'name' => str_replace('.', '_', Container::underscore($name.'_id')),
761
                        'referencedColumnName' => 'id',
762
                    ),
763
                    'nullable' => $allNullable,
764
                );
765
766
                break;
767
            case 'multi_ref':
768
                $em = $this->getContainer()->get('doctrine')->getManager();
769
                $entityName = $em->getClassMetadata($extra)->getName();
770
                $parts = explode('\\', $entityName);
771
                $joinTableName = strtolower(
772
                    $prefix.Container::underscore($objectName).'_'.Container::underscore(
773
                        $parts[count($parts) - 1]
774
                    )
775
                );
776
                $fields[$type][] = array(
777
                    'fieldName' => lcfirst(Container::camelize($name)),
778
                    'type' => 'entity',
779
                    'formType' => EntityType::class,
780
                    'targetEntity' => $entityName,
781
                    'joinTable' => array(
782
                        'name' => $joinTableName,
783
                        'joinColumns' => array(
784
                            array(
785
                                'name' => strtolower(Container::underscore($objectName)).'_id',
786
                                'referencedColumnName' => 'id',
787
                            ),
788
                        ),
789
                        'inverseJoinColumns' => array(
790
                            array(
791
                                'name' => strtolower(
792
                                        Container::underscore($parts[count($parts) - 1])
793
                                    ).'_id',
794
                                'referencedColumnName' => 'id',
795
                                'unique' => true,
796
                            ),
797
                        ),
798
                    ),
799
                    'nullable' => $allNullable,
800
                );
801
802
                break;
803 View Code Duplication
            case 'boolean':
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...
804
                $fields[$type][] = array(
805
                    'fieldName' => lcfirst(Container::camelize($name)),
806
                    'type' => 'boolean',
807
                    'formType' => CheckboxType::class,
808
                    'nullable' => $allNullable,
809
                );
810
811
                break;
812 View Code Duplication
            case 'integer':
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...
813
                $fields[$type][] = array(
814
                    'fieldName' => lcfirst(Container::camelize($name)),
815
                    'type' => 'integer',
816
                    'formType' => IntegerType::class,
817
                    'nullable' => $allNullable,
818
                );
819
820
                break;
821 View Code Duplication
            case 'decimal':
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...
822
                $fields[$type][] = array(
823
                    'fieldName' => lcfirst(Container::camelize($name)),
824
                    'type' => 'decimal',
825
                    'precision' => 10,
826
                    'scale' => 2,
827
                    'formType' => NumberType::class,
828
                    'nullable' => $allNullable,
829
                );
830
831
                break;
832 View Code Duplication
            case 'datetime':
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...
833
                $fields[$type][] = array(
834
                    'fieldName' => lcfirst(Container::camelize($name)),
835
                    'type' => 'datetime',
836
                    'formType' => DateTimeType::class,
837
                    'nullable' => $allNullable,
838
                );
839
840
                break;
841
        }
842
843
        return $fields;
844
    }
845
846
    /**
847
     * Get an array with the available page templates.
848
     *
849
     * @param BundleInterface $bundle The bundle for which we want to get the template configurations
850
     *
851
     * @return array
852
     */
853
    protected function getAvailableTemplates(BundleInterface $bundle)
854
    {
855
        $configs = array();
856
        $counter = 1;
857
858
        // Get the available sections from disc
859
        $dir = $bundle->getPath().'/Resources/config/pagetemplates/';
860
        if (file_exists($dir) && is_dir($dir)) {
861
            $finder = new Finder();
862
            $finder->files()->in($dir)->depth('== 0');
863
            foreach ($finder as $file) {
864
                $info = $this->getTemplateInfo($dir, $file->getFileName());
865
                if (is_array($info)) {
866
                    $configs[$counter++] = $info;
867
                }
868
            }
869
        }
870
871
        return $configs;
872
    }
873
874
    /**
875
     * Get the information about a pagepart section configuration file.
876
     *
877
     * @param string $dir
878
     * @param string $file
879
     *
880
     * @return array|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
881
     */
882
    protected function getTemplateInfo($dir, $file)
883
    {
884
        $info = null;
885
886
        try {
887
            $data = Yaml::parse(file_get_contents($dir.$file));
888
            // Parse contexts
889
            $contexts = array();
890
            foreach ($data['rows'] as $row) {
891
                foreach ($row['regions'] as $region) {
892
                    $contexts[] = $region['name'];
893
                }
894
            }
895
            $info = array(
896
                'name' => $data['name'],
897
                'contexts' => $contexts,
898
                'file' => $file,
899
            );
900
        } catch (ParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
901
        }
902
903
        return $info;
904
    }
905
906
    /**
907
     * Get an array with the available page templates.
908
     *
909
     * @param BundleInterface $bundle The bundle for which we want to get the template configurations
910
     *
911
     * @return array
912
     */
913
    protected function getAvailablePages(BundleInterface $bundle)
914
    {
915
        $pages = array();
916
        $counter = 1;
917
918
        // Get the available pages from disc
919
        $dir = $bundle->getPath().'/Entity/Pages/';
920
        if (file_exists($dir) && is_dir($dir)) {
921
            $finder = new Finder();
922
            $finder->files()->in($dir)->depth('== 0');
923
            foreach ($finder as $file) {
924
                $pages[$counter++] = array(
925
                    'name' => substr($file->getFileName(), 0, strlen($file->getFileName()) - 4),
926
                    'path' => $file->getPathName(),
927
                );
928
            }
929
        }
930
931
        return $pages;
932
    }
933
934
    /**
935
     * Check that it is possible to generate the behat tests.
936
     *
937
     * @param BundleInterface $bundle
938
     *
939
     * @return bool
940
     */
941
    protected function canGenerateBehatTests(BundleInterface $bundle)
942
    {
943
        $behatFile = dirname($this->getContainer()->getParameter('kernel.root_dir').'/').'/behat.yml';
944
        $pagePartContext = $bundle->getPath().'/Features/Context/PagePartContext.php';
945
        $behatTestPage = $bundle->getPath().'/Entity/Pages/BehatTestPage.php';
946
947
        // Make sure behat is configured and the PagePartContext and BehatTestPage exits
948
        return file_exists($behatFile) && file_exists($pagePartContext) && file_exists($behatTestPage);
949
    }
950
}
951