Completed
Pull Request — master (#1214)
by
unknown
15:00
created

DocBlockTypeResolver::gatherGroupUseStatements()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 11
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Metadata\Driver;
6
7
class DocBlockTypeResolver
8
{
9
    /** resolve type hints from property */
10
    private const CLASS_PROPERTY_TYPE_HINT_REGEX = '#@var[\s]*([^\n\$\s]*)#';
11
    /** resolve single use statements */
12
    private const SINGLE_USE_STATEMENTS_REGEX = '/^[^\S\r\n]*use[\s]*([^;\n]*)[\s]*;$/m';
13
    /** resolve group use statements */
14
    private const GROUP_USE_STATEMENTS_REGEX = '/^[^\S\r\n]*use[[\s]*([^;\n]*)[\s]*{([a-zA-Z0-9\s\n\r,]*)};$/m';
15
    private const GLOBAL_NAMESPACE_PREFIX = '\\';
16
17
    public function getPropertyDocblockTypeHint(\ReflectionProperty $reflectionProperty): ?string
18
    {
19
        preg_match_all(self::CLASS_PROPERTY_TYPE_HINT_REGEX, $reflectionProperty->getDocComment(), $matchedDocBlockParameterTypes);
20
        $typeHint = trim($matchedDocBlockParameterTypes[1][0]);
21
22
        $unionTypeHint = [];
23
        foreach (explode('|', $typeHint) as $singleTypeHint) {
24
            if ('null' !== $singleTypeHint) {
25
                $unionTypeHint[] = $singleTypeHint;
26
            }
27
        }
28
        $typeHint = implode('|', $unionTypeHint);
29
        if (count($unionTypeHint) > 1) {
30
            throw new \InvalidArgumentException(sprintf("Can't use union type %s for collection in %s:%s", $typeHint, $reflectionProperty->getDeclaringClass()->getName(), $reflectionProperty->getName()));
31
        }
32
33
        if (false === strpos($typeHint, '[]')) {
34
            throw new \InvalidArgumentException(sprintf("Can't use incorrect type %s for collection in %s:%s", $typeHint, $reflectionProperty->getDeclaringClass()->getName(), $reflectionProperty->getName()));
35
        }
36
37
        $typeHint = rtrim($typeHint, '[]');
38
        if (!$this->hasGlobalNamespacePrefix($typeHint) && !$this->isPrimitiveType($typeHint)) {
39
            $typeHint = $this->expandClassNameUsingUseStatements($typeHint, $this->getDeclaringClassOrTrait($reflectionProperty), $reflectionProperty);
40
        }
41
42
        return 'array<' . ltrim($typeHint, '\\') . '>';
43
    }
44
45
    private function expandClassNameUsingUseStatements(string $typeHint, \ReflectionClass $declaringClass, \ReflectionProperty $reflectionProperty): string
46
    {
47
        $expandedClassName = $declaringClass->getNamespaceName() . '\\' . $typeHint;
48
        if (class_exists($expandedClassName)) {
49
            return $expandedClassName;
50
        }
51
52
        $classContents = file_get_contents($declaringClass->getFileName());
53
        $foundUseStatements = $this->gatherGroupUseStatements($classContents);
54
        $foundUseStatements = array_merge($this->gatherSingleUseStatements($classContents), $foundUseStatements);
55
56
        foreach ($foundUseStatements as $statementClassName) {
57
            if ($alias = explode('as', $statementClassName)) {
58
                if (array_key_exists(1, $alias) && trim($alias[1]) === $typeHint) {
59
                    return trim($alias[0]);
60
                }
61
            }
62
63
            if ($this->endsWith($statementClassName, $typeHint)) {
64
                return $statementClassName;
65
            }
66
        }
67
68
        throw new \InvalidArgumentException(sprintf("Can't use incorrect type %s for collection in %s:%s", $typeHint, $declaringClass->getName(), $reflectionProperty->getName()));
69
    }
70
71
    private function endsWith(string $statementClassToCheck, string $typeHintToSearchFor): bool
72
    {
73
        $typeHintToSearchFor = '\\' . $typeHintToSearchFor;
74
75
        return substr($statementClassToCheck, -strlen($typeHintToSearchFor)) === $typeHintToSearchFor;
76
    }
77
78
    private function isPrimitiveType(string $type): bool
79
    {
80
        return in_array($type, ['int', 'float', 'bool', 'string']);
81
    }
82
83
    private function hasGlobalNamespacePrefix(string $typeHint): bool
84
    {
85
        return self::GLOBAL_NAMESPACE_PREFIX === $typeHint[0];
86
    }
87
88
    private function gatherGroupUseStatements(string $classContents): array
89
    {
90
        $foundUseStatements = [];
91
        preg_match_all(self::GROUP_USE_STATEMENTS_REGEX, $classContents, $foundGroupUseStatements);
92
        for ($useStatementIndex = 0; $useStatementIndex < count($foundGroupUseStatements[0]); $useStatementIndex++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
93
            foreach (explode(',', $foundGroupUseStatements[2][$useStatementIndex]) as $singleUseStatement) {
94
                $foundUseStatements[] = trim($foundGroupUseStatements[1][$useStatementIndex]) . trim($singleUseStatement);
95
            }
96
        }
97
98
        return $foundUseStatements;
99
    }
100
101
    private function gatherSingleUseStatements(string $classContents): array
102
    {
103
        $foundUseStatements = [];
104
        preg_match_all(self::SINGLE_USE_STATEMENTS_REGEX, $classContents, $foundSingleUseStatements);
105
        for ($useStatementIndex = 0; $useStatementIndex < count($foundSingleUseStatements[0]); $useStatementIndex++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
106
            $foundUseStatements[] = trim($foundSingleUseStatements[1][$useStatementIndex]);
107
        }
108
109
        return $foundUseStatements;
110
    }
111
112
    private function getDeclaringClassOrTrait(\ReflectionProperty $reflectionProperty): \ReflectionClass
113
    {
114
        foreach ($reflectionProperty->getDeclaringClass()->getTraits() as $trait) {
115
            foreach ($trait->getProperties() as $traitProperty) {
116
                if ($traitProperty->getName() === $reflectionProperty->getName()) {
117
                    return $this->getDeclaringClassOrTrait($traitProperty);
118
                }
119
            }
120
        }
121
122
        return $reflectionProperty->getDeclaringClass();
123
    }
124
}
125