Completed
Pull Request — 5.6 (#2830)
by Jeroen
14:14
created

GeneratorBundle/Generator/KunstmaanGenerator.php (1 issue)

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\Generator;
4
5
use Doctrine\Common\Inflector\Inflector;
6
use Doctrine\ORM\Mapping\ClassMetadataInfo;
7
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
8
use Doctrine\ORM\Tools\EntityGenerator;
9
use Doctrine\ORM\Tools\EntityRepositoryGenerator;
10
use Kunstmaan\GeneratorBundle\Helper\CommandAssistant;
11
use Kunstmaan\GeneratorBundle\Helper\GeneratorUtils;
12
use Sensio\Bundle\GeneratorBundle\Generator\Generator;
13
use Symfony\Bridge\Doctrine\RegistryInterface;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15
use Symfony\Component\Filesystem\Filesystem;
16
use Symfony\Component\Finder\Finder;
17
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
18
use Symfony\Component\HttpKernel\Kernel;
19
use Twig\Environment;
20
use Twig\Lexer;
21
use Twig\Loader\FilesystemLoader;
22
23
/**
24
 * Class that contains all common generator logic.
25
 */
26
class KunstmaanGenerator extends Generator
27
{
28
    /**
29
     * @var Filesystem
30
     */
31
    protected $filesystem;
32
33
    /**
34
     * @var RegistryInterface
35
     */
36
    protected $registry;
37
38
    /**
39
     * @var string
40
     */
41
    protected $skeletonDir;
42
43
    /**
44
     * @var CommandAssistant
45
     */
46
    protected $assistant;
47
48
    /**
49
     * @var ContainerInterface
50
     */
51
    protected $container;
52
53
    /**
54
     * @param Filesystem         $filesystem  The filesystem
55
     * @param RegistryInterface  $registry    The registry
56
     * @param string             $skeletonDir The directory of the skeleton
57
     * @param CommandAssistant   $assistant   The command assistant
58
     * @param ContainerInterface $container   The container
59
     */
60 1
    public function __construct(
61
        Filesystem $filesystem,
62
        RegistryInterface $registry,
63
        $skeletonDir,
64
        CommandAssistant $assistant,
65
        ContainerInterface $container = null
66
    ) {
67 1
        $this->filesystem = $filesystem;
68 1
        $this->registry = $registry;
69 1
        $this->skeletonDir = GeneratorUtils::getFullSkeletonPath($skeletonDir);
70 1
        $this->assistant = $assistant;
71 1
        $this->container = $container;
72
73 1
        $this->setSkeletonDirs([$this->skeletonDir, GeneratorUtils::getFullSkeletonPath('/common')]);
74 1
    }
75
76
    /**
77
     * Check that the keyword is a reserved word for the database system.
78
     *
79
     * @param string $keyword
80
     *
81
     * @return bool
82
     */
83
    public function isReservedKeyword($keyword)
84
    {
85
        return $this->registry->getConnection()->getDatabasePlatform()->getReservedKeywordsList()->isKeyword($keyword);
86
    }
87
88
    /**
89
     * Generate the entity PHP code.
90
     *
91
     * @param string      $name
92
     * @param array       $fields
93
     * @param string      $namePrefix
94
     * @param string      $dbPrefix
95
     * @param string|null $extendClass
96
     * @param bool        $withRepository
97
     *
98
     * @return array
99
     *
100
     * @throws \RuntimeException
101
     */
102
    protected function generateEntity(
103
        BundleInterface $bundle,
104
        $name,
105
        $fields,
106
        $namePrefix,
107
        $dbPrefix,
108
        $extendClass = null,
109
        $withRepository = false
110
    ) {
111
        // configure the bundle (needed if the bundle does not contain any Entities yet)
112
        $config = $this->registry->getManager(null)->getConfiguration();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Doctrine\Persistence\ObjectManager as the method getConfiguration() does only exist in the following implementations of said interface: Doctrine\ORM\Decorator\EntityManagerDecorator, Doctrine\ORM\EntityManager.

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...
113
        $config->setEntityNamespaces(
114
            array_merge(
115
                [$bundle->getName() => $bundle->getNamespace() . '\\Entity' . ($namePrefix ? '\\' . $namePrefix : '')],
116
                $config->getEntityNamespaces()
117
            )
118
        );
119
120
        $entityClass = $this->registry->getAliasNamespace($bundle->getName()) . ($namePrefix ? '\\' . $namePrefix : '') . '\\' . $name;
121
        $entityPath = $bundle->getPath() . '/Entity/' . ($namePrefix ? $namePrefix . '/' : '') . str_replace('\\', '/', $name) . '.php';
122
        if (file_exists($entityPath)) {
123
            throw new \RuntimeException(sprintf('Entity "%s" already exists.', $entityClass));
124
        }
125
126
        $class = new ClassMetadataInfo($entityClass, new UnderscoreNamingStrategy());
127
        if ($withRepository) {
128
            if ($this->isSymfony4()) {
129
                $repositoryClass = preg_replace('/\\\\Entity\\\\/', '\\Repository\\', $entityClass, 1) . 'Repository';
130
                $class->customRepositoryClassName = $repositoryClass;
131
                $this->getSymfony4RepositoryGenerator()->writeEntityRepositoryClass($entityClass, $repositoryClass, $bundle->getPath());
132
            } else {
133
                $entityClass = preg_replace('/\\\\Entity\\\\/', '\\Repository\\', $entityClass, 1);
134
                $class->customRepositoryClassName = $entityClass . 'Repository';
135
                $path = $bundle->getPath() . str_repeat('/..', substr_count(get_class($bundle), '\\'));
136
                $this->getRepositoryGenerator()->writeEntityRepositoryClass($class->customRepositoryClassName, $path);
137
            }
138
        }
139
140
        foreach ($fields as $fieldSet) {
141
            foreach ($fieldSet as $fieldArray) {
142
                foreach ($fieldArray as $field) {
143
                    if (array_key_exists('joinColumn', $field)) {
144
                        $class->mapManyToOne($field);
145
                    } elseif (array_key_exists('joinTable', $field)) {
146
                        $class->mapManyToMany($field);
147
                    } else {
148
                        $class->mapField($field);
149
                    }
150
                }
151
            }
152
        }
153
        $class->setPrimaryTable(
154
            [
155
                'name' => strtolower($dbPrefix) . Inflector::tableize(Inflector::pluralize($name)),
156
            ]
157
        );
158
        $entityCode = $this->getEntityGenerator($extendClass)->generateEntityClass($class);
159
160
        return [$entityCode, $entityPath];
161
    }
162
163
    /**
164
     * Get a Doctrine EntityGenerator instance.
165
     *
166
     * @param string|null $classToExtend
167
     *
168
     * @return EntityGenerator
169
     */
170
    protected function getEntityGenerator($classToExtend = null)
171
    {
172
        $entityGenerator = new EntityGenerator();
173
        if (!is_null($classToExtend)) {
174
            $entityGenerator->setClassToExtend($classToExtend);
175
        }
176
        $entityGenerator->setGenerateAnnotations(true);
177
        $entityGenerator->setGenerateStubMethods(true);
178
        $entityGenerator->setRegenerateEntityIfExists(false);
179
        $entityGenerator->setUpdateEntityIfExists(true);
180
        $entityGenerator->setNumSpaces(4);
181
        $entityGenerator->setAnnotationPrefix('ORM\\');
182
183
        return $entityGenerator;
184
    }
185
186
    /**
187
     * Generate the entity admin type.
188
     *
189
     * @param        $bundle
190
     * @param        $entityName
191
     * @param        $entityPrefix
192
     * @param string $extendClass
193
     */
194
    protected function generateEntityAdminType(
195
        $bundle,
196
        $entityName,
197
        $entityPrefix,
198
        array $fields,
199
        $extendClass = '\Symfony\Component\Form\AbstractType'
200
    ) {
201
        $className = $entityName . 'AdminType';
202
        $savePath = $bundle->getPath() . '/Form/' . $entityPrefix . '/' . $className . '.php';
203
        $name = str_replace(
204
                '\\',
205
                '_',
206
                strtolower($bundle->getNamespace())
207
            ) . '_' . strtolower($entityName) . 'type';
208
209
        $params = [
210
            'className' => $className,
211
            'name' => $name,
212
            'namespace' => $bundle->getNamespace(),
213
            'entity' => '\\' . $bundle->getNamespace() . '\Entity\\' . $entityPrefix . '\\' . $entityName,
214
            'fields' => $fields,
215
            'entity_prefix' => $entityPrefix,
216
            'extend_class' => $extendClass,
217
        ];
218
        $this->renderFile('/Form/EntityAdminType.php', $savePath, $params);
219
    }
220
221
    /**
222
     * Install the default page templates.
223
     *
224
     * @param BundleInterface $bundle
225
     */
226
    protected function installDefaultPageTemplates($bundle)
227
    {
228
        // Configuration templates
229 View Code Duplication
        if ($this->isSymfony4()) {
230
            $dirPath = $this->container->getParameter('kernel.project_dir') . '/config/kunstmaancms/pagetemplates/';
231
        } else {
232
            $dirPath = sprintf('%s/Resources/config/pagetemplates/', $bundle->getPath());
233
        }
234
235
        $skeletonDir = sprintf('%s/Resources/config/pagetemplates/', GeneratorUtils::getFullSkeletonPath('/common'));
236
237
        // Only copy templates over when the folder does not exist yet...
238
        if (!$this->filesystem->exists($dirPath)) {
239
            $files = [
240
                'default-one-column.yml',
241
                'default-two-column-left.yml',
242
                'default-two-column-right.yml',
243
                'default-three-column.yml',
244
            ];
245
            foreach ($files as $file) {
246
                $this->filesystem->copy($skeletonDir . $file, $dirPath . $file, false);
247
                GeneratorUtils::replace('~~~BUNDLE~~~', $bundle->getName(), $dirPath . $file);
248
            }
249
        }
250
251
        // Twig templates
252
        $dirPath = $this->getTemplateDir($bundle) . '/Pages/Common/';
253
254
        $skeletonDir = sprintf('%s/Resources/views/Pages/Common/', GeneratorUtils::getFullSkeletonPath('/common'));
255
256
        if (!$this->filesystem->exists($dirPath)) {
257
            $files = [
258
                'one-column-pagetemplate.html.twig',
259
                'two-column-left-pagetemplate.html.twig',
260
                'two-column-right-pagetemplate.html.twig',
261
                'three-column-pagetemplate.html.twig',
262
            ];
263
            foreach ($files as $file) {
264
                $this->filesystem->copy($skeletonDir . $file, $dirPath . $file, false);
265
            }
266
            $this->filesystem->copy($skeletonDir . 'view.html.twig', $dirPath . 'view.html.twig', false);
267
        }
268
269
        $contents = file_get_contents($dirPath . 'view.html.twig');
270
271
        $twigFile = $this->isSymfony4() ?
272
            $twigFile = "{% extends 'Layout/layout.html.twig' %}\n" :
273
            $twigFile = "{% extends '" . $bundle->getName() . ":Layout:layout.html.twig' %}\n"
274
        ;
275
276
        if (strpos($contents, '{% extends ') === false) {
277
            GeneratorUtils::prepend(
278
                $twigFile,
279
                $dirPath . 'view.html.twig'
280
            );
281
        }
282
    }
283
284
    /**
285
     * Install the default pagepart configuration.
286
     *
287
     * @param BundleInterface $bundle
288
     */
289
    protected function installDefaultPagePartConfiguration($bundle)
290
    {
291
        // Pagepart configuration
292 View Code Duplication
        if ($this->isSymfony4()) {
293
            $dirPath = $this->container->getParameter('kernel.project_dir') . '/config/kunstmaancms/pageparts/';
294
        } else {
295
            $dirPath = sprintf('%s/Resources/config/pageparts/', $bundle->getPath());
296
        }
297
298
        $skeletonDir = sprintf('%s/Resources/config/pageparts/', GeneratorUtils::getFullSkeletonPath('/common'));
299
300
        // Only copy when folder does not exist yet
301
        if (!$this->filesystem->exists($dirPath)) {
302
            $files = ['footer.yml', 'main.yml', 'left-sidebar.yml', 'right-sidebar.yml'];
303
            foreach ($files as $file) {
304
                $this->filesystem->copy($skeletonDir . $file, $dirPath . $file, false);
305
            }
306
        }
307
    }
308
309
    /**
310
     * Render all files in the source directory and copy them to the target directory.
311
     *
312
     * @param string $sourceDir  The source directory where we need to look in
313
     * @param string $targetDir  The target directory where we need to copy the files too
314
     * @param array  $parameters The parameters that will be passed to the templates
315
     * @param bool   $override   Whether to override an existing file or not
316
     * @param bool   $recursive  Whether to render all files recursively or not
317
     */
318 1
    public function renderFiles($sourceDir, $targetDir, array $parameters, $override = false, $recursive = true)
319
    {
320
        // Make sure the source -and target dir contain a trailing slash
321 1
        $sourceDir = rtrim($sourceDir, '/') . '/';
322 1
        $targetDir = rtrim($targetDir, '/') . '/';
323
324 1
        $this->setSkeletonDirs([$sourceDir]);
325
326 1
        $finder = new Finder();
327 1
        $finder->files()->in($sourceDir);
328 1
        if (!$recursive) {
329
            $finder->depth('== 0');
330
        }
331
332
        // Get all files in the source directory
333 1
        foreach ($finder as $file) {
334 1
            $name = $file->getRelativePathname();
335
336
            // Check that we are allowed to overwrite the file if it already exists
337 1 View Code Duplication
            if (!is_file($targetDir . $name) || $override === true) {
338 1
                $fileParts = explode('.', $name);
339 1
                if (end($fileParts) === 'twig') {
340 1
                    $this->renderTwigFile($name, $targetDir . $name, $parameters, $sourceDir);
341
                } else {
342
                    $this->renderFile($name, $targetDir . $name, $parameters);
343
                }
344
            }
345
        }
346 1
    }
347
348
    /**
349
     * Render all files in the source directory and copy them to the target directory.
350
     *
351
     * @param string      $sourceDir      The source directory where we need to look in
352
     * @param string      $targetDir      The target directory where we need to copy the files too
353
     * @param string      $filename       The name of the file that needs to be rendered
354
     * @param array       $parameters     The parameters that will be passed to the templates
355
     * @param bool        $override       Whether to override an existing file or not
356
     * @param string|null $targetFilename The name of the target file (if null, then use $filename)
357
     */
358 1
    public function renderSingleFile($sourceDir, $targetDir, $filename, array $parameters, $override = false, $targetFilename = null)
359
    {
360
        // Make sure the source -and target dir contain a trailing slash
361 1
        $sourceDir = rtrim($sourceDir, '/') . '/';
362 1
        $targetDir = rtrim($targetDir, '/') . '/';
363 1
        if (is_null($targetFilename)) {
364 1
            $targetFilename = $filename;
365
        }
366
367 1
        $this->setSkeletonDirs([$sourceDir]);
368
369 1 View Code Duplication
        if (is_file($sourceDir . $filename)) {
370
            // Check that we are allowed the overwrite the file if it already exists
371 1
            if (!is_file($targetDir . $targetFilename) || $override === true) {
372 1
                $fileParts = explode('.', $filename);
373 1
                if (end($fileParts) === 'twig') {
374 1
                    $this->renderTwigFile($filename, $targetDir . $targetFilename, $parameters, $sourceDir);
375
                } else {
376 1
                    $this->renderFile($filename, $targetDir . $targetFilename, $parameters);
377
                }
378
            }
379
        }
380 1
    }
381
382
    /**
383
     * Render a file and make it executable.
384
     *
385
     * @param string $sourceDir  The source directory where we need to look in
386
     * @param string $targetDir  The target directory where we need to copy the files too
387
     * @param string $filename   The name of the file that needs to be rendered
388
     * @param array  $parameters The parameters that will be passed to the templates
389
     * @param bool   $override   Whether to override an existing file or not
390
     * @param int    $mode       The mode
391
     */
392
    public function renderExecutableFile($sourceDir, $targetDir, $filename, array $parameters, $override = false, $mode = 0774)
393
    {
394
        $this->renderSingleFile($sourceDir, $targetDir, $filename, $parameters, $override);
395
396
        $targetDir = rtrim($targetDir, '/') . '/';
397
        $targetFile = $targetDir . $filename;
398
        $this->filesystem->chmod($targetFile, $mode);
399
    }
400
401
    /**
402
     * Copy all files in the source directory to the target directory.
403
     *
404
     * @param string $sourceDir The source directory where we need to look in
405
     * @param string $targetDir The target directory where we need to copy the files too
406
     * @param bool   $override  Whether to override an existing file or not
407
     */
408
    public function copyFiles($sourceDir, $targetDir, $override = false)
409
    {
410
        // Make sure the source -and target dir contain a trailing slash
411
        $sourceDir = rtrim($sourceDir, '/') . '/';
412
        $targetDir = rtrim($targetDir, '/') . '/';
413
414
        $this->filesystem->mirror($sourceDir, $targetDir, null, ['override' => $override]);
415
    }
416
417
    /**
418
     * Remove a directory from the filesystem.
419
     *
420
     * @param string $targetDir
421
     */
422
    public function removeDirectory($targetDir)
423
    {
424
        // Make sure the target dir contain a trailing slash
425
        $targetDir = rtrim($targetDir, '/') . '/';
426
427
        $this->filesystem->remove($targetDir);
428
    }
429
430
    /**
431
     * Remove a file from the filesystem.
432
     *
433
     * @param string $file
434
     */
435
    public function removeFile($file)
436
    {
437
        $this->filesystem->remove($file);
438
    }
439
440
    /**
441
     * Render a twig file with custom twig tags.
442
     *
443
     * @param string $template
444
     * @param string $sourceDir
445
     *
446
     * @return string
447
     */
448 1
    public function renderTwig($template, array $parameters, $sourceDir)
449
    {
450 1
        $twig = new Environment(
451 1
            new FilesystemLoader([$sourceDir]), [
452 1
                'debug' => true,
453
                'cache' => false,
454
                'strict_variables' => true,
455
                'autoescape' => false,
456
            ]
457
        );
458
459
        // Ruby erb template syntax
460 1
        $lexer = new Lexer(
461 1
            $twig, [
462 1
                'tag_comment' => ['<%#', '%>'],
463
                'tag_block' => ['<%', '%>'],
464
                'tag_variable' => ['<%=', '%>'],
465
            ]
466
        );
467
468 1
        $twig->setLexer($lexer);
469
470 1
        return $twig->render($template, $parameters);
471
    }
472
473
    /**
474
     * Render a twig file, and save it to disk.
475
     *
476
     * @param string $template
477
     * @param string $target
478
     * @param string $sourceDir
479
     *
480
     * @return int
481
     */
482 1
    public function renderTwigFile($template, $target, array $parameters, $sourceDir)
483
    {
484 1
        if (!is_dir(dirname($target))) {
485 1
            mkdir(dirname($target), 0777, true);
486
        }
487
488 1
        return file_put_contents($target, $this->renderTwig($template, $parameters, $sourceDir));
489
    }
490
491
    /**
492
     * @return \Doctrine\ORM\Tools\EntityRepositoryGenerator
493
     */
494
    protected function getRepositoryGenerator()
495
    {
496
        return new EntityRepositoryGenerator();
497
    }
498
499
    /**
500
     * @return \Kunstmaan\GeneratorBundle\Generator\Symfony4EntityRepositoryGenerator
501
     */
502
    protected function getSymfony4RepositoryGenerator()
503
    {
504
        return new \Kunstmaan\GeneratorBundle\Generator\Symfony4EntityRepositoryGenerator();
505
    }
506
507
    /**
508
     * @internal
509
     */
510 1 View Code Duplication
    protected function getTemplateDir(BundleInterface $bundle)
511
    {
512 1
        if ($this->isSymfony4()) {
513 1
            return $this->container->getParameter('kernel.project_dir') . '/templates';
514
        }
515
516
        return $bundle->getPath() . '/Resources/views';
517
    }
518
519
    /**
520
     * @internal
521
     */
522 View Code Duplication
    protected function getAssetsDir(BundleInterface $bundle)
523
    {
524
        if ($this->isSymfony4()) {
525
            return $this->container->getParameter('kernel.project_dir') . '/assets';
526
        }
527
528
        return $bundle->getPath() . '/Resources';
529
    }
530
531
    /**
532
     * @internal
533
     */
534 1
    protected function isSymfony4()
535
    {
536 1
        return Kernel::VERSION_ID >= 40000;
537
    }
538
}
539