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 (#224)
by joseph
22:25
created

FieldGenerator::createDbalUsingAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 12
ccs 0
cts 11
cp 0
crap 2
rs 9.9666
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
    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
        parent::__construct(
126
            $filesystem,
127
            $fileCreationTransaction,
128
            $namespaceHelper,
129
            $config,
130
            $codeHelper,
131
            $pathHelper,
132
            $findAndReplaceHelper
133
        );
134
        $this->typeHelper                        = $typeHelper;
135
        $this->reflectionHelper                  = $reflectionHelper;
136
        $this->createDbalFieldAndInterfaceAction = $createDbalFieldAndInterfaceAction;
137
    }
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
    public function generateField(
160
        string $fieldFqn,
161
        string $fieldType,
162
        ?string $phpType = null,
163
        $defaultValue = null,
164
        bool $isUnique = false
165
    ): string {
166
        $this->validateArguments($fieldFqn, $fieldType, $phpType);
167
        $this->setupClassProperties($fieldFqn, $fieldType, $phpType, $defaultValue, $isUnique);
168
169
        $this->pathHelper->ensurePathExists($this->fieldsPath);
170
        $this->pathHelper->ensurePathExists($this->fieldsInterfacePath);
171
172
        $this->assertFileDoesNotExist($this->getTraitPath(), 'Trait');
173
        $this->assertFileDoesNotExist($this->getInterfacePath(), 'Interface');
174
175
        if (true === $this->isArchetype) {
176
            return $this->createFieldFromArchetype();
177
        }
178
179
        return $this->createDbalUsingAction();
180
    }
181
182
    protected function validateArguments(
183
        string $fieldFqn,
184
        string $fieldType,
185
        ?string $phpType
186
    ): void {
187
        //Check for a correct looking field FQN
188
        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
            throw new InvalidArgumentException(
190
                'Fully qualified name [ ' . $fieldFqn . ' ]'
191
                . ' does not include [ ' . AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE . ' ].' . "\n"
192
                . 'Please ensure you pass in the full namespace qualified field name'
193
            );
194
        }
195
        $fieldShortName = $this->namespaceHelper->getClassShortName($fieldFqn);
196
        if (preg_match('%^(get|set|is|has)%i', $fieldShortName, $matches)) {
197
            throw new InvalidArgumentException(
198
                'Your field short name ' . $fieldShortName
199
                . ' begins with the forbidden string "' . $matches[1] .
200
                '", 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
            false === ($this->hasFieldNamespace($fieldType)
206
            && $this->traitFqnLooksLikeField($fieldType))
207
            && false === in_array(strtolower($fieldType), MappingHelper::COMMON_TYPES, true)
208
        ) {
209
            throw new InvalidArgumentException(
210
                'fieldType ' . $fieldType . ' is not a valid field type'
211
            );
212
        }
213
        //Check the phpType is valid
214
        if (
215
            (null !== $phpType)
216
            && (false === in_array($phpType, MappingHelper::PHP_TYPES, true))
217
        ) {
218
            throw new InvalidArgumentException(
219
                'phpType must be either null or one of MappingHelper::PHP_TYPES'
220
            );
221
        }
222
    }
223
224
    private function hasFieldNamespace(string $fieldType): bool
225
    {
226
        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
    protected function traitFqnLooksLikeField(string $traitFqn): bool
237
    {
238
        try {
239
            $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
        if (true !== $reflection->isTrait()) {
248
            throw new InvalidArgumentException('field type is not a trait FQN');
249
        }
250
        if ('FieldTrait' !== substr($traitFqn, -strlen('FieldTrait'))) {
251
            throw new InvalidArgumentException('traitFqn does not end in FieldTrait');
252
        }
253
254
        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
    protected function setupClassProperties(
271
        string $fieldFqn,
272
        string $fieldType,
273
        ?string $phpType,
274
        $defaultValue,
275
        bool $isUnique
276
    ): void {
277
        $this->isArchetype = false;
278
        $this->fieldType   = strtolower($fieldType);
279
        if (true !== in_array($this->fieldType, MappingHelper::COMMON_TYPES, true)) {
280
            $this->isArchetype = true;
281
            $this->fieldType   = $fieldType;
282
        }
283
        $this->phpType      = $phpType ?? $this->getPhpTypeForType();
284
        $this->defaultValue = $this->typeHelper->normaliseValueToType($defaultValue, $this->phpType);
285
286
        if (null !== $this->defaultValue) {
287
            $defaultValueType = $this->typeHelper->getType($this->defaultValue);
288
            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
        $this->isNullable = (null === $defaultValue);
302
        $this->isUnique   = $isUnique;
303
304
        if (substr($fieldFqn, -strlen(self::FIELD_TRAIT_SUFFIX)) === self::FIELD_TRAIT_SUFFIX) {
305
            $fieldFqn = substr($fieldFqn, 0, -strlen(self::FIELD_TRAIT_SUFFIX));
306
        }
307
        $this->fieldFqn = $fieldFqn;
308
309
        [$className, $traitNamespace, $traitSubDirectories] = $this->parseFullyQualifiedName(
310
            $this->fieldFqn,
311
            $this->srcSubFolderName
312
        );
313
        $this->className = $className;
314
        list(, $interfaceNamespace, $interfaceSubDirectories) = $this->parseFullyQualifiedName(
315
            str_replace('Traits', 'Interfaces', $this->fieldFqn),
316
            $this->srcSubFolderName
317
        );
318
319
        $this->fieldsPath = $this->pathHelper->resolvePath(
320
            $this->pathToProjectRoot . '/' . implode('/', $traitSubDirectories)
321
        );
322
323
        $this->fieldsInterfacePath = $this->pathHelper->resolvePath(
324
            $this->pathToProjectRoot . '/' . implode('/', $interfaceSubDirectories)
325
        );
326
327
        $this->traitNamespace     = $traitNamespace;
328
        $this->interfaceNamespace = $interfaceNamespace;
329
    }
330
331
    /**
332
     * @return string
333
     * @throws DoctrineStaticMetaException
334
     *
335
     */
336
    protected function getPhpTypeForType(): string
337
    {
338
        if (true === $this->isArchetype) {
339
            return '';
340
        }
341
        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
        return MappingHelper::COMMON_TYPES_TO_PHP_TYPES[$this->fieldType];
354
    }
355
356
    private function assertFileDoesNotExist(string $filePath, string $type): void
357
    {
358
        if (file_exists($filePath)) {
359
            throw new RuntimeException("Field $type already exists at $filePath");
360
        }
361
    }
362
363
    protected function getTraitPath(): string
364
    {
365
        return $this->fieldsPath . '/' . $this->codeHelper->classy($this->className) . 'FieldTrait.php';
366
    }
367
368
    protected function getInterfacePath(): string
369
    {
370
        return $this->fieldsInterfacePath . '/' . $this->codeHelper->classy($this->className) . 'FieldInterface.php';
371
    }
372
373
    /**
374
     * @return string
375
     * @throws ReflectionException
376
     */
377
    protected function createFieldFromArchetype(): string
378
    {
379
        $copier = new ArchetypeFieldGenerator(
380
            $this->fileSystem,
381
            $this->namespaceHelper,
382
            $this->codeHelper,
383
            $this->findAndReplaceHelper,
384
            $this->reflectionHelper
385
        );
386
387
        return $copier->createFromArchetype(
388
            $this->fieldFqn,
389
            $this->getTraitPath(),
390
            $this->getInterfacePath(),
391
            '\\' . $this->fieldType,
392
            $this->projectRootNamespace
393
        ) . self::FIELD_TRAIT_SUFFIX;
394
    }
395
396
    private function createDbalUsingAction(): string
397
    {
398
        $fqn = $this->fieldFqn . FieldTraitCreator::SUFFIX;
399
        $this->createDbalFieldAndInterfaceAction->setFieldTraitFqn($fqn)
400
                                                ->setIsUnique($this->isUnique)
401
                                                ->setDefaultValue($this->defaultValue)
402
                                                ->setMappingHelperCommonType($this->fieldType)
403
                                                ->setProjectRootDirectory($this->pathToProjectRoot)
404
                                                ->setProjectRootNamespace($this->projectRootNamespace)
405
                                                ->run();
406
407
        return $fqn;
408
    }
409
}
410