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
28:43
created

FieldGenerator::createDbalField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 21
rs 9.7
c 0
b 0
f 0
ccs 18
cts 18
cp 1
cc 1
nc 1
nop 0
crap 1
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 36
    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 36
        parent::__construct(
149 36
            $filesystem,
150 36
            $fileCreationTransaction,
151 36
            $namespaceHelper,
152 36
            $config,
153 36
            $codeHelper,
154 36
            $pathHelper,
155 36
            $findAndReplaceHelper
156
        );
157 36
        $this->typeHelper       = $typeHelper;
158 36
        $this->reflectionHelper = $reflectionHelper;
159 36
    }
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 36
    public function generateField(
182
        string $fieldFqn,
183
        string $fieldType,
184
        ?string $phpType = null,
185
        $defaultValue = null,
186
        bool $isUnique = false
187
    ): string {
188 36
        $this->validateArguments($fieldFqn, $fieldType, $phpType);
189 26
        $this->setupClassProperties($fieldFqn, $fieldType, $phpType, $defaultValue, $isUnique);
190
191 26
        $this->pathHelper->ensurePathExists($this->fieldsPath);
192 26
        $this->pathHelper->ensurePathExists($this->fieldsInterfacePath);
193
194 26
        $this->assertFileDoesNotExist($this->getTraitPath(), 'Trait');
195 26
        $this->assertFileDoesNotExist($this->getInterfacePath(), 'Interface');
196
197 26
        if (true === $this->isArchetype) {
198 14
            return $this->createFieldFromArchetype();
199
        }
200
201 16
        return $this->createDbalField();
202
    }
203
204 36
    protected function validateArguments(
205
        string $fieldFqn,
206
        string $fieldType,
207
        ?string $phpType
208
    ): void {
209
        //Check for a correct looking field FQN
210 36
        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 2
            throw new \InvalidArgumentException(
212 2
                'Fully qualified name [ ' . $fieldFqn . ' ]'
213 2
                . ' does not include [ ' . AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE . ' ].' . "\n"
214 2
                . 'Please ensure you pass in the full namespace qualified field name'
215
            );
216
        }
217 34
        $fieldShortName = $this->namespaceHelper->getClassShortName($fieldFqn);
218 34
        if (preg_match('%^(get|set|is|has)%i', $fieldShortName, $matches)) {
219 2
            throw new \InvalidArgumentException(
220 2
                'Your field short name ' . $fieldShortName
221 2
                . ' begins with the forbidden string "' . $matches[1] .
222 2
                '", 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 32
        if (false === \in_array($fieldType, self::STANDARD_FIELDS, true)
227 32
            && false === \in_array(\strtolower($fieldType), MappingHelper::ALL_DBAL_TYPES, true)
228 32
            && 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 30
        if ((null !== $phpType)
236 30
            && (false === \in_array($phpType, MappingHelper::PHP_TYPES, true))
237
        ) {
238 4
            throw new \InvalidArgumentException(
239 4
                'phpType must be either null or one of MappingHelper::PHP_TYPES'
240
            );
241
        }
242 26
    }
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 4
    protected function traitFqnLooksLikeField(string $traitFqn): bool
253
    {
254
        try {
255 4
            $reflection = new \ts\Reflection\ReflectionClass($traitFqn);
256 2
        } catch (\ReflectionException $e) {
257 2
            throw new \InvalidArgumentException(
258 2
                'invalid traitFqn ' . $traitFqn . ' does not seem to exist',
259 2
                $e->getCode(),
260 2
                $e
261
            );
262
        }
263 2
        if (true !== $reflection->isTrait()) {
264
            throw new \InvalidArgumentException('field type is not a trait FQN');
265
        }
266 2
        if ('FieldTrait' !== \substr($traitFqn, -\strlen('FieldTrait'))) {
267
            throw new \InvalidArgumentException('traitFqn does not end in FieldTrait');
268
        }
269
270 2
        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 26
    protected function setupClassProperties(
287
        string $fieldFqn,
288
        string $fieldType,
289
        ?string $phpType,
290
        $defaultValue,
291
        bool $isUnique
292
    ): void {
293 26
        $this->isArchetype = false;
294 26
        $this->fieldType   = \strtolower($fieldType);
295 26
        if (true !== \in_array($this->fieldType, MappingHelper::COMMON_TYPES, true)) {
296 14
            $this->isArchetype = true;
297 14
            $this->fieldType   = $fieldType;
298
        }
299 26
        $this->phpType      = $phpType ?? $this->getPhpTypeForDbalType();
300 26
        $this->defaultValue = $this->typeHelper->normaliseValueToType($defaultValue, $this->phpType);
301
302 26
        if (null !== $this->defaultValue) {
303 2
            $defaultValueType = $this->typeHelper->getType($this->defaultValue);
304 2
            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 26
        $this->isNullable = (null === $defaultValue);
318 26
        $this->isUnique   = $isUnique;
319
320 26
        if (\substr($fieldFqn, -\strlen(self::FIELD_TRAIT_SUFFIX)) === self::FIELD_TRAIT_SUFFIX) {
321 8
            $fieldFqn = \substr($fieldFqn, 0, -\strlen(self::FIELD_TRAIT_SUFFIX));
322
        }
323 26
        $this->fieldFqn = $fieldFqn;
324
325 26
        list($className, $traitNamespace, $traitSubDirectories) = $this->parseFullyQualifiedName(
326 26
            $this->fieldFqn,
327 26
            $this->srcSubFolderName
328
        );
329 26
        $this->className = $className;
330 26
        list(, $interfaceNamespace, $interfaceSubDirectories) = $this->parseFullyQualifiedName(
331 26
            \str_replace('Traits', 'Interfaces', $this->fieldFqn),
332 26
            $this->srcSubFolderName
333
        );
334
335 26
        $this->fieldsPath = $this->pathHelper->resolvePath(
336 26
            $this->pathToProjectRoot . '/' . \implode('/', $traitSubDirectories)
337
        );
338
339 26
        $this->fieldsInterfacePath = $this->pathHelper->resolvePath(
340 26
            $this->pathToProjectRoot . '/' . \implode('/', $interfaceSubDirectories)
341
        );
342
343 26
        $this->traitNamespace     = $traitNamespace;
344 26
        $this->interfaceNamespace = $interfaceNamespace;
345 26
    }
346
347
    /**
348
     * @return string
349
     * @throws DoctrineStaticMetaException
350
     *
351
     */
352 26
    protected function getPhpTypeForDbalType(): string
353
    {
354 26
        if (true === $this->isArchetype) {
355 14
            return '';
356
        }
357 16
        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 16
        return MappingHelper::COMMON_TYPES_TO_PHP_TYPES[$this->fieldType];
370
    }
371
372 26
    protected function getTraitPath(): string
373
    {
374 26
        return $this->fieldsPath . '/' . $this->codeHelper->classy($this->className) . 'FieldTrait.php';
375
    }
376
377 26
    protected function getInterfacePath(): string
378
    {
379 26
        return $this->fieldsInterfacePath . '/' . $this->codeHelper->classy($this->className) . 'FieldInterface.php';
380
    }
381
382
    /**
383
     * @return string
384
     * @throws \ReflectionException
385
     */
386 14
    protected function createFieldFromArchetype(): string
387
    {
388 14
        $copier = new ArchetypeFieldGenerator(
389 14
            $this->fileSystem,
390 14
            $this->namespaceHelper,
391 14
            $this->codeHelper,
392 14
            $this->findAndReplaceHelper,
393 14
            $this->reflectionHelper
394
        );
395
396 14
        return $copier->createFromArchetype(
397 14
            $this->fieldFqn,
398 14
            $this->getTraitPath(),
399 14
            $this->getInterfacePath(),
400 14
            '\\' . $this->fieldType,
401 14
            $this->projectRootNamespace
402 14
        ) . self::FIELD_TRAIT_SUFFIX;
403
    }
404
405
    /**
406
     * @return string
407
     * @throws DoctrineStaticMetaException
408
     */
409 16
    protected function createDbalField(): string
410
    {
411 16
        $creator = new DbalFieldGenerator(
412 16
            $this->fileSystem,
413 16
            $this->codeHelper,
414 16
            $this->fileCreationTransaction,
415 16
            $this->findAndReplaceHelper,
416 16
            $this->typeHelper,
417 16
            $this->pathHelper
418
        );
419
420 16
        return $creator->create(
421 16
            $this->className,
422 16
            $this->getTraitPath(),
423 16
            $this->getInterfacePath(),
424 16
            $this->fieldType,
425 16
            $this->defaultValue,
426 16
            $this->isUnique,
427 16
            $this->phpType,
428 16
            $this->traitNamespace,
429 16
            $this->interfaceNamespace
430
        );
431
    }
432
433 26
    private function assertFileDoesNotExist(string $filePath, string $type): void
434
    {
435 26
        if (file_exists($filePath)) {
436
            throw new \RuntimeException("Field $type already exists at $filePath");
437
        }
438 26
    }
439
}
440