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 (#57)
by Ross
16:56
created

FieldGenerator::getTraitPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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