Passed
Push — master ( 71f6f9...733da2 )
by Jakub
01:38
created

ReflectionExtractor::createProperty()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 3
dl 0
loc 17
ccs 11
cts 11
cp 1
crap 3
rs 9.9332
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Zalas\PHPUnit\Doubles\PhpDocumentor;
5
6
use phpDocumentor\Reflection\DocBlock;
7
use phpDocumentor\Reflection\DocBlock\Tag;
8
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
9
use phpDocumentor\Reflection\DocBlockFactory;
10
use phpDocumentor\Reflection\Types\Context;
11
use phpDocumentor\Reflection\Types\ContextFactory;
12
use Zalas\PHPUnit\Doubles\Extractor\Extractor;
13
use Zalas\PHPUnit\Doubles\Extractor\Property;
14
15
final class ReflectionExtractor implements Extractor
16
{
17
    /**
18
     * @var string[]
19
     */
20
    private $ignoredClasses;
21
22
    /**
23
     * @param string[] $ignoredClasses
24
     */
25 22
    public function __construct(array $ignoredClasses)
26
    {
27 22
        $this->ignoredClasses = $ignoredClasses;
28
    }
29
30
    /**
31
     * @param object   $object
32
     * @param callable $filter
33
     *
34
     * @return Property[]
35
     */
36 21
    public function extract(/*object */$object, callable $filter): array
37
    {
38 21
        return $this->extractFromReflection(new \ReflectionClass($object), $filter);
39
    }
40
41
    /**
42
     * @return Property[]
43
     */
44 21
    private function extractFromReflection(\ReflectionClass $class, callable $filter): array
45
    {
46 21
        $properties = $this->mapClassToProperties($class, $filter);
47 21
        $parentProperties = $class->getParentClass() ? $this->extractFromReflection($class->getParentClass(), $filter) : [];
48
49 21
        return \array_merge($properties, $parentProperties);
50
    }
51
52
    /**
53
     * @return Property[]
54
     */
55 21
    private function mapClassToProperties(\ReflectionClass $class, callable $filter): array
56
    {
57 21
        if (\in_array($class->getName(), $this->ignoredClasses)) {
58 19
            return [];
59
        }
60
61 21
        $docBlockFactory = DocBlockFactory::createInstance();
62 21
        $classContext = (new ContextFactory())->createFromReflector($class);
63
64 21
        return \array_filter(
65 21
            \array_map($this->propertyFactory($class, $docBlockFactory, $classContext), $class->getProperties()),
66 21
            $this->buildFilter($filter)
67
        );
68
    }
69
70 21
    private function propertyFactory(\ReflectionClass $class, $docBlockFactory, $classContext): callable
71
    {
72
        return function (\ReflectionProperty $propertyReflection) use ($docBlockFactory, $classContext, $class): ?Property {
73 21
            $context = $this->getTraitContextIfExists($propertyReflection) ?? $classContext;
74
75 21
            if ($propertyReflection->getDeclaringClass()->getName() === $class->getName()) {
76 21
                return $this->createProperty($propertyReflection, $docBlockFactory, $context);
77
            }
78
79 21
            return null;
80 21
        };
81
    }
82
83 21
    private function buildFilter(callable $filter): callable
84
    {
85
        return function ($property) use ($filter): bool {
86 21
            return $property instanceof Property && $filter($property);
87 21
        };
88
    }
89
90 21
    private function getTraitContextIfExists(\ReflectionProperty $propertyReflection): ?Context
91
    {
92 21
        foreach ($propertyReflection->getDeclaringClass()->getTraits() as $trait) {
93 21
            if ($trait->hasProperty($propertyReflection->getName())) {
94 21
                return (new ContextFactory())->createFromReflector($trait);
95
            }
96
        }
97
98 21
        return null;
99
    }
100
101 21
    private function createProperty(\ReflectionProperty $propertyReflection, DocBlockFactory $docBlockFactory, Context $context): ?Property
102
    {
103 21
        if ($propertyReflection->getDocComment()) {
104 21
            $var = $this->extractVar($docBlockFactory->create($propertyReflection, $context));
105
106 21
            return null !== $var ? new Property(
107 21
                $propertyReflection->getName(),
108 21
                \array_map(
109
                    function ($type) {
110 21
                        return \ltrim($type, '\\');
111 21
                    },
112 21
                    \explode('|', $var)
113
                )
114 21
            ) : null;
115
        }
116
117 2
        return null;
118
    }
119
120 21
    private function extractVar(DocBlock $docBlock): ?string
121
    {
122 21
        $var = $this->getFirstTag($docBlock, 'var');
123
124 21
        return $var instanceof Var_ ? (string) $var->getType() : null;
125
    }
126
127 21
    private function getFirstTag(DocBlock $docBlock, string $name): ?Tag
128
    {
129 21
        $tags = $docBlock->getTagsByName($name);
130
131 21
        return $tags[0] ?? null;
132
    }
133
}
134