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
Push — master ( 2a6b46...0d82f1 )
by joseph
20s queued 14s
created

FieldGenerator::hasFieldNamespace()   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.037

Importance

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