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

DoctrineStaticMeta::getStaticMethods()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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