Completed
Pull Request — master (#1214)
by
unknown
21:22 queued 06:23
created

DocBlockTypeResolver::gatherSingleUseStatements()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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