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
06:58
created

FieldGenerator::validateArguments()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

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