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 (#199)
by joseph
19:04
created

FieldGenerator::getTraitPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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