Completed
Pull Request — master (#1214)
by
unknown
12:55
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\$]*)#';
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
        if (!$reflectionProperty->getDocComment()) {
20
            return null;
21
        }
22
23
        preg_match_all(self::CLASS_PROPERTY_TYPE_HINT_REGEX, $reflectionProperty->getDocComment(), $matchedDocBlockParameterTypes);
24
        if (!isset($matchedDocBlockParameterTypes[1][0])) {
25
            return null;
26
        }
27
28
        $typeHint = trim($matchedDocBlockParameterTypes[1][0]);
29
30
        $unionTypeHint = [];
31
        foreach (explode('|', $typeHint) as $singleTypeHint) {
32
            if ('null' !== $singleTypeHint) {
33
                $unionTypeHint[] = $singleTypeHint;
34
            }
35
        }
36
        $typeHint = implode('|', $unionTypeHint);
37
        if (count($unionTypeHint) > 1) {
38
            throw new \InvalidArgumentException(sprintf("Can't use union type %s for collection in %s:%s", $typeHint, $reflectionProperty->getDeclaringClass()->getName(), $reflectionProperty->getName()));
39
        }
40
41
        if (false !== strpos($typeHint, 'array<')) {
42
            $resolvedTypes = [];
43
            preg_match_all("#array<(.*)>#", $typeHint, $genericTypesToResolve);
44
            $genericTypesToResolve = $genericTypesToResolve[1][0];
45
            foreach (explode(",", $genericTypesToResolve) as $genericTypeToResolve) {
46
                $resolvedTypes[] = $this->resolveType(trim($genericTypeToResolve), $reflectionProperty);
47
            }
48
49
            return "array<" . implode(",", $resolvedTypes) . ">";
50
        }elseif (false !== strpos($typeHint, '[]')) {
51
            $typeHint = rtrim($typeHint, '[]');
52
            $typeHint = $this->resolveType($typeHint, $reflectionProperty);
53
54
            return 'array<' . $typeHint . '>';
55
        }
56
57
        return $this->resolveType($typeHint, $reflectionProperty);
58
    }
59
60
    private function expandClassNameUsingUseStatements(string $typeHint, \ReflectionClass $declaringClass, \ReflectionProperty $reflectionProperty): string
61
    {
62
        if (class_exists($typeHint)) {
63
            return $typeHint;
64
        }
65
        $expandedClassName = $declaringClass->getNamespaceName() . '\\' . $typeHint;
66
        if (class_exists($expandedClassName)) {
67
            return $expandedClassName;
68
        }
69
70
        $classContents = file_get_contents($declaringClass->getFileName());
71
        $foundUseStatements = $this->gatherGroupUseStatements($classContents);
72
        $foundUseStatements = array_merge($this->gatherSingleUseStatements($classContents), $foundUseStatements);
73
74
        foreach ($foundUseStatements as $statementClassName) {
75
            if ($alias = explode('as', $statementClassName)) {
76
                if (array_key_exists(1, $alias) && trim($alias[1]) === $typeHint) {
77
                    return trim($alias[0]);
78
                }
79
            }
80
81
            if ($this->endsWith($statementClassName, $typeHint)) {
82
                return $statementClassName;
83
            }
84
        }
85
86
        throw new \InvalidArgumentException(sprintf("Can't use incorrect type %s for collection in %s:%s", $typeHint, $declaringClass->getName(), $reflectionProperty->getName()));
87
    }
88
89
    private function endsWith(string $statementClassToCheck, string $typeHintToSearchFor): bool
90
    {
91
        $typeHintToSearchFor = '\\' . $typeHintToSearchFor;
92
93
        return substr($statementClassToCheck, -strlen($typeHintToSearchFor)) === $typeHintToSearchFor;
94
    }
95
96
    private function isPrimitiveType(string $type): bool
97
    {
98
        return in_array($type, ['int', 'float', 'bool', 'string']);
99
    }
100
101
    private function hasGlobalNamespacePrefix(string $typeHint): bool
102
    {
103
        return self::GLOBAL_NAMESPACE_PREFIX === $typeHint[0];
104
    }
105
106
    private function gatherGroupUseStatements(string $classContents): array
107
    {
108
        $foundUseStatements = [];
109
        preg_match_all(self::GROUP_USE_STATEMENTS_REGEX, $classContents, $foundGroupUseStatements);
110
        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...
111
            foreach (explode(',', $foundGroupUseStatements[2][$useStatementIndex]) as $singleUseStatement) {
112
                $foundUseStatements[] = trim($foundGroupUseStatements[1][$useStatementIndex]) . trim($singleUseStatement);
113
            }
114
        }
115
116
        return $foundUseStatements;
117
    }
118
119
    private function gatherSingleUseStatements(string $classContents): array
120
    {
121
        $foundUseStatements = [];
122
        preg_match_all(self::SINGLE_USE_STATEMENTS_REGEX, $classContents, $foundSingleUseStatements);
123
        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...
124
            $foundUseStatements[] = trim($foundSingleUseStatements[1][$useStatementIndex]);
125
        }
126
127
        return $foundUseStatements;
128
    }
129
130
    private function getDeclaringClassOrTrait(\ReflectionProperty $reflectionProperty): \ReflectionClass
131
    {
132
        foreach ($reflectionProperty->getDeclaringClass()->getTraits() as $trait) {
133
            foreach ($trait->getProperties() as $traitProperty) {
134
                if ($traitProperty->getName() === $reflectionProperty->getName()) {
135
                    return $this->getDeclaringClassOrTrait($traitProperty);
136
                }
137
            }
138
        }
139
140
        return $reflectionProperty->getDeclaringClass();
141
    }
142
143
    private function resolveType(string $typeHint, \ReflectionProperty $reflectionProperty): string
144
    {
145
        if (!$this->hasGlobalNamespacePrefix($typeHint) && !$this->isPrimitiveType($typeHint)) {
146
            $typeHint = $this->expandClassNameUsingUseStatements($typeHint, $this->getDeclaringClassOrTrait($reflectionProperty), $reflectionProperty);
147
        }
148
149
        return ltrim($typeHint, '\\');
150
    }
151
}
152