GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#51)
by joseph
102:38 queued 99:24
created

FieldGenerator::ensurePathExists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
crap 2
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator;
4
5
use Doctrine\Common\Inflector\Inflector;
6
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
9
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\TypeHelper;
10
use EdmondsCommerce\DoctrineStaticMeta\Config;
11
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\IpAddressFieldTrait;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\LabelFieldTrait;
13
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\NameFieldTrait;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\QtyFieldTrait;
15
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\ActionedDateFieldTrait;
16
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\ActivatedDateFieldTrait;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\CompletedDateFieldTrait;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\DeactivatedDateFieldTrait;
19
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\TimestampFieldTrait;
20
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Flag\ApprovedFieldTrait;
21
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Flag\DefaultFieldTrait;
22
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Person\EmailFieldTrait;
23
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Person\YearOfBirthFieldTrait;
24
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\UsesPHPMetaDataInterface;
25
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
26
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
27
use gossi\codegen\model\PhpClass;
28
use gossi\codegen\model\PhpInterface;
29
use gossi\codegen\model\PhpMethod;
30
use gossi\codegen\model\PhpParameter;
31
use gossi\codegen\model\PhpTrait;
32
use gossi\docblock\Docblock;
33
use gossi\docblock\tags\UnknownTag;
34
use Symfony\Component\Filesystem\Filesystem;
35
36
/**
37
 * Class FieldGenerator
38
 *
39
 * @package EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator
40
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
41
 */
42
class FieldGenerator extends AbstractGenerator
43
{
44
    /**
45
     * @var string
46
     */
47
    protected $fieldsPath;
48
49
    /**
50
     * @var string
51
     */
52
    protected $fieldsInterfacePath;
53
54
    /**
55
     * @var string
56
     * CamelCase Version of Field Name
57
     */
58
    protected $classy;
59
60
    /**
61
     * @var string
62
     * UPPER_SNAKE_CASE version of Field Name
63
     */
64
    protected $consty;
65
66
    /**
67
     * @var string
68
     */
69
    protected $phpType;
70
71
    /**
72
     * @var string
73
     */
74
    protected $dbalType;
75
76
    /**
77
     * @var bool
78
     */
79
    protected $isNullable;
80
81
    /**
82
     * @var bool
83
     */
84
    protected $isUnique;
85
86
    /**
87
     * @var mixed
88
     */
89
    protected $defaultValue;
90
91
    /**
92
     * @var string
93
     */
94
    protected $traitNamespace;
95
96
    /**
97
     * @var string
98
     */
99
    protected $interfaceNamespace;
100
101
    public const STANDARD_FIELDS = [
102
        // Attribute
103
        IpAddressFieldTrait::class,
104
        LabelFieldTrait::class,
105
        NameFieldTrait::class,
106
        QtyFieldTrait::class,
107
        // Date
108
        ActionedDateFieldTrait::class,
109
        ActivatedDateFieldTrait::class,
110
        CompletedDateFieldTrait::class,
111
        DeactivatedDateFieldTrait::class,
112
        TimestampFieldTrait::class,
113
        // Flag
114
        ApprovedFieldTrait::class,
115
        DefaultFieldTrait::class,
116
        // Person
117
        EmailFieldTrait::class,
118
        YearOfBirthFieldTrait::class,
119
    ];
120
    /**
121
     * @var TypeHelper
122
     */
123
    protected $typeHelper;
124
125 27
    public function __construct(
126
        Filesystem $filesystem,
127
        FileCreationTransaction $fileCreationTransaction,
128
        NamespaceHelper $namespaceHelper,
129
        Config $config,
130
        CodeHelper $codeHelper,
131
        TypeHelper $typeHelper
132
    ) {
133 27
        parent::__construct($filesystem, $fileCreationTransaction, $namespaceHelper, $config, $codeHelper);
134 27
        $this->typeHelper = $typeHelper;
135 27
    }
136
137
138
    /**
139
     * @param string $fieldFqn
140
     * @param string $entityFqn
141
     *
142
     * @throws DoctrineStaticMetaException
143
     * @SuppressWarnings(PHPMD.StaticAccess)
144
     */
145 19
    public function setEntityHasField(string $entityFqn, string $fieldFqn): void
146
    {
147
        try {
148 19
            $entityReflection         = new \ReflectionClass($entityFqn);
149 19
            $entity                   = PhpClass::fromFile($entityReflection->getFileName());
150 19
            $fieldReflection          = new \ReflectionClass($fieldFqn);
151 19
            $field                    = PhpTrait::fromFile($fieldReflection->getFileName());
152 19
            $fieldInterfaceFqn        = \str_replace(
153 19
                ['Traits', 'Trait'],
154 19
                ['Interfaces', 'Interface'],
155 19
                $fieldFqn
156
            );
157 19
            $fieldInterfaceReflection = new \ReflectionClass($fieldInterfaceFqn);
158 19
            $fieldInterface           = PhpInterface::fromFile($fieldInterfaceReflection->getFileName());
159
        } catch (\Exception $e) {
160
            throw new DoctrineStaticMetaException(
161
                'Failed loading the entity or field from FQN: '.$e->getMessage(),
162
                $e->getCode(),
163
                $e
164
            );
165
        }
166 19
        $entity->addTrait($field);
167 19
        $entity->addInterface($fieldInterface);
168 19
        $this->codeHelper->generate($entity, $entityReflection->getFileName());
169 19
    }
170
171
172
    /**
173
     * Generate a new Field based on a property name and Doctrine Type
174
     *
175
     * @see MappingHelper::ALL_DBAL_TYPES for the full list of Dbal Types
176
     *
177
     * @param string      $fieldFqn
178
     * @param string      $dbalType
179
     * @param null|string $phpType
180
     *
181
     * @param mixed       $defaultValue
182
     * @param bool        $isUnique
183
     *
184
     * @return string - The Fully Qualified Name of the generated Field Trait
185
     *
186
     * @throws DoctrineStaticMetaException
187
     * @SuppressWarnings(PHPMD.StaticAccess)
188
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
189
     */
190 11
    public function generateField(
191
        string $fieldFqn,
192
        string $dbalType,
193
        ?string $phpType = null,
194
        $defaultValue = null,
195
        bool $isUnique = false
196
    ): string {
197 11
        $this->validateArguments($fieldFqn, $dbalType, $phpType);
198 7
        $this->setupClassProperties($fieldFqn, $dbalType, $phpType, $defaultValue, $isUnique);
199
200 7
        $this->ensurePathExists($this->fieldsPath);
201 7
        $this->ensurePathExists($this->fieldsInterfacePath);
202
203 7
        $this->generateInterface();
204
205 7
        return $this->generateTrait();
206
    }
207
208 11
    protected function validateArguments(
209
        string $fieldFqn,
210
        string $dbalType,
211
        ?string $phpType
212
    ): void {
213 11
        if (false === strpos($fieldFqn, AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE)) {
214 1
            throw new \InvalidArgumentException(
215 1
                'Fully qualified name [ '.$fieldFqn.' ]'
216 1
                .' does not include [ '.AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE.' ].'."\n"
217 1
                .'Please ensure you pass in the full namespace qualified field name'
218
            );
219
        }
220 10
        if (false === \in_array($dbalType, MappingHelper::ALL_DBAL_TYPES, true)) {
221 1
            throw new \InvalidArgumentException(
222 1
                'dbalType must be either null or one of MappingHelper::ALL_DBAL_TYPES'
223
            );
224
        }
225 9
        if ((null !== $phpType)
226 9
            && (false === \in_array($phpType, MappingHelper::PHP_TYPES, true))
227
        ) {
228 2
            throw new \InvalidArgumentException(
229 2
                'phpType must be either null or one of MappingHelper::PHP_TYPES'
230
            );
231
        }
232 7
    }
233
234
    /**
235
     * Defining the properties for the field to be generated
236
     *
237
     * @param string      $fieldFqn
238
     * @param string      $dbalType
239
     * @param null|string $phpType
240
     * @param mixed       $defaultValue
241
     * @param bool        $isUnique
242
     *
243
     * @throws DoctrineStaticMetaException
244
     * @SuppressWarnings(PHPMD.StaticAccess)
245
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
246
     */
247 7
    protected function setupClassProperties(
248
        string $fieldFqn,
249
        string $dbalType,
250
        ?string $phpType,
251
        $defaultValue,
252
        bool $isUnique
253
    ) {
254 7
        $this->dbalType     = $dbalType;
255 7
        $this->phpType      = $phpType ?? $this->getPhpTypeForDbalType();
256 7
        $this->defaultValue = $this->typeHelper->normaliseValueToType($defaultValue, $this->phpType);
257
258 7
        if (null !== $this->defaultValue) {
259 1
            $defaultValueType = $this->typeHelper->getType($this->defaultValue);
260 1
            if ($defaultValueType !== $this->phpType) {
261
                throw new \InvalidArgumentException(
262
                    'default value '.$this->defaultValue.' has the type: '.$defaultValueType
263
                    .' whereas the phpType for this field has been set as '.$this->phpType.', these do not match up'
264
                );
265
            }
266
        }
267 7
        $this->isNullable = (null === $defaultValue);
268 7
        $this->isUnique   = $isUnique;
269 7
        list($className, $traitNamespace, $traitSubDirectories) = $this->parseFullyQualifiedName(
270 7
            $fieldFqn,
271 7
            $this->srcSubFolderName
272
        );
273 7
        list(, $interfaceNamespace, $interfaceSubDirectories) = $this->parseFullyQualifiedName(
274 7
            str_replace('Traits', 'Interfaces', $fieldFqn),
275 7
            $this->srcSubFolderName
276
        );
277
278 7
        $this->fieldsPath = $this->codeHelper->resolvePath(
279 7
            $this->pathToProjectRoot.'/'.implode('/', $traitSubDirectories)
280
        );
281
282 7
        $this->fieldsInterfacePath = $this->codeHelper->resolvePath(
283 7
            $this->pathToProjectRoot.'/'.implode('/', $interfaceSubDirectories)
284
        );
285
286 7
        $this->classy             = Inflector::classify($className);
287 7
        $this->consty             = strtoupper(Inflector::tableize($className));
288 7
        $this->traitNamespace     = $traitNamespace;
289 7
        $this->interfaceNamespace = $interfaceNamespace;
290 7
    }
291
292
293
    /**
294
     * @param string $path
295
     */
296 7
    protected function ensurePathExists(string $path): void
297
    {
298 7
        if ($this->fileSystem->exists($path)) {
299 5
            return;
300
        }
301 7
        $this->fileSystem->mkdir($path);
302 7
    }
303
304
305
    /**
306
     * @return string
307
     * @throws DoctrineStaticMetaException
308
     *
309
     */
310 7
    protected function getPhpTypeForDbalType(): string
311
    {
312 7
        if (!\in_array($this->dbalType, MappingHelper::COMMON_TYPES, true)) {
313
            throw new DoctrineStaticMetaException(
314
                'Field type of '.$this->dbalType.' is not one of MappingHelper::COMMON_TYPES'
315
                ."\n\nYou can only use this dbal type if you pass in the explicit phpType as well "
316
                ."\n\nAlternatively, suggest you set the type as string and then edit the generated code as you see fit"
317
            );
318
        }
319
320 7
        return MappingHelper::COMMON_TYPES_TO_PHP_TYPES[$this->dbalType];
321
    }
322
323
    /**
324
     * @throws DoctrineStaticMetaException
325
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
326
     */
327 7
    protected function generateInterface(): void
328
    {
329 7
        $filePath = $this->fieldsInterfacePath.'/'.$this->classy.'FieldInterface.php';
330 7
        $this->assertFileDoesNotExist($filePath, 'Interface');
331
        try {
332 7
            $this->fileSystem->copy(
333 7
                $this->codeHelper->resolvePath(static::FIELD_INTERFACE_TEMPLATE_PATH),
334 7
                $filePath
335
            );
336 7
            $this->interfacePostCopy($filePath);
337 7
            $this->codeHelper->replaceTypeHintsInFile(
338 7
                $filePath,
339 7
                $this->phpType,
340 7
                $this->dbalType,
341 7
                $this->isNullable
342
            );
343
        } catch (\Exception $e) {
344
            throw new DoctrineStaticMetaException(
345
                'Error in '.__METHOD__.': '.$e->getMessage(),
346
                $e->getCode(),
347
                $e
348
            );
349
        }
350 7
    }
351
352
    /**
353
     * @param string $filePath
354
     *
355
     * @throws \RuntimeException
356
     * @throws DoctrineStaticMetaException
357
     */
358 7
    protected function postCopy(string $filePath): void
359
    {
360 7
        $this->fileCreationTransaction::setPathCreated($filePath);
361 7
        $this->replaceName(
362 7
            $this->classy,
363 7
            $filePath,
364 7
            static::FIND_ENTITY_FIELD_NAME
365
        );
366 7
        $this->findReplace('TEMPLATE_FIELD_NAME', $this->consty, $filePath);
367 7
        $this->codeHelper->tidyNamespacesInFile($filePath);
368 7
        $this->setGetterToIsForBools($filePath);
369 7
    }
370
371
    /**
372
     * @param string $filePath
373
     *
374
     * @throws \RuntimeException
375
     * @throws DoctrineStaticMetaException
376
     */
377 7
    protected function traitPostCopy(string $filePath): void
378
    {
379 7
        $this->replaceFieldTraitNamespace($this->traitNamespace, $filePath);
380 7
        $this->replaceFieldInterfaceNamespace($this->interfaceNamespace, $filePath);
381 7
        $this->postCopy($filePath);
382 7
    }
383
384 7
    protected function setGetterToIsForBools(string $filePath): void
385
    {
386 7
        if ($this->phpType !== 'bool') {
387 7
            return;
388
        }
389 4
        $replaceName = $this->codeHelper->getGetterMethodNameForBoolean($this->classy);
390 4
        $findName    = 'get'.$this->classy;
391 4
        $this->findReplace($findName, $replaceName, $filePath);
392 4
    }
393
394
    /**
395
     * @param string $filePath
396
     *
397
     * @throws DoctrineStaticMetaException
398
     */
399 7
    protected function interfacePostCopy(string $filePath): void
400
    {
401 7
        $this->replaceFieldInterfaceNamespace($this->interfaceNamespace, $filePath);
402 7
        $this->replaceDefaultValueInInterface($filePath);
403 7
        $this->postCopy($filePath);
404 7
    }
405
406
    /**
407
     * @param string $filePath
408
     */
409 7
    protected function replaceDefaultValueInInterface(string $filePath): void
410
    {
411 7
        $defaultType = $this->typeHelper->getType($this->defaultValue);
412
        switch (true) {
413 7
            case $defaultType === 'null':
414 6
                $replace = 'null';
415 6
                break;
416 1
            case $this->phpType === 'string':
417
                $replace = "'$this->defaultValue'";
418
                break;
419 1
            case $this->phpType === 'bool':
420 1
                $replace = true === $this->defaultValue ? 'true' : 'false';
421 1
                break;
422 1
            case $this->phpType === 'int':
423 1
            case $this->phpType === 'float':
424 1
                $replace = (string)$this->defaultValue;
425 1
                break;
426
            case $this->phpType === 'DateTime':
427
                if ($this->defaultValue !== MappingHelper::DATETIME_DEFAULT_CURRENT_TIME_STAMP) {
428
                    throw new \InvalidArgumentException(
429
                        'Invalid default value '.$this->defaultValue
430
                        .'We only support current timestamp as the default on DateTime'
431
                    );
432
                }
433
                $replace = "\EdmondsCommerce\DoctrineStaticMeta\MappingHelper::DATETIME_DEFAULT_CURRENT_TIME_STAMP";
434
                break;
435
            default:
436
                throw new \RuntimeException(
437
                    'failed to calculate replace based on defaultType '.$defaultType
438
                    .' and phpType '.$this->phpType.' in '.__METHOD__
439
                );
440
        }
441 7
        $this->findReplace("'defaultValue'", $replace, $filePath);
442 7
    }
443
444
    /**
445
     * @return string
446
     * @throws DoctrineStaticMetaException
447
     * @SuppressWarnings(PHPMD.StaticAccess)
448
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
449
     */
450 7
    protected function generateTrait(): string
451
    {
452 7
        $filePath = $this->fieldsPath.'/'.$this->classy.'FieldTrait.php';
453 7
        $this->assertFileDoesNotExist($filePath, 'Trait');
454
        try {
455 7
            $this->fileSystem->copy(
456 7
                $this->codeHelper->resolvePath(static::FIELD_TRAIT_TEMPLATE_PATH),
457 7
                $filePath
458
            );
459 7
            $this->fileCreationTransaction::setPathCreated($filePath);
460 7
            $this->traitPostCopy($filePath);
461 7
            $trait = PhpTrait::fromFile($filePath);
462 7
            $trait->setMethod($this->getPropertyMetaMethod());
463 7
            $trait->addUseStatement('\\'.MappingHelper::class);
464 7
            $trait->addUseStatement('\\'.ClassMetadataBuilder::class);
465 7
            $this->codeHelper->generate($trait, $filePath);
466 7
            $this->codeHelper->replaceTypeHintsInFile(
467 7
                $filePath,
468 7
                $this->phpType,
469 7
                $this->dbalType,
470 7
                $this->isNullable
471
            );
472
473 7
            return $trait->getQualifiedName();
474
        } catch (\Exception $e) {
475
            throw new DoctrineStaticMetaException(
476
                'Error in '.__METHOD__.': '.$e->getMessage(),
477
                $e->getCode(),
478
                $e
479
            );
480
        }
481
    }
482
483
    /**
484
     * @return PhpMethod
485
     * @SuppressWarnings(PHPMD.StaticAccess)
486
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
487
     */
488 7
    protected function getPropertyMetaMethod(): PhpMethod
489
    {
490 7
        $name   = UsesPHPMetaDataInterface::METHOD_PREFIX_GET_PROPERTY_DOCTRINE_META.$this->classy;
491 7
        $method = PhpMethod::create($name);
492 7
        $method->setStatic(true);
493 7
        $method->setVisibility('public');
494 7
        $method->setParameters(
495 7
            [PhpParameter::create('builder')->setType('ClassMetadataBuilder')]
496
        );
497 7
        $mappingHelperMethodName = 'setSimple'.ucfirst(strtolower($this->dbalType)).'Fields';
498
499
        $methodBody = "
500 7
        MappingHelper::$mappingHelperMethodName(
501 7
            [{$this->classy}FieldInterface::PROP_{$this->consty}],
502
            \$builder,
503 7
            {$this->classy}FieldInterface::DEFAULT_{$this->consty}
504
        );                        
505
";
506 7
        if (\in_array($this->dbalType, MappingHelper::UNIQUEABLE_TYPES, true)) {
507 7
            $isUniqueString = $this->isUnique ? 'true' : 'false';
508
            $methodBody     = "
509 7
        MappingHelper::$mappingHelperMethodName(
510 7
            [{$this->classy}FieldInterface::PROP_{$this->consty}],
511
            \$builder,
512 7
            {$this->classy}FieldInterface::DEFAULT_{$this->consty},
513 7
            $isUniqueString
514
        );                        
515
";
516
        }
517 7
        $method->setBody($methodBody);
518 7
        $method->setDocblock(
519 7
            DocBlock::create()
520 7
                    ->appendTag(
521 7
                        UnknownTag::create('SuppressWarnings(PHPMD.StaticAccess)')
522
                    )
523
        );
524
525 7
        return $method;
526
    }
527
528 7
    private function assertFileDoesNotExist(string $filePath, string $type): void
529
    {
530 7
        if (file_exists($filePath)) {
531 1
            throw new \RuntimeException("Field $type already exists at $filePath");
532
        }
533 7
    }
534
}
535