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 (#214)
by joseph
20:33
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.0008

Importance

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