Completed
Push — master ( 6b0bad...19b970 )
by Kristof
23:51 queued 10:02
created

KunstmaanGenerator   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 510
Duplicated Lines 6.86 %

Coupling/Cohesion

Components 2
Dependencies 14

Importance

Changes 0
Metric Value
wmc 55
lcom 2
cbo 14
dl 35
loc 510
rs 6
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A isReservedKeyword() 0 4 1
A getEntityGenerator() 0 15 2
A generateEntityAdminType() 0 26 1
B installDefaultPageTemplates() 0 45 6
A installDefaultPagePartConfiguration() 0 14 3
B renderFiles() 8 29 6
B renderSingleFile() 11 23 6
A renderExecutableFile() 0 8 1
A copyFiles() 0 8 1
A removeDirectory() 0 7 1
A removeFile() 0 4 1
A renderTwig() 0 24 1
A renderTwigFile() 0 8 2
A getRepositoryGenerator() 0 4 1
A getTemplateDir() 8 8 2
A getAssetsDir() 8 8 2
A isSymfony4() 0 4 1
C generateEntity() 0 54 12
A writeEntityRepositoryClass() 0 23 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like KunstmaanGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use KunstmaanGenerator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Kunstmaan\GeneratorBundle\Generator;
4
5
use Doctrine\ORM\Mapping\ClassMetadataInfo;
6
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
7
use Doctrine\ORM\Tools\EntityGenerator;
8
use Doctrine\ORM\Tools\EntityRepositoryGenerator;
9
use Kunstmaan\GeneratorBundle\Helper\CommandAssistant;
10
use Kunstmaan\GeneratorBundle\Helper\GeneratorUtils;
11
use Sensio\Bundle\GeneratorBundle\Generator\Generator;
12
use Symfony\Bridge\Doctrine\RegistryInterface;
13
use Symfony\Component\DependencyInjection\ContainerInterface;
14
use Symfony\Component\Filesystem\Filesystem;
15
use Symfony\Component\Finder\Finder;
16
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
17
use Symfony\Component\HttpKernel\Kernel;
18
19
/**
20
 * Class that contains all common generator logic.
21
 */
22
class KunstmaanGenerator extends Generator
23
{
24
    /**
25
     * @var Filesystem
26
     */
27
    protected $filesystem;
28
29
    /**
30
     * @var RegistryInterface
31
     */
32
    protected $registry;
33
34
    /**
35
     * @var string
36
     */
37
    protected $skeletonDir;
38
39
    /**
40
     * @var CommandAssistant
41
     */
42
    protected $assistant;
43
44
    /**
45
     * @var ContainerInterface
46
     */
47
    protected $container;
48
49
    /**
50
     * @param Filesystem         $filesystem  The filesystem
51
     * @param RegistryInterface  $registry    The registry
52
     * @param string             $skeletonDir The directory of the skeleton
53
     * @param CommandAssistant   $assistant   The command assistant
54
     * @param ContainerInterface $container   The container
0 ignored issues
show
Documentation introduced by
Should the type for parameter $container not be null|ContainerInterface?

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