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 (#154)
by joseph
34:48
created

FieldGenerator   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 388
Duplicated Lines 0 %

Test Coverage

Coverage 88.11%

Importance

Changes 0
Metric Value
eloc 168
dl 0
loc 388
ccs 126
cts 143
cp 0.8811
rs 10
c 0
b 0
f 0
wmc 29

11 Methods

Rating   Name   Duplication   Size   Complexity  
A generateField() 0 21 2
A __construct() 0 22 1
A getPhpTypeForDbalType() 0 18 3
A getTraitPath() 0 3 1
B validateArguments() 0 36 8
A getInterfacePath() 0 3 1
A createDbalField() 0 21 1
A traitFqnLooksLikeField() 0 19 4
A createFieldFromArchetype() 0 17 1
A setupClassProperties() 0 59 5
A assertFileDoesNotExist() 0 4 2
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\Field;
4
5
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\CodeHelper;
6
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\FileCreationTransaction;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\FindAndReplaceHelper;
9
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
10
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\PathHelper;
11
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\ReflectionHelper;
12
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\TypeHelper;
13
use EdmondsCommerce\DoctrineStaticMeta\Config;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Boolean\DefaultsDisabledFieldTrait;
15
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Boolean\DefaultsEnabledFieldTrait;
16
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Boolean\DefaultsNullFieldTrait;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\DateTime\DateTimeSettableNoDefaultFieldTrait;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\DateTime\DateTimeSettableOnceFieldTrait;
19
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\BusinessIdentifierCodeFieldTrait;
20
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\CountryCodeFieldTrait;
21
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\EmailAddressFieldTrait;
22
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\EnumFieldTrait;
23
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\IpAddressFieldTrait;
24
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\IsbnFieldTrait;
25
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\LocaleIdentifierFieldTrait;
26
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\NullableStringFieldTrait;
27
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\SettableUuidFieldTrait;
28
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\ShortIndexedRequiredStringFieldTrait;
29
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\UnicodeLanguageIdentifierFieldTrait;
30
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\UniqueStringFieldTrait;
31
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\UrlFieldTrait;
32
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\TimeStamp\CreationTimestampFieldTrait;
33
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
34
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
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
    public const FIELD_TRAIT_SUFFIX = 'FieldTrait';
46
    public const STANDARD_FIELDS    = [
47
        BusinessIdentifierCodeFieldTrait::class,
48
        CountryCodeFieldTrait::class,
49
        CreationTimestampFieldTrait::class,
50
        DateTimeSettableNoDefaultFieldTrait::class,
51
        DateTimeSettableOnceFieldTrait::class,
52
        DefaultsDisabledFieldTrait::class,
53
        DefaultsEnabledFieldTrait::class,
54
        DefaultsNullFieldTrait::class,
55
        EmailAddressFieldTrait::class,
56
        EnumFieldTrait::class,
57
        IpAddressFieldTrait::class,
58
        IsbnFieldTrait::class,
59
        LocaleIdentifierFieldTrait::class,
60
        NullableStringFieldTrait::class,
61
        SettableUuidFieldTrait::class,
62
        ShortIndexedRequiredStringFieldTrait::class,
63
        UnicodeLanguageIdentifierFieldTrait::class,
64
        UniqueStringFieldTrait::class,
65
        UrlFieldTrait::class,
66
    ];
67
    /**
68
     * @var string
69
     */
70
    protected $fieldsPath;
71
    /**
72
     * @var string
73
     */
74
    protected $fieldsInterfacePath;
75
    /**
76
     * @var string
77
     */
78
    protected $phpType;
79
    /**
80
     * @var string
81
     */
82
    protected $fieldType;
83
    /**
84
     * Are we currently generating an archetype based field?
85
     *
86
     * @var bool
87
     */
88
    protected $isArchetype = false;
89
    /**
90
     * @var bool
91
     */
92
    protected $isNullable;
93
    /**
94
     * @var bool
95
     */
96
    protected $isUnique;
97
    /**
98
     * @var mixed
99
     */
100
    protected $defaultValue;
101
    /**
102
     * @var string
103
     */
104
    protected $traitNamespace;
105
    /**
106
     * @var string
107
     */
108
    protected $interfaceNamespace;
109
    /**
110
     * @var TypeHelper
111
     */
112
    protected $typeHelper;
113
    /**
114
     * @var ArchetypeFieldGenerator
115
     */
116
    protected $archetypeFieldCopier;
117
    /**
118
     * @var string
119
     */
120
    protected $fieldFqn;
121
    /**
122
     * @var string
123
     */
124
    protected $className;
125
    /**
126
     * @var ReflectionHelper
127
     */
128
    private $reflectionHelper;
129
130
131 54
    public function __construct(
132
        Filesystem $filesystem,
133
        FileCreationTransaction $fileCreationTransaction,
134
        NamespaceHelper $namespaceHelper,
135
        Config $config,
136
        CodeHelper $codeHelper,
137
        PathHelper $pathHelper,
138
        FindAndReplaceHelper $findAndReplaceHelper,
139
        TypeHelper $typeHelper,
140
        ReflectionHelper $reflectionHelper
141
    ) {
142 54
        parent::__construct(
143 54
            $filesystem,
144 54
            $fileCreationTransaction,
145 54
            $namespaceHelper,
146 54
            $config,
147 54
            $codeHelper,
148 54
            $pathHelper,
149 54
            $findAndReplaceHelper
150
        );
151 54
        $this->typeHelper       = $typeHelper;
152 54
        $this->reflectionHelper = $reflectionHelper;
153 54
    }
154
155
156
    /**
157
     * Generate a new Field based on a property name and Doctrine Type or Archetype field FQN
158
     *
159
     * @see MappingHelper::ALL_DBAL_TYPES for the full list of Dbal Types
160
     *
161
     * @param string      $fieldFqn
162
     * @param string      $fieldType
163
     * @param null|string $phpType
164
     *
165
     * @param mixed       $defaultValue
166
     * @param bool        $isUnique
167
     *
168
     * @return string - The Fully Qualified Name of the generated Field Trait
169
     *
170
     * @throws DoctrineStaticMetaException
171
     * @throws \ReflectionException
172
     * @SuppressWarnings(PHPMD.StaticAccess)
173
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
174
     */
175 54
    public function generateField(
176
        string $fieldFqn,
177
        string $fieldType,
178
        ?string $phpType = null,
179
        $defaultValue = null,
180
        bool $isUnique = false
181
    ): string {
182 54
        $this->validateArguments($fieldFqn, $fieldType, $phpType);
183 39
        $this->setupClassProperties($fieldFqn, $fieldType, $phpType, $defaultValue, $isUnique);
184
185 39
        $this->pathHelper->ensurePathExists($this->fieldsPath);
186 39
        $this->pathHelper->ensurePathExists($this->fieldsInterfacePath);
187
188 39
        $this->assertFileDoesNotExist($this->getTraitPath(), 'Trait');
189 39
        $this->assertFileDoesNotExist($this->getInterfacePath(), 'Interface');
190
191 39
        if (true === $this->isArchetype) {
192 21
            return $this->createFieldFromArchetype();
193
        }
194
195 24
        return $this->createDbalField();
196
    }
197
198 54
    protected function validateArguments(
199
        string $fieldFqn,
200
        string $fieldType,
201
        ?string $phpType
202
    ): void {
203
        //Check for a correct looking field FQN
204 54
        if (false === \ts\stringContains($fieldFqn, AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE)) {
205 3
            throw new \InvalidArgumentException(
206 3
                'Fully qualified name [ ' . $fieldFqn . ' ]'
207 3
                . ' does not include [ ' . AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE . ' ].' . "\n"
208 3
                . 'Please ensure you pass in the full namespace qualified field name'
209
            );
210
        }
211 51
        $fieldShortName = $this->namespaceHelper->getClassShortName($fieldFqn);
212 51
        if (preg_match('%^(get|set|is|has)%i', $fieldShortName, $matches)) {
213 3
            throw new \InvalidArgumentException(
214 3
                'Your field short name ' . $fieldShortName
215 3
                . ' begins with the forbidden string "' . $matches[1] .
216 3
                '", please do not use accessor prefixes in your field name'
217
            );
218
        }
219
        //Check that the field type is either a Dbal Type or a Field Archetype FQN
220 48
        if (false === \in_array($fieldType, self::STANDARD_FIELDS, true)
221 48
            && false === \in_array(\strtolower($fieldType), MappingHelper::ALL_DBAL_TYPES, true)
222 48
            && false === $this->traitFqnLooksLikeField($fieldType)
0 ignored issues
show
introduced by
The condition false === $this->traitFq...ksLikeField($fieldType) is always false.
Loading history...
223
        ) {
224
            throw new \InvalidArgumentException(
225
                'fieldType ' . $fieldType . ' is not a valid field type'
226
            );
227
        }
228
        //Check the phpType is valid
229 45
        if ((null !== $phpType)
230 45
            && (false === \in_array($phpType, MappingHelper::PHP_TYPES, true))
231
        ) {
232 6
            throw new \InvalidArgumentException(
233 6
                'phpType must be either null or one of MappingHelper::PHP_TYPES'
234
            );
235
        }
236 39
    }
237
238
    /**
239
     * Does the specified trait FQN look like a field trait?
240
     *
241
     * @param string $traitFqn
242
     *
243
     * @return bool
244
     * @throws \ReflectionException
245
     */
246 6
    protected function traitFqnLooksLikeField(string $traitFqn): bool
247
    {
248
        try {
249 6
            $reflection = new \ts\Reflection\ReflectionClass($traitFqn);
250 3
        } catch (\ReflectionException $e) {
251 3
            throw new \InvalidArgumentException(
252 3
                'invalid traitFqn ' . $traitFqn . ' does not seem to exist',
253 3
                $e->getCode(),
254 3
                $e
255
            );
256
        }
257 3
        if (true !== $reflection->isTrait()) {
258
            throw new \InvalidArgumentException('field type is not a trait FQN');
259
        }
260 3
        if ('FieldTrait' !== \substr($traitFqn, -\strlen('FieldTrait'))) {
261
            throw new \InvalidArgumentException('traitFqn does not end in FieldTrait');
262
        }
263
264 3
        return true;
265
    }
266
267
    /**
268
     * Defining the properties for the field to be generated
269
     *
270
     * @param string      $fieldFqn
271
     * @param string      $fieldType
272
     * @param null|string $phpType
273
     * @param mixed       $defaultValue
274
     * @param bool        $isUnique
275
     *
276
     * @throws DoctrineStaticMetaException
277
     * @SuppressWarnings(PHPMD.StaticAccess)
278
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
279
     */
280 39
    protected function setupClassProperties(
281
        string $fieldFqn,
282
        string $fieldType,
283
        ?string $phpType,
284
        $defaultValue,
285
        bool $isUnique
286
    ): void {
287 39
        $this->isArchetype = false;
288 39
        $this->fieldType   = \strtolower($fieldType);
289 39
        if (true !== \in_array($this->fieldType, MappingHelper::COMMON_TYPES, true)) {
290 21
            $this->isArchetype = true;
291 21
            $this->fieldType   = $fieldType;
292
        }
293 39
        $this->phpType      = $phpType ?? $this->getPhpTypeForDbalType();
294 39
        $this->defaultValue = $this->typeHelper->normaliseValueToType($defaultValue, $this->phpType);
295
296 39
        if (null !== $this->defaultValue) {
297 3
            $defaultValueType = $this->typeHelper->getType($this->defaultValue);
298 3
            if ($defaultValueType !== $this->phpType) {
299
                throw new \InvalidArgumentException(
300
                    'default value ' .
301
                    $this->defaultValue .
302
                    ' has the type: ' .
303
                    $defaultValueType
304
                    .
305
                    ' whereas the phpType for this field has been set as ' .
306
                    $this->phpType .
307
                    ', these do not match up'
308
                );
309
            }
310
        }
311 39
        $this->isNullable = (null === $defaultValue);
312 39
        $this->isUnique   = $isUnique;
313
314 39
        if (\substr($fieldFqn, -\strlen(self::FIELD_TRAIT_SUFFIX)) === self::FIELD_TRAIT_SUFFIX) {
315 12
            $fieldFqn = \substr($fieldFqn, 0, -\strlen(self::FIELD_TRAIT_SUFFIX));
316
        }
317 39
        $this->fieldFqn = $fieldFqn;
318
319 39
        list($className, $traitNamespace, $traitSubDirectories) = $this->parseFullyQualifiedName(
320 39
            $this->fieldFqn,
321 39
            $this->srcSubFolderName
322
        );
323 39
        $this->className = $className;
324 39
        list(, $interfaceNamespace, $interfaceSubDirectories) = $this->parseFullyQualifiedName(
325 39
            \str_replace('Traits', 'Interfaces', $this->fieldFqn),
326 39
            $this->srcSubFolderName
327
        );
328
329 39
        $this->fieldsPath = $this->pathHelper->resolvePath(
330 39
            $this->pathToProjectRoot . '/' . \implode('/', $traitSubDirectories)
331
        );
332
333 39
        $this->fieldsInterfacePath = $this->pathHelper->resolvePath(
334 39
            $this->pathToProjectRoot . '/' . \implode('/', $interfaceSubDirectories)
335
        );
336
337 39
        $this->traitNamespace     = $traitNamespace;
338 39
        $this->interfaceNamespace = $interfaceNamespace;
339 39
    }
340
341
    /**
342
     * @return string
343
     * @throws DoctrineStaticMetaException
344
     *
345
     */
346 39
    protected function getPhpTypeForDbalType(): string
347
    {
348 39
        if (true === $this->isArchetype) {
349 21
            return '';
350
        }
351 24
        if (!\in_array($this->fieldType, MappingHelper::COMMON_TYPES, true)) {
352
            throw new DoctrineStaticMetaException(
353
                'Field type of ' .
354
                $this->fieldType .
355
                ' is not one of MappingHelper::COMMON_TYPES'
356
                .
357
                "\n\nYou can only use this fieldType type if you pass in the explicit phpType as well "
358
                .
359
                "\n\nAlternatively, suggest you set the type as string and then edit the generated code as you see fit"
360
            );
361
        }
362
363 24
        return MappingHelper::COMMON_TYPES_TO_PHP_TYPES[$this->fieldType];
364
    }
365
366 39
    private function assertFileDoesNotExist(string $filePath, string $type): void
367
    {
368 39
        if (file_exists($filePath)) {
369
            throw new \RuntimeException("Field $type already exists at $filePath");
370
        }
371 39
    }
372
373 39
    protected function getTraitPath(): string
374
    {
375 39
        return $this->fieldsPath . '/' . $this->codeHelper->classy($this->className) . 'FieldTrait.php';
376
    }
377
378 39
    protected function getInterfacePath(): string
379
    {
380 39
        return $this->fieldsInterfacePath . '/' . $this->codeHelper->classy($this->className) . 'FieldInterface.php';
381
    }
382
383
    /**
384
     * @return string
385
     * @throws \ReflectionException
386
     */
387 21
    protected function createFieldFromArchetype(): string
388
    {
389 21
        $copier = new ArchetypeFieldGenerator(
390 21
            $this->fileSystem,
391 21
            $this->namespaceHelper,
392 21
            $this->codeHelper,
393 21
            $this->findAndReplaceHelper,
394 21
            $this->reflectionHelper
395
        );
396
397 21
        return $copier->createFromArchetype(
398 21
            $this->fieldFqn,
399 21
            $this->getTraitPath(),
400 21
            $this->getInterfacePath(),
401 21
            '\\' . $this->fieldType,
402 21
            $this->projectRootNamespace
403 21
        ) . self::FIELD_TRAIT_SUFFIX;
404
    }
405
406
    /**
407
     * @return string
408
     * @throws DoctrineStaticMetaException
409
     */
410 24
    protected function createDbalField(): string
411
    {
412 24
        $creator = new DbalFieldGenerator(
413 24
            $this->fileSystem,
414 24
            $this->codeHelper,
415 24
            $this->fileCreationTransaction,
416 24
            $this->findAndReplaceHelper,
417 24
            $this->typeHelper,
418 24
            $this->pathHelper
419
        );
420
421 24
        return $creator->create(
422 24
            $this->className,
423 24
            $this->getTraitPath(),
424 24
            $this->getInterfacePath(),
425 24
            $this->fieldType,
426 24
            $this->defaultValue,
427 24
            $this->isUnique,
428 24
            $this->phpType,
429 24
            $this->traitNamespace,
430 24
            $this->interfaceNamespace
431
        );
432
    }
433
}
434