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 (#163)
by Ross
15:00
created

DoctrineStaticMeta::setMetaData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta;
4
5
use Doctrine\Common\Inflector\Inflector;
6
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
7
use Doctrine\ORM\Mapping\ClassMetadata;
8
use Doctrine\ORM\Mapping\ClassMetadataInfo;
9
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
10
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
11
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\ReflectionHelper;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\UsesPHPMetaDataInterface;
13
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
14
use ts\Reflection\ReflectionClass;
15
16
/**
17
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
18
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
19
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
20
 */
21
class DoctrineStaticMeta
22
{
23
    /**
24
     * @var ReflectionHelper
25
     */
26
    private static $reflectionHelper;
27
    /**
28
     * @var NamespaceHelper
29
     */
30
    private static $namespaceHelper;
31
    /**
32
     * @var \ts\Reflection\ReflectionClass
33
     */
34
    private $reflectionClass;
35
36
    /**
37
     * @var ClassMetadata|\Doctrine\Common\Persistence\Mapping\ClassMetadata|ClassMetadataInfo
38
     */
39
    private $metaData;
40
41
    /**
42
     * @var string
43
     */
44
    private $singular;
45
46
    /**
47
     * @var string
48
     */
49
    private $plural;
50
51
    /**
52
     * @var array
53
     */
54
    private $setters;
55
56
    /**
57
     * @var array
58
     */
59
    private $getters;
60
61
    /**
62
     * @var array|null
63
     */
64
    private $staticMethods;
65
    /**
66
     * @var array
67
     */
68
    private $requiredRelationProperties;
69
    /**
70
     * @var array
71
     */
72
    private $embeddableProperties;
73
74
    /**
75
     * DoctrineStaticMeta constructor.
76
     *
77
     * @param string $entityFqn
78
     *
79
     * @throws \ReflectionException
80
     */
81 36
    public function __construct(string $entityFqn)
82
    {
83 36
        $this->reflectionClass = new \ts\Reflection\ReflectionClass($entityFqn);
84 36
    }
85
86
    public function buildMetaData(): void
87
    {
88
        if (false === $this->metaData instanceof ClassMetadataInfo) {
89
            throw new \RuntimeException('Invalid meta data class ' . \ts\print_r($this->metaData, true));
90
        }
91
        $builder = new ClassMetadataBuilder($this->metaData);
92
        $this->loadDoctrineMetaData($builder, UsesPHPMetaDataInterface::METHOD_PREFIX_GET_PROPERTY_DOCTRINE_META);
93
        $this->loadDoctrineMetaData($builder, UsesPHPMetaDataInterface::METHOD_PREFIX_GET_CLASS_DOCTRINE_META);
94
        $this->setTableName($builder);
95
        $this->setChangeTrackingPolicy($builder);
96
        $this->setCustomRepositoryClass($builder);
97
    }
98
99
    /**
100
     * This method will reflect on the entity class and pull out all the methods that begin with
101
     * UsesPHPMetaDataInterface::METHOD_PREFIX_GET_PROPERTY_DOCTRINE_META
102
     *
103
     * Once it has an array of methods, it calls them all, passing in the $builder
104
     *
105
     * @param ClassMetadataBuilder $builder
106
     * @param string               $methodPrefix
107
     *
108
     * @throws DoctrineStaticMetaException
109
     * @SuppressWarnings(PHPMD.StaticAccess)
110
     */
111
    private function loadDoctrineMetaData(ClassMetadataBuilder $builder, string $methodPrefix): void
112
    {
113
        $methodName = '__no_method__';
114
        try {
115
            $staticMethods = $this->getStaticMethods();
116
            //now loop through and call them
117
            foreach ($staticMethods as $method) {
118
                $methodName = $method->getName();
119
                if (0 === stripos(
120
                    $methodName,
121
                    $methodPrefix
122
                )
123
                ) {
124
                    $method->setAccessible(true);
125
                    $method->invokeArgs(null, [$builder]);
126
                }
127
            }
128
        } catch (\Exception $e) {
129
            throw new DoctrineStaticMetaException(
130
                'Exception in ' . __METHOD__ . ' for '
131
                . $this->reflectionClass->getName() . "::$methodName\n\n"
132
                . $e->getMessage(),
133
                $e->getCode(),
134
                $e
135
            );
136
        }
137
    }
138
139
    /**
140
     * Get an array of all static methods implemented by the current class
141
     *
142
     * Merges trait methods
143
     * Filters out this trait
144
     *
145
     * @return array|\ReflectionMethod[]
146
     * @throws \ReflectionException
147
     */
148 1
    public function getStaticMethods(): array
149
    {
150 1
        if (null !== $this->staticMethods) {
151
            return $this->staticMethods;
152
        }
153 1
        $this->staticMethods = $this->reflectionClass->getMethods(
154 1
            \ReflectionMethod::IS_STATIC
155
        );
156
157 1
        return $this->staticMethods;
158
    }
159
160
    /**
161
     * Sets the table name for the class
162
     *
163
     * @param ClassMetadataBuilder $builder
164
     *
165
     * @SuppressWarnings(PHPMD.StaticAccess)
166
     */
167
    private function setTableName(ClassMetadataBuilder $builder): void
168
    {
169
        $tableName = MappingHelper::getTableNameForEntityFqn($this->reflectionClass->getName());
170
        $builder->setTable($tableName);
171
    }
172
173
    /**
174
     * Setting the change policy to be Notify - best performance
175
     *
176
     * @see http://doctrine-orm.readthedocs.io/en/latest/reference/change-tracking-policies.html
177
     *
178
     * @param ClassMetadataBuilder $builder
179
     */
180
    public function setChangeTrackingPolicy(ClassMetadataBuilder $builder): void
181
    {
182
        $builder->setChangeTrackingPolicyNotify();
183
    }
184
185
    private function setCustomRepositoryClass(ClassMetadataBuilder $builder)
186
    {
187
        $repositoryClassName = (new NamespaceHelper())->getRepositoryqnFromEntityFqn($this->reflectionClass->getName());
188
        $builder->setCustomRepositoryClass($repositoryClassName);
189
    }
190
191
    /**
192
     * Get an array of required relation properties, keyed by the property name and the value being an array of FQNs
193
     * for the declared types
194
     *
195
     * @return array [ propertyName => [...types]]
196
     * @throws \ReflectionException
197
     */
198 1
    public function getRequiredRelationProperties(): array
199
    {
200 1
        if (null !== $this->requiredRelationProperties) {
201
            return $this->requiredRelationProperties;
202
        }
203 1
        $traits = $this->reflectionClass->getTraits();
204 1
        $return = [];
205 1
        foreach ($traits as $traitName => $traitReflection) {
206 1
            if (false === \ts\stringContains($traitName, '\\HasRequired')) {
207 1
                continue;
208
            }
209 1
            if (false === \ts\stringContains($traitName, '\\Entity\\Relations\\')) {
210
                continue;
211
            }
212
213 1
            $property          = $traitReflection->getProperties()[0]->getName();
214 1
            $return[$property] = $this->getTypesFromVarComment(
215 1
                $property,
216 1
                $this->getReflectionHelper()->getTraitProvidingProperty($traitReflection, $property)
217
            );
218
        }
219 1
        $this->requiredRelationProperties = $return;
220
221 1
        return $return;
222
    }
223
224
    /**
225
     * Parse the docblock for a property and get the type, then read the source code to resolve the short type to the
226
     * FQN of the type. Roll on PHP 7.3
227
     *
228
     * @param string          $property
229
     *
230
     * @param ReflectionClass $traitReflection
231
     *
232
     * @return array
233
     */
234 2
    private function getTypesFromVarComment(string $property, ReflectionClass $traitReflection): array
235
    {
236 2
        $docComment = $this->reflectionClass->getProperty($property)->getDocComment();
237 2
        \preg_match('%@var\s*?(.+)%', $docComment, $matches);
238 2
        $traitCode = \ts\file_get_contents($traitReflection->getFileName());
239 2
        $types     = \explode('|', $matches[1]);
240 2
        $return    = [];
241 2
        foreach ($types as $type) {
242 2
            $type = \trim($type);
243 2
            if ('null' === $type) {
244
                continue;
245
            }
246 2
            if ('ArrayCollection' === $type) {
247 1
                continue;
248
            }
249 2
            $arrayNotation = '';
250 2
            if ('[]' === substr($type, -2)) {
251 1
                $type          = substr($type, 0, -2);
252 1
                $arrayNotation = '[]';
253
            }
254 2
            $pattern = "%^use (.+?)\\\\${type}(;| |\[)%m";
255 2
            \preg_match($pattern, $traitCode, $matches);
256 2
            if (!isset($matches[1])) {
257
                throw new \RuntimeException(
258
                    'Failed finding match for type ' . $type . ' in ' . $traitReflection->getFileName()
259
                );
260
            }
261 2
            $return[] = $matches[1] . '\\' . $type . $arrayNotation;
262
        }
263
264 2
        return $return;
265
    }
266
267 2
    private function getReflectionHelper(): ReflectionHelper
268
    {
269 2
        if (null === self::$reflectionHelper) {
270
            self::$reflectionHelper = new ReflectionHelper($this->getNamespaceHelper());
271
        }
272
273 2
        return self::$reflectionHelper;
274
    }
275
276 1
    private function getNamespaceHelper(): NamespaceHelper
277
    {
278 1
        if (null === self::$namespaceHelper) {
279
            self::$namespaceHelper = new NamespaceHelper();
280
        }
281
282 1
        return self::$namespaceHelper;
283
    }
284
285
    /**
286
     * Get an array of property names that contain embeddable objects
287
     *
288
     * @return array
289
     * @throws \ReflectionException
290
     */
291 1
    public function getEmbeddableProperties(): array
292
    {
293 1
        if (null !== $this->embeddableProperties) {
294
            return $this->embeddableProperties;
295
        }
296 1
        $traits = $this->reflectionClass->getTraits();
297 1
        $return = [];
298 1
        foreach ($traits as $traitName => $traitReflection) {
299 1
            if (\ts\stringContains($traitName, '\\Entity\\Embeddable\\Traits')) {
300 1
                $property                     = $traitReflection->getProperties()[0]->getName();
301 1
                $embeddableObjectInterfaceFqn = $this->getTypesFromVarComment(
302 1
                    $property,
303 1
                    $this->getReflectionHelper()->getTraitProvidingProperty($traitReflection, $property)
304 1
                )[0];
305 1
                $embeddableObject             = $this->getNamespaceHelper()
306 1
                                                     ->getEmbeddableObjectFqnFromEmbeddableObjectInterfaceFqn(
307 1
                                                         $embeddableObjectInterfaceFqn
308
                                                     );
309 1
                $return[$property]            = $embeddableObject;
310
            }
311
        }
312
313 1
        return $return;
314
    }
315
316
    /**
317
     * Get the property name the Entity is mapped by when plural
318
     *
319
     * Override it in your entity class if you are using an Entity class name that doesn't pluralize nicely
320
     *
321
     * @return string
322
     * @throws DoctrineStaticMetaException
323
     * @SuppressWarnings(PHPMD.StaticAccess)
324
     */
325 1
    public function getPlural(): string
326
    {
327
        try {
328 1
            if (null === $this->plural) {
329 1
                $singular     = $this->getSingular();
330 1
                $this->plural = Inflector::pluralize($singular);
331
            }
332
333 1
            return $this->plural;
334
        } catch (\Exception $e) {
335
            throw new DoctrineStaticMetaException(
336
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
337
                $e->getCode(),
338
                $e
339
            );
340
        }
341
    }
342
343
    /**
344
     * Get the property the name the Entity is mapped by when singular
345
     *
346
     * @return string
347
     * @throws DoctrineStaticMetaException
348
     * @SuppressWarnings(PHPMD.StaticAccess)
349
     */
350 2
    public function getSingular(): string
351
    {
352
        try {
353 2
            if (null === $this->singular) {
354 2
                $reflectionClass = $this->getReflectionClass();
355
356 2
                $shortName         = $reflectionClass->getShortName();
357 2
                $singularShortName = MappingHelper::singularize($shortName);
358
359 2
                $namespaceName   = $reflectionClass->getNamespaceName();
360 2
                $namespaceParts  = \explode(AbstractGenerator::ENTITIES_FOLDER_NAME, $namespaceName);
361 2
                $entityNamespace = \array_pop($namespaceParts);
362
363 2
                $namespacedShortName = \preg_replace(
364 2
                    '/\\\\/',
365 2
                    '',
366 2
                    $entityNamespace . $singularShortName
367
                );
368
369 2
                $this->singular = \lcfirst($namespacedShortName);
370
            }
371
372 2
            return $this->singular;
373
        } catch (\Exception $e) {
374
            throw new DoctrineStaticMetaException(
375
                'Exception in ' . __METHOD__ . ': ' . $e->getMessage(),
376
                $e->getCode(),
377
                $e
378
            );
379
        }
380
    }
381
382
    /**
383
     * @return \ts\Reflection\ReflectionClass
384
     */
385 6
    public function getReflectionClass(): \ts\Reflection\ReflectionClass
386
    {
387 6
        return $this->reflectionClass;
388
    }
389
390
    /**
391
     * Get an array of setters by name
392
     *
393
     * @return array|string[]
394
     */
395 1
    public function getSetters(): array
396
    {
397 1
        if (null !== $this->setters) {
398
            return $this->setters;
399
        }
400
        $skip            = [
401 1
            'addPropertyChangedListener'     => true,
402
            'setEntityCollectionAndNotify'   => true,
403
            'addToEntityCollectionAndNotify' => true,
404
            'setEntityAndNotify'             => true,
405
        ];
406 1
        $this->setters   = [];
407 1
        $reflectionClass = $this->getReflectionClass();
408 1
        foreach ($reflectionClass->getMethods(
409 1
            \ReflectionMethod::IS_PRIVATE | \ReflectionMethod::IS_PUBLIC
410
        ) as $method) {
411 1
            $methodName = $method->getName();
412 1
            if (isset($skip[$methodName])) {
413 1
                continue;
414
            }
415 1
            if (\ts\stringStartsWith($methodName, 'set')) {
416 1
                $this->setters[$this->getGetterForSetter($methodName)] = $methodName;
417 1
                continue;
418
            }
419
        }
420
421 1
        return $this->setters;
422
    }
423
424 1
    private function getGetterForSetter(string $setterName): string
425
    {
426 1
        $propertyName    = $this->getPropertyNameFromSetterName($setterName);
427 1
        $matchingGetters = [];
428 1
        foreach ($this->getGetters() as $getterName) {
429 1
            $getterPropertyName = $this->getPropertyNameFromGetterName($getterName);
430 1
            if (strtolower($getterPropertyName) === strtolower($propertyName)) {
431 1
                $matchingGetters[] = $getterName;
432
            }
433
        }
434 1
        if (count($matchingGetters) !== 1) {
435
            throw new \RuntimeException(
436
                'Found either less or more than one matching getter for ' .
437
                $propertyName . ': ' . print_r($matchingGetters, true)
438
                . "\n Current Entity: " . $this->getReflectionClass()->getName()
439
            );
440
        }
441
442 1
        return current($matchingGetters);
443
    }
444
445 14
    public function getPropertyNameFromSetterName(string $setterName): string
446
    {
447 14
        $propertyName = preg_replace('%^(set|add)(.+)%', '$2', $setterName);
448 14
        $propertyName = lcfirst($propertyName);
449
450 14
        return $propertyName;
451
    }
452
453
    /**
454
     * Get an array of getters by name
455
     * [];
456
     *
457
     * @return array|string[]
458
     */
459 2
    public function getGetters(): array
460
    {
461 2
        if (null !== $this->getters) {
462 1
            return $this->getters;
463
        }
464
        $skip = [
465 2
            'getEntityFqn'          => true,
466
            'getDoctrineStaticMeta' => true,
467
            'isValid'               => true,
468
        ];
469
470 2
        $this->getters   = [];
471 2
        $reflectionClass = $this->getReflectionClass();
472 2
        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
473 2
            $methodName = $method->getName();
474 2
            if (isset($skip[$methodName])) {
475 2
                continue;
476
            }
477 2
            if (\ts\stringStartsWith($methodName, 'get')) {
478 2
                $this->getters[] = $methodName;
479 2
                continue;
480
            }
481 2
            if (\ts\stringStartsWith($methodName, 'is')) {
482 2
                $this->getters[] = $methodName;
483 2
                continue;
484
            }
485 2
            if (\ts\stringStartsWith($methodName, 'has')) {
486
                $this->getters[] = $methodName;
487 2
                continue;
488
            }
489
        }
490
491 2
        return $this->getters;
492
    }
493
494 14
    public function getPropertyNameFromGetterName(string $getterName): string
495
    {
496 14
        $propertyName = preg_replace('%^(get|is|has)(.+)%', '$2', $getterName);
497 14
        $propertyName = lcfirst($propertyName);
498
499 14
        return $propertyName;
500
    }
501
502
    /**
503
     * Get the short name (without fully qualified namespace) of the current Entity
504
     *
505
     * @return string
506
     */
507 1
    public function getShortName(): string
508
    {
509 1
        $reflectionClass = $this->getReflectionClass();
510
511 1
        return $reflectionClass->getShortName();
512
    }
513
514 1
    public function getMetaData(): ClassMetadata
515
    {
516 1
        return $this->metaData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->metaData could return the type Doctrine\Common\Persiste...pping\ClassMetadataInfo which includes types incompatible with the type-hinted return Doctrine\ORM\Mapping\ClassMetadata. Consider adding an additional type-check to rule them out.
Loading history...
517
    }
518
519 1
    public function setMetaData(ClassMetadata $metaData): self
520
    {
521 1
        $this->metaData = $metaData;
522
523 1
        return $this;
524
    }
525
}
526