Passed
Push — master ( 9581df...0efa8f )
by Arthur
05:51
created

TypeResolver   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 157
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 84
dl 0
loc 157
c 0
b 0
f 0
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveTypesFromVarDoc() 0 24 4
A classExists() 0 3 2
A getClassFromFilename() 0 8 1
A getNamespaceFromFilename() 0 10 1
A resolve() 0 31 3
A checkTypeExistance() 0 12 4
A __construct() 0 3 1
A getFqnFromFileName() 0 3 1
1
<?php
2
3
namespace Larapie\DataTransferObject\Resolvers;
4
5
use Larapie\DataTransferObject\Exceptions\TypeDoesNotExistException;
6
use phpDocumentor\Reflection\Types\Array_;
7
use phpDocumentor\Reflection\Types\Boolean;
8
use phpDocumentor\Reflection\Types\Callable_;
9
use phpDocumentor\Reflection\Types\Compound;
10
use phpDocumentor\Reflection\Types\Float_;
11
use phpDocumentor\Reflection\Types\Integer;
12
use phpDocumentor\Reflection\Types\Iterable_;
13
use phpDocumentor\Reflection\Types\Mixed_;
14
use phpDocumentor\Reflection\Types\Null_;
15
use phpDocumentor\Reflection\Types\Object_;
16
use phpDocumentor\Reflection\Types\Parent_;
17
use phpDocumentor\Reflection\Types\Resource_;
18
use phpDocumentor\Reflection\Types\Scalar;
19
use phpDocumentor\Reflection\Types\Self_;
20
use phpDocumentor\Reflection\Types\Static_;
21
use phpDocumentor\Reflection\Types\String_;
22
use phpDocumentor\Reflection\Types\Void_;
23
use ReflectionProperty;
24
use Larapie\DataTransferObject\PropertyType;
25
26
class TypeResolver
27
{
28
    /**
29
     * @var ReflectionProperty
30
     */
31
    protected $reflection;
32
33
    /** @var string[] List of recognized keywords and unto which Value Object they map */
34
    private static $typeKeywords = array(
35
        'string',
36
        'int',
37
        'integer',
38
        'bool',
39
        'boolean',
40
        'float',
41
        'double',
42
        'object',
43
        'mixed',
44
        'array',
45
        'resource',
46
        'void',
47
        'null',
48
        'scalar',
49
        'callback',
50
        'callable',
51
        'false',
52
        'true',
53
        'self',
54
        '$this',
55
        'static',
56
        'parent',
57
        'iterable'
58
    );
59
60
    /**
61
     * TypeResolver constructor.
62
     * @param ReflectionProperty $reflection
63
     */
64
    public function __construct(ReflectionProperty $reflection)
65
    {
66
        $this->reflection = $reflection;
67
    }
68
69
    /**
70
     * @return PropertyType
71
     */
72
    public function resolve(): PropertyType
73
    {
74
        $type = new PropertyType();
75
76
        $docComment = $this->reflection->getDocComment();
77
78
        if (!$docComment) {
79
            $type->setNullable(true);
80
81
            return $type;
82
        }
83
84
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', $docComment, $matches);
0 ignored issues
show
Bug introduced by
It seems like $docComment can also be of type true; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

84
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', /** @scrutinizer ignore-type */ $docComment, $matches);
Loading history...
85
86
        if (!count($matches)) {
87
            $type->setNullable(true);
88
89
            return $type;
90
        }
91
92
        $varDocComment = end($matches);
93
94
95
        $types = $this->resolveTypesFromVarDoc($varDocComment);
96
97
        $type->setTypes($types);
98
        $type->setArrayTypes(str_replace('[]', '', $types));
99
        $type->setHasType(true);
100
        $type->setNullable(strpos($varDocComment, 'null') !== false);
101
102
        return $type;
103
    }
104
105
    protected function resolveTypesFromVarDoc(string $varDocComment)
106
    {
107
        if ($this->reflection->getDeclaringClass()->isAnonymous()) {
108
            $filename = $this->reflection->getDeclaringClass()->getFileName();
109
110
            $contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory();
111
            $context = $contextFactory->createForNamespace($this->getNamespaceFromFilename($filename), file_get_contents($filename));
112
        } else {
113
            $contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory();
114
            $context = $contextFactory->createFromReflector($this->reflection);
115
        }
116
117
        $resolver = new \phpDocumentor\Reflection\TypeResolver();
118
        $resolvedTypes = $resolver->resolve($varDocComment, $context);
119
        $types = [];
120
        if ($resolvedTypes instanceof Compound) {
121
            foreach ($resolvedTypes as $type) {
122
                $types[] = $type->__toString();
123
            }
124
        } else {
125
            $types = [$resolvedTypes->__toString()];
126
        }
127
        $this->checkTypeExistance($types);
128
        return $types;
129
    }
130
131
    protected function getClassFromFilename($fileName)
132
    {
133
        $directoriesAndFilename = explode('/', $fileName);
134
        $fileName = array_pop($directoriesAndFilename);
135
        $nameAndExtension = explode('.', $fileName);
136
        $className = array_shift($nameAndExtension);
137
138
        return $className;
139
    }
140
141
    protected function getNamespaceFromFilename($filename)
142
    {
143
        $lines = file($filename);
144
        $namespace = preg_grep('/^namespace /', $lines);
0 ignored issues
show
Bug introduced by
It seems like $lines can also be of type false; however, parameter $input of preg_grep() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
        $namespace = preg_grep('/^namespace /', /** @scrutinizer ignore-type */ $lines);
Loading history...
145
        $namespaceLine = array_shift($namespace);
146
        $match = array();
147
        preg_match('/^namespace (.*);$/', $namespaceLine, $match);
148
        $fullNamespace = array_pop($match);
149
150
        return $fullNamespace;
151
    }
152
153
    protected function getFqnFromFileName($fileName)
154
    {
155
        return $this->getNamespaceFromFilename($fileName) . '\\' . $this->getClassFromFilename($fileName);
156
    }
157
158
    protected function checkTypeExistance(array $types)
159
    {
160
        foreach ($types as $type) {
161
            $type = str_replace("[]","",$type);
162
            if (!in_array($type, self::$typeKeywords)){
163
                if(!$this->classExists($type))
164
                    throw new TypeDoesNotExistException(sprintf(
165
                        'The @var annotation on %s::%s contains a non existent class "%s". '
166
                        . 'Did you maybe forget to add a "use" statement for this annotation?',
167
                        $this->reflection->getDeclaringClass()->getName(),
168
                        $this->reflection->getName(),
169
                        $type
170
                    ));
171
            }
172
173
        }
174
    }
175
176
    /**
177
     * @param string $class
178
     * @return bool
179
     */
180
    private function classExists($class)
181
    {
182
        return class_exists($class) || interface_exists($class);
183
    }
184
185
}
186