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.
Passed
Pull Request — master (#58)
by joseph
17:08
created

FieldGenerator::ensurePathExists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 4
cts 4
cp 1
cc 2
eloc 3
nc 2
nop 1
crap 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\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->pathHelper->ensurePathExists($this->fieldsPath);
182 29
        $this->pathHelper->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 29
    protected function getTraitPath(): string
195
    {
196 29
        return $this->fieldsPath.'/'.$this->codeHelper->classy($this->className).'FieldTrait.php';
197
    }
198
199
200 29
    protected function getInterfacePath(): string
201
    {
202 29
        return $this->fieldsInterfacePath.'/'.$this->codeHelper->classy($this->className).'FieldInterface.php';
203
    }
204
205
    /**
206
     * @return string
207
     * @throws DoctrineStaticMetaException
208
     */
209 28
    protected function createDbalField(): string
210
    {
211 28
        $creator = new DbalFieldGenerator(
212 28
            $this->fileSystem,
213 28
            $this->codeHelper,
214 28
            $this->fileCreationTransaction,
215 28
            $this->findAndReplaceHelper,
216 28
            $this->typeHelper,
217 28
            $this->pathHelper
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->findAndReplaceHelper
244
        );
245
246 2
        return $copier->createFromArchetype(
247 2
            $this->fieldFqn,
248 2
            $this->getTraitPath(),
249 2
            $this->getInterfacePath(),
250 2
            '\\'.$this->fieldType,
251 2
            $this->projectRootNamespace
252 2
        ).self::FIELD_TRAIT_SUFFIX;
253
    }
254
255 33
    protected function validateArguments(
256
        string $fieldFqn,
257
        string $fieldType,
258
        ?string $phpType
259
    ): void {
260
        //Check for a correct looking field FQN
261 33
        if (false === \strpos($fieldFqn, AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE)) {
262 1
            throw new \InvalidArgumentException(
263 1
                'Fully qualified name [ '.$fieldFqn.' ]'
264 1
                .' does not include [ '.AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE.' ].'."\n"
265 1
                .'Please ensure you pass in the full namespace qualified field name'
266
            );
267
        }
268
        //Check that the field type is either a Dbal Type or a Field Archetype FQN
269 32
        if (false === \in_array($fieldType, MappingHelper::ALL_DBAL_TYPES, true)
270 32
            && false === \in_array($fieldType, self::STANDARD_FIELDS, true)
271 32
            && false === $this->traitFqnLooksLikeField($fieldType)
272
        ) {
273
            throw new \InvalidArgumentException(
274
                'fieldType '.$fieldType.' is not a valid field type'
275
            );
276
        }
277
        //Check the phpType is valid
278 31
        if ((null !== $phpType)
279 31
            && (false === \in_array($phpType, MappingHelper::PHP_TYPES, true))
280
        ) {
281 2
            throw new \InvalidArgumentException(
282 2
                'phpType must be either null or one of MappingHelper::PHP_TYPES'
283
            );
284
        }
285 29
    }
286
287
    /**
288
     * Does the specified trait FQN look like a field trait?
289
     *
290
     * @param string $traitFqn
291
     *
292
     * @return bool
293
     * @throws \ReflectionException
294
     */
295 2
    protected function traitFqnLooksLikeField(string $traitFqn): bool
296
    {
297
        try {
298 2
            $reflection = new \ReflectionClass($traitFqn);
299 1
        } catch (\ReflectionException $e) {
300 1
            throw new \InvalidArgumentException(
301 1
                'invalid traitFqn '.$traitFqn.' does not seem to exist',
302 1
                $e->getCode(),
303 1
                $e
304
            );
305
        }
306 1
        if (true !== $reflection->isTrait()) {
307
            throw new \InvalidArgumentException('field type is not a trait FQN');
308
        }
309 1
        if ('FieldTrait' !== \substr($traitFqn, -\strlen('FieldTrait'))) {
310
            throw new \InvalidArgumentException('traitFqn does not end in FieldTrait');
311
        }
312
313 1
        return true;
314
    }
315
316
    /**
317
     * Defining the properties for the field to be generated
318
     *
319
     * @param string      $fieldFqn
320
     * @param string      $fieldType
321
     * @param null|string $phpType
322
     * @param mixed       $defaultValue
323
     * @param bool        $isUnique
324
     *
325
     * @throws DoctrineStaticMetaException
326
     * @SuppressWarnings(PHPMD.StaticAccess)
327
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
328
     */
329 29
    protected function setupClassProperties(
330
        string $fieldFqn,
331
        string $fieldType,
332
        ?string $phpType,
333
        $defaultValue,
334
        bool $isUnique
335
    ): void {
336 29
        $this->fieldType = $fieldType;
337 29
        if (true !== \in_array($this->fieldType, MappingHelper::COMMON_TYPES, true)) {
338 2
            $this->isArchetype = true;
339
        }
340 29
        $this->phpType      = $phpType ?? $this->getPhpTypeForDbalType();
341 29
        $this->defaultValue = $this->typeHelper->normaliseValueToType($defaultValue, $this->phpType);
342
343 29
        if (null !== $this->defaultValue) {
344 2
            $defaultValueType = $this->typeHelper->getType($this->defaultValue);
345 2
            if ($defaultValueType !== $this->phpType) {
346
                throw new \InvalidArgumentException(
347
                    'default value '.$this->defaultValue.' has the type: '.$defaultValueType
348
                    .' whereas the phpType for this field has been set as '.$this->phpType.', these do not match up'
349
                );
350
            }
351
        }
352 29
        $this->isNullable = (null === $defaultValue);
353 29
        $this->isUnique   = $isUnique;
354
355 29
        if (\substr($fieldFqn, -\strlen(self::FIELD_TRAIT_SUFFIX)) === self::FIELD_TRAIT_SUFFIX) {
356 4
            $fieldFqn = \substr($fieldFqn, 0, -\strlen(self::FIELD_TRAIT_SUFFIX));
357
        }
358 29
        $this->fieldFqn = $fieldFqn;
359
360 29
        list($className, $traitNamespace, $traitSubDirectories) = $this->parseFullyQualifiedName(
361 29
            $fieldFqn,
362 29
            $this->srcSubFolderName
363
        );
364 29
        $this->className = $className;
365 29
        list(, $interfaceNamespace, $interfaceSubDirectories) = $this->parseFullyQualifiedName(
366 29
            \str_replace('Traits', 'Interfaces', $fieldFqn),
367 29
            $this->srcSubFolderName
368
        );
369
370 29
        $this->fieldsPath = $this->pathHelper->resolvePath(
371 29
            $this->pathToProjectRoot.'/'.\implode('/', $traitSubDirectories)
372
        );
373
374 29
        $this->fieldsInterfacePath = $this->pathHelper->resolvePath(
375 29
            $this->pathToProjectRoot.'/'.\implode('/', $interfaceSubDirectories)
376
        );
377
378 29
        $this->traitNamespace     = $traitNamespace;
379 29
        $this->interfaceNamespace = $interfaceNamespace;
380 29
    }
381
382 29
    private function assertFileDoesNotExist(string $filePath, string $type): void
383
    {
384 29
        if (file_exists($filePath)) {
385 1
            throw new \RuntimeException("Field $type already exists at $filePath");
386
        }
387 29
    }
388
389
    /**
390
     * @return string
391
     * @throws DoctrineStaticMetaException
392
     *
393
     */
394 29
    protected function getPhpTypeForDbalType(): string
395
    {
396 29
        if (true === $this->isArchetype) {
397 2
            return '';
398
        }
399 28
        if (!\in_array($this->fieldType, MappingHelper::COMMON_TYPES, true)) {
400
            throw new DoctrineStaticMetaException(
401
                'Field type of '.$this->fieldType.' is not one of MappingHelper::COMMON_TYPES'
402
                ."\n\nYou can only use this fieldType type if you pass in the explicit phpType as well "
403
                ."\n\nAlternatively, suggest you set the type as string and then edit the generated code as you see fit"
404
            );
405
        }
406
407 28
        return MappingHelper::COMMON_TYPES_TO_PHP_TYPES[$this->fieldType];
408
    }
409
}
410