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 ( ad6674...32d2a0 )
by Ross
12s queued 11s
created

FieldGenerator   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 344
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 344
rs 10
c 0
b 0
f 0
wmc 26

14 Methods

Rating   Name   Duplication   Size   Complexity  
B setEntityHasField() 0 24 2
B generateField() 0 57 5
A ensurePathExists() 0 6 2
A getPhpTypeForDbalType() 0 11 2
A setGetterToIsForBools() 0 8 2
A postCopy() 0 11 1
A getIsNullable() 0 3 1
A assertFileDoesNotExist() 0 4 2
A setIsNullable() 0 5 1
B generateTrait() 0 26 2
A generateInterface() 0 18 2
A interfacePostCopy() 0 4 1
A traitPostCopy() 0 5 1
B getPropertyMetaMethod() 0 28 2
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator;
4
5
use Doctrine\Common\Inflector\Inflector;
6
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
7
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\IpAddressFieldTrait;
8
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\LabelFieldTrait;
9
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\NameFieldTrait;
10
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Attribute\QtyFieldTrait;
11
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\ActionedDateFieldTrait;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\ActivatedDateFieldTrait;
13
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\CompletedDateFieldTrait;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\DeactivatedDateFieldTrait;
15
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Date\TimestampFieldTrait;
16
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Flag\ApprovedFieldTrait;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Flag\DefaultFieldTrait;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Person\EmailFieldTrait;
19
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\Person\YearOfBirthFieldTrait;
20
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\UsesPHPMetaDataInterface;
21
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
22
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
23
use gossi\codegen\model\PhpClass;
24
use gossi\codegen\model\PhpInterface;
25
use gossi\codegen\model\PhpMethod;
26
use gossi\codegen\model\PhpParameter;
27
use gossi\codegen\model\PhpTrait;
28
use gossi\docblock\Docblock;
29
use gossi\docblock\tags\UnknownTag;
30
31
/**
32
 * Class FieldGenerator
33
 *
34
 * @package EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator
35
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
36
 */
37
class FieldGenerator extends AbstractGenerator
38
{
39
    protected $fieldsPath;
40
41
    protected $fieldsInterfacePath;
42
43
    protected $classy;
44
45
    protected $consty;
46
47
    protected $phpType;
48
49
    protected $dbalType;
50
51
    protected $isNullable = false;
52
53
    protected $traitNamespace;
54
55
    protected $interfaceNamespace;
56
57
    public const STANDARD_FIELDS = [
58
        // Attribute
59
        IpAddressFieldTrait::class,
60
        LabelFieldTrait::class,
61
        NameFieldTrait::class,
62
        QtyFieldTrait::class,
63
        // Date
64
        ActionedDateFieldTrait::class,
65
        ActivatedDateFieldTrait::class,
66
        CompletedDateFieldTrait::class,
67
        DeactivatedDateFieldTrait::class,
68
        TimestampFieldTrait::class,
69
        // Flag
70
        ApprovedFieldTrait::class,
71
        DefaultFieldTrait::class,
72
        // Person
73
        EmailFieldTrait::class,
74
        YearOfBirthFieldTrait::class,
75
    ];
76
77
    /**
78
     * @param string $fieldFqn
79
     * @param string $entityFqn
80
     *
81
     * @throws DoctrineStaticMetaException
82
     * @SuppressWarnings(PHPMD.StaticAccess)
83
     */
84
    public function setEntityHasField(string $entityFqn, string $fieldFqn): void
85
    {
86
        try {
87
            $entityReflection         = new \ReflectionClass($entityFqn);
88
            $entity                   = PhpClass::fromFile($entityReflection->getFileName());
89
            $fieldReflection          = new \ReflectionClass($fieldFqn);
90
            $field                    = PhpTrait::fromFile($fieldReflection->getFileName());
91
            $fieldInterfaceFqn        = \str_replace(
92
                ['Traits', 'Trait'],
93
                ['Interfaces', 'Interface'],
94
                $fieldFqn
95
            );
96
            $fieldInterfaceReflection = new \ReflectionClass($fieldInterfaceFqn);
97
            $fieldInterface           = PhpInterface::fromFile($fieldInterfaceReflection->getFileName());
98
        } catch (\Exception $e) {
99
            throw new DoctrineStaticMetaException(
100
                'Failed loading the entity or field from FQN: '.$e->getMessage(),
101
                $e->getCode(),
102
                $e
103
            );
104
        }
105
        $entity->addTrait($field);
106
        $entity->addInterface($fieldInterface);
107
        $this->codeHelper->generate($entity, $entityReflection->getFileName());
108
    }
109
110
111
    /**
112
     * Generate a new Field based on a property name and Doctrine Type
113
     *
114
     * @see MappingHelper::ALL_DBAL_TYPES for the full list of Dbal Types
115
     *
116
     * @param string      $fieldFqn
117
     * @param string      $dbalType
118
     * @param null|string $phpType
119
     *
120
     * @return string - The Fully Qualified Name of the generated Field Trait
121
     *
122
     * @throws \Symfony\Component\Filesystem\Exception\IOException
123
     * @throws \InvalidArgumentException
124
     * @throws \RuntimeException
125
     * @throws DoctrineStaticMetaException
126
     * @SuppressWarnings(PHPMD.StaticAccess)
127
     *
128
     */
129
    public function generateField(
130
        string $fieldFqn,
131
        string $dbalType,
132
        ?string $phpType = null
133
    ): string {
134
        if (false === strpos($fieldFqn, AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE)) {
135
            throw new \RuntimeException(
136
                'Fully qualified name [ '.$fieldFqn.' ]'
137
                .' does not include [ '.AbstractGenerator::ENTITY_FIELD_TRAIT_NAMESPACE.' ].'."\n"
138
                .'Please ensure you pass in the full namespace qualified field name'
139
            );
140
        }
141
        if (false === \in_array($dbalType, MappingHelper::ALL_DBAL_TYPES, true)) {
142
            throw new \InvalidArgumentException(
143
                'dbalType must be either null or one of MappingHelper::ALL_DBAL_TYPES'
144
            );
145
        }
146
        if ((null !== $phpType)
147
            && (false === \in_array($phpType, MappingHelper::PHP_TYPES, true))
148
        ) {
149
            throw new \InvalidArgumentException(
150
                'phpType must be either null or one of MappingHelper::PHP_TYPES'
151
            );
152
        }
153
154
        $this->dbalType = $dbalType;
155
        $this->phpType  = $phpType ?? $this->getPhpTypeForDbalType();
156
157
        list($className, $traitNamespace, $traitSubDirectories) = $this->parseFullyQualifiedName(
158
            $fieldFqn,
159
            $this->srcSubFolderName
160
        );
161
162
        list(, $interfaceNamespace, $interfaceSubDirectories) = $this->parseFullyQualifiedName(
163
            str_replace('Traits', 'Interfaces', $fieldFqn),
164
            $this->srcSubFolderName
165
        );
166
167
        $this->fieldsPath = $this->codeHelper->resolvePath(
168
            $this->pathToProjectRoot.'/'.implode('/', $traitSubDirectories)
169
        );
170
171
        $this->fieldsInterfacePath = $this->codeHelper->resolvePath(
172
            $this->pathToProjectRoot.'/'.implode('/', $interfaceSubDirectories)
173
        );
174
175
        $this->classy             = Inflector::classify($className);
176
        $this->consty             = strtoupper(Inflector::tableize($className));
177
        $this->traitNamespace     = $traitNamespace;
178
        $this->interfaceNamespace = $interfaceNamespace;
179
180
        $this->ensurePathExists($this->fieldsPath);
181
        $this->ensurePathExists($this->fieldsInterfacePath);
182
183
        $this->generateInterface();
184
185
        return $this->generateTrait();
186
    }
187
188
    /**
189
     * @param string $path
190
     */
191
    protected function ensurePathExists(string $path): void
192
    {
193
        if ($this->fileSystem->exists($path)) {
194
            return;
195
        }
196
        $this->fileSystem->mkdir($path);
197
    }
198
199
200
    /**
201
     * @return string
202
     * @throws DoctrineStaticMetaException
203
     *
204
     */
205
    protected function getPhpTypeForDbalType(): string
206
    {
207
        if (!\in_array($this->dbalType, MappingHelper::COMMON_TYPES, true)) {
208
            throw new DoctrineStaticMetaException(
209
                'Field type of '.$this->dbalType.' is not one of MappingHelper::COMMON_TYPES'
210
                ."\n\nYou can only use this dbal type if you pass in the explicit phpType as well "
211
                ."\n\nAlternatively, suggest you set the type as string and then edit the generated code as you see fit"
212
            );
213
        }
214
215
        return MappingHelper::COMMON_TYPES_TO_PHP_TYPES[$this->dbalType];
216
    }
217
218
    /**
219
     * @throws DoctrineStaticMetaException
220
     */
221
    protected function generateInterface(): void
222
    {
223
        $filePath = $this->fieldsInterfacePath.'/'.$this->classy.'FieldInterface.php';
224
        $this->assertFileDoesNotExist($filePath, 'Interface');
225
        try {
226
            $this->fileSystem->copy(
227
                $this->codeHelper->resolvePath(static::FIELD_INTERFACE_TEMPLATE_PATH),
228
                $filePath
229
            );
230
            $this->interfacePostCopy($filePath);
231
            $this->codeHelper->replaceTypeHintsInFile(
232
                $filePath,
233
                $this->phpType,
234
                $this->dbalType,
235
                $this->isNullable
236
            );
237
        } catch (\Exception $e) {
238
            throw new DoctrineStaticMetaException('Error in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
239
        }
240
    }
241
242
    /**
243
     * @param string $filePath
244
     *
245
     * @throws \RuntimeException
246
     * @throws DoctrineStaticMetaException
247
     */
248
    protected function postCopy(string $filePath): void
249
    {
250
        $this->fileCreationTransaction::setPathCreated($filePath);
251
        $this->replaceName(
252
            $this->classy,
253
            $filePath,
254
            static::FIND_ENTITY_FIELD_NAME
255
        );
256
        $this->findReplace('TEMPLATE_FIELD_NAME', $this->consty, $filePath);
257
        $this->codeHelper->tidyNamespacesInFile($filePath);
258
        $this->setGetterToIsForBools($filePath);
259
    }
260
261
    /**
262
     * @param string $filePath
263
     *
264
     * @throws \RuntimeException
265
     * @throws DoctrineStaticMetaException
266
     */
267
    protected function traitPostCopy(string $filePath): void
268
    {
269
        $this->replaceFieldTraitNamespace($this->traitNamespace, $filePath);
270
        $this->replaceFieldInterfaceNamespace($this->interfaceNamespace, $filePath);
271
        $this->postCopy($filePath);
272
    }
273
274
    protected function setGetterToIsForBools(string $filePath): void
275
    {
276
        if ($this->phpType !== 'bool') {
277
            return;
278
        }
279
        $this->findReplace(' get', ' is', $filePath);
280
        $this->findReplace(' isIs', ' is', $filePath);
281
        $this->findReplace(' isis', ' is', $filePath);
282
    }
283
284
    /**
285
     * @param string $filePath
286
     *
287
     * @throws \RuntimeException
288
     * @throws DoctrineStaticMetaException
289
     */
290
    protected function interfacePostCopy(string $filePath): void
291
    {
292
        $this->replaceFieldInterfaceNamespace($this->interfaceNamespace, $filePath);
293
        $this->postCopy($filePath);
294
    }
295
296
    /**
297
     * @return string
298
     * @throws DoctrineStaticMetaException
299
     * @SuppressWarnings(PHPMD.StaticAccess)
300
     */
301
    protected function generateTrait(): string
302
    {
303
        $filePath = $this->fieldsPath.'/'.$this->classy.'FieldTrait.php';
304
        $this->assertFileDoesNotExist($filePath, 'Trait');
305
        try {
306
            $this->fileSystem->copy(
307
                $this->codeHelper->resolvePath(static::FIELD_TRAIT_TEMPLATE_PATH),
308
                $filePath
309
            );
310
            $this->fileCreationTransaction::setPathCreated($filePath);
311
            $this->traitPostCopy($filePath);
312
            $trait = PhpTrait::fromFile($filePath);
313
            $trait->setMethod($this->getPropertyMetaMethod());
314
            $trait->addUseStatement('\\'.MappingHelper::class);
315
            $trait->addUseStatement('\\'.ClassMetadataBuilder::class);
316
            $this->codeHelper->generate($trait, $filePath);
317
            $this->codeHelper->replaceTypeHintsInFile(
318
                $filePath,
319
                $this->phpType,
320
                $this->dbalType,
321
                $this->isNullable
322
            );
323
324
            return $trait->getQualifiedName();
325
        } catch (\Exception $e) {
326
            throw new DoctrineStaticMetaException('Error in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e);
327
        }
328
    }
329
330
    /**
331
     *
332
     * @return PhpMethod
333
     * @SuppressWarnings(PHPMD.StaticAccess)
334
     */
335
    protected function getPropertyMetaMethod(): PhpMethod
336
    {
337
        $name   = UsesPHPMetaDataInterface::METHOD_PREFIX_GET_PROPERTY_DOCTRINE_META.$this->classy;
338
        $method = PhpMethod::create($name);
339
        $method->setStatic(true);
340
        $method->setVisibility('public');
341
        $method->setParameters(
342
            [PhpParameter::create('builder')->setType('ClassMetadataBuilder')]
343
        );
344
        $mappingHelperMethodName = 'setSimple'.ucfirst(strtolower($this->dbalType)).'Fields';
345
        $isNullableString        = $this->isNullable ? 'true' : 'false';
346
        $method->setBody(
347
            "
348
        MappingHelper::$mappingHelperMethodName(
349
            [{$this->classy}FieldInterface::PROP_{$this->consty}],
350
            \$builder,
351
            $isNullableString
352
        );                        
353
"
354
        );
355
        $method->setDocblock(
356
            DocBlock::create()
357
                    ->appendTag(
358
                        UnknownTag::create('SuppressWarnings(PHPMD.StaticAccess)')
359
                    )
360
        );
361
362
        return $method;
363
    }
364
365
    public function setIsNullable(bool $isNullable): FieldGenerator
366
    {
367
        $this->isNullable = $isNullable;
368
369
        return $this;
370
    }
371
372
    public function getIsNullable(): bool
373
    {
374
        return $this->isNullable;
375
    }
376
377
    private function assertFileDoesNotExist(string $filePath, string $type): void
378
    {
379
        if (file_exists($filePath)) {
380
            throw new \RuntimeException("Field $type already exists at $filePath");
381
        }
382
    }
383
}
384