Completed
Push — master ( 3486c0...5be343 )
by Jakub
03:32
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
     * @param object   $object
19
     * @param callable $filter
20
     *
21
     * @return Property[]
22
     */
23 6
    public function extract(/*object */$object, callable $filter): array
24
    {
25 6
        return $this->extractFromReflection(new \ReflectionClass($object), $filter);
26
    }
27
28
    /**
29
     * @return Property[]
30
     */
31 6
    private function extractFromReflection(\ReflectionClass $class, callable $filter): array
32
    {
33 6
        $properties = $this->mapClassToProperties($class, $filter);
34 6
        $parentProperties = $class->getParentClass() ? $this->extractFromReflection($class->getParentClass(), $filter) : [];
35
36 6
        return \array_merge($properties, $parentProperties);
37
    }
38
39
    /**
40
     * @return Property[]
41
     */
42 6
    private function mapClassToProperties(\ReflectionClass $class, callable $filter): array
43
    {
44 6
        $docBlockFactory = DocBlockFactory::createInstance();
45 6
        $classContext = (new ContextFactory())->createFromReflector($class);
46
47 6
        return \array_filter(
48 6
            \array_map($this->propertyFactory($class, $docBlockFactory, $classContext), $class->getProperties()),
49 6
            $this->buildFilter($filter)
50
        );
51
    }
52
53 6
    private function propertyFactory(\ReflectionClass $class, $docBlockFactory, $classContext): callable
54
    {
55
        return function (\ReflectionProperty $propertyReflection) use ($docBlockFactory, $classContext, $class): ?Property {
56 6
            $context = $this->getTraitContextIfExists($propertyReflection) ?? $classContext;
57
58 6
            if ($propertyReflection->getDeclaringClass()->getName() === $class->getName()) {
59 6
                return $this->createProperty($propertyReflection, $docBlockFactory, $context);
60
            }
61
62 6
            return null;
63 6
        };
64
    }
65
66 6
    private function buildFilter(callable $filter): callable
67
    {
68
        return function ($property) use ($filter): bool {
69 6
            return $property instanceof Property && $filter($property);
70 6
        };
71
    }
72
73 6
    private function getTraitContextIfExists(\ReflectionProperty $propertyReflection): ?Context
74
    {
75 6
        foreach ($propertyReflection->getDeclaringClass()->getTraits() as $trait) {
76 6
            if ($trait->hasProperty($propertyReflection->getName())) {
77 6
                return (new ContextFactory())->createFromReflector($trait);
78
            }
79
        }
80
81 6
        return null;
82
    }
83
84 6
    private function createProperty(\ReflectionProperty $propertyReflection, DocBlockFactory $docBlockFactory, Context $context): ?Property
85
    {
86 6
        if ($propertyReflection->getDocComment()) {
87 6
            $var = $this->extractVar($docBlockFactory->create($propertyReflection, $context));
88
89 6
            return null !== $var ? new Property(
90 6
                $propertyReflection->getName(),
91 6
                \array_map(
92
                    function ($type) {
93 6
                        return \ltrim($type, '\\');
94 6
                    },
95 6
                    \explode('|', $var)
96
                )
97 6
            ) : null;
98
        }
99
100 2
        return null;
101
    }
102
103 6
    private function extractVar(DocBlock $docBlock): ?string
104
    {
105 6
        $var = $this->getFirstTag($docBlock, 'var');
106
107 6
        return $var instanceof Var_ ? (string) $var->getType() : null;
108
    }
109
110 6
    private function getFirstTag(DocBlock $docBlock, string $name): ?Tag
111
    {
112 6
        $tags = $docBlock->getTagsByName($name);
113
114 6
        return $tags[0] ?? null;
115
    }
116
}
117