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 (#214)
by joseph
21:10
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 66
    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 66
        parent::__construct(
123 66
            $filesystem,
124 66
            $fileCreationTransaction,
125 66
            $namespaceHelper,
126 66
            $config,
127 66
            $codeHelper,
128 66
            $pathHelper,
129 66
            $findAndReplaceHelper
130
        );
131 66
        $this->typeHelper                        = $typeHelper;
132 66
        $this->reflectionHelper                  = $reflectionHelper;
133 66
        $this->createDbalFieldAndInterfaceAction = $createDbalFieldAndInterfaceAction;
134 66
    }
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 66
    public function generateField(
157
        string $fieldFqn,
158
        string $fieldType,
159
        ?string $phpType = null,
160
        $defaultValue = null,
161
        bool $isUnique = false
162
    ): string {
163 66
        $this->validateArguments($fieldFqn, $fieldType, $phpType);
164 56
        $this->setupClassProperties($fieldFqn, $fieldType, $phpType, $defaultValue, $isUnique);
165
166 56
        $this->pathHelper->ensurePathExists($this->fieldsPath);
167 56
        $this->pathHelper->ensurePathExists($this->fieldsInterfacePath);
168
169 56
        $this->assertFileDoesNotExist($this->getTraitPath(), 'Trait');
170 56
        $this->assertFileDoesNotExist($this->getInterfacePath(), 'Interface');
171
172 56
        if (true === $this->isArchetype) {
173 14
            return $this->createFieldFromArchetype();
174
        }
175
176 46
        return $this->createDbalUsingAction();
177
    }
178
179 66
    protected function validateArguments(
180
        string $fieldFqn,
181
        string $fieldType,
182
        ?string $phpType
183
    ): void {
184
        //Check for a correct looking field FQN
185 66
        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 2
            throw new InvalidArgumentException(
187 2
                'Fully qualified name [ ' . $fieldFqn . ' ]'
188 2
                . ' does not include [ ' . AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE . ' ].' . "\n"
189 2
                . 'Please ensure you pass in the full namespace qualified field name'
190
            );
191
        }
192 64
        $fieldShortName = $this->namespaceHelper->getClassShortName($fieldFqn);
193 64
        if (preg_match('%^(get|set|is|has)%i', $fieldShortName, $matches)) {
194 2
            throw new InvalidArgumentException(
195 2
                'Your field short name ' . $fieldShortName
196 2
                . ' begins with the forbidden string "' . $matches[1] .
197 2
                '", 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 62
        if (false === ($this->hasFieldNamespace($fieldType) && $this->traitFqnLooksLikeField($fieldType))
202 62
            && false === in_array(strtolower($fieldType), MappingHelper::COMMON_TYPES, true)
203
        ) {
204 2
            throw new InvalidArgumentException(
205 2
                'fieldType ' . $fieldType . ' is not a valid field type'
206
            );
207
        }
208
        //Check the phpType is valid
209 60
        if ((null !== $phpType)
210 60
            && (false === in_array($phpType, MappingHelper::PHP_TYPES, true))
211
        ) {
212 4
            throw new InvalidArgumentException(
213 4
                'phpType must be either null or one of MappingHelper::PHP_TYPES'
214
            );
215
        }
216 56
    }
217
218 62
    private function hasFieldNamespace(string $fieldType): bool
219
    {
220 62
        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 14
    protected function traitFqnLooksLikeField(string $traitFqn): bool
231
    {
232
        try {
233 14
            $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 14
        if (true !== $reflection->isTrait()) {
242
            throw new InvalidArgumentException('field type is not a trait FQN');
243
        }
244 14
        if ('FieldTrait' !== substr($traitFqn, -strlen('FieldTrait'))) {
245
            throw new InvalidArgumentException('traitFqn does not end in FieldTrait');
246
        }
247
248 14
        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 56
    protected function setupClassProperties(
265
        string $fieldFqn,
266
        string $fieldType,
267
        ?string $phpType,
268
        $defaultValue,
269
        bool $isUnique
270
    ): void {
271 56
        $this->isArchetype = false;
272 56
        $this->fieldType   = strtolower($fieldType);
273 56
        if (true !== in_array($this->fieldType, MappingHelper::COMMON_TYPES, true)) {
274 14
            $this->isArchetype = true;
275 14
            $this->fieldType   = $fieldType;
276
        }
277 56
        $this->phpType      = $phpType ?? $this->getPhpTypeForType();
278 56
        $this->defaultValue = $this->typeHelper->normaliseValueToType($defaultValue, $this->phpType);
279
280 56
        if (null !== $this->defaultValue) {
281 32
            $defaultValueType = $this->typeHelper->getType($this->defaultValue);
282 32
            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 56
        $this->isNullable = (null === $defaultValue);
296 56
        $this->isUnique   = $isUnique;
297
298 56
        if (substr($fieldFqn, -strlen(self::FIELD_TRAIT_SUFFIX)) === self::FIELD_TRAIT_SUFFIX) {
299 8
            $fieldFqn = substr($fieldFqn, 0, -strlen(self::FIELD_TRAIT_SUFFIX));
300
        }
301 56
        $this->fieldFqn = $fieldFqn;
302
303 56
        [$className, $traitNamespace, $traitSubDirectories] = $this->parseFullyQualifiedName(
304 56
            $this->fieldFqn,
305 56
            $this->srcSubFolderName
306
        );
307 56
        $this->className = $className;
308 56
        list(, $interfaceNamespace, $interfaceSubDirectories) = $this->parseFullyQualifiedName(
309 56
            str_replace('Traits', 'Interfaces', $this->fieldFqn),
310 56
            $this->srcSubFolderName
311
        );
312
313 56
        $this->fieldsPath = $this->pathHelper->resolvePath(
314 56
            $this->pathToProjectRoot . '/' . implode('/', $traitSubDirectories)
315
        );
316
317 56
        $this->fieldsInterfacePath = $this->pathHelper->resolvePath(
318 56
            $this->pathToProjectRoot . '/' . implode('/', $interfaceSubDirectories)
319
        );
320
321 56
        $this->traitNamespace     = $traitNamespace;
322 56
        $this->interfaceNamespace = $interfaceNamespace;
323 56
    }
324
325
    /**
326
     * @return string
327
     * @throws DoctrineStaticMetaException
328
     *
329
     */
330 56
    protected function getPhpTypeForType(): string
331
    {
332 56
        if (true === $this->isArchetype) {
333 14
            return '';
334
        }
335 46
        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 46
        return MappingHelper::COMMON_TYPES_TO_PHP_TYPES[$this->fieldType];
348
    }
349
350 56
    private function assertFileDoesNotExist(string $filePath, string $type): void
351
    {
352 56
        if (file_exists($filePath)) {
353
            throw new RuntimeException("Field $type already exists at $filePath");
354
        }
355 56
    }
356
357 56
    protected function getTraitPath(): string
358
    {
359 56
        return $this->fieldsPath . '/' . $this->codeHelper->classy($this->className) . 'FieldTrait.php';
360
    }
361
362 56
    protected function getInterfacePath(): string
363
    {
364 56
        return $this->fieldsInterfacePath . '/' . $this->codeHelper->classy($this->className) . 'FieldInterface.php';
365
    }
366
367
    /**
368
     * @return string
369
     * @throws ReflectionException
370
     */
371 14
    protected function createFieldFromArchetype(): string
372
    {
373 14
        $copier = new ArchetypeFieldGenerator(
374 14
            $this->fileSystem,
375 14
            $this->namespaceHelper,
376 14
            $this->codeHelper,
377 14
            $this->findAndReplaceHelper,
378 14
            $this->reflectionHelper
379
        );
380
381 14
        return $copier->createFromArchetype(
382 14
            $this->fieldFqn,
383 14
            $this->getTraitPath(),
384 14
            $this->getInterfacePath(),
385 14
            '\\' . $this->fieldType,
386 14
            $this->projectRootNamespace
387 14
        ) . self::FIELD_TRAIT_SUFFIX;
388
    }
389
390 46
    private function createDbalUsingAction(): string
391
    {
392 46
        $fqn = $this->fieldFqn . FieldTraitCreator::SUFFIX;
393 46
        $this->createDbalFieldAndInterfaceAction->setFieldTraitFqn($fqn)
394 46
                                                ->setIsUnique($this->isUnique)
395 46
                                                ->setDefaultValue($this->defaultValue)
396 46
                                                ->setMappingHelperCommonType($this->fieldType)
397 46
                                                ->setProjectRootDirectory($this->pathToProjectRoot)
398 46
                                                ->setProjectRootNamespace($this->projectRootNamespace)
399 46
                                                ->run();
400
401 46
        return $fqn;
402
    }
403
}
404