PhpDocReader::propertyClassParameters()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 0
loc 11
rs 9.4285
cc 3
eloc 7
nc 3
nop 2
1
<?php
2
/**
3
 * This file is part of the Stack package.
4
 *
5
 * (c) Andrzej Kostrzewa <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Stack\DI\Definition\Source;
12
13
use Stack\DI\Definition\ObjectDefinition;
14
use Stack\DI\Exception\AnnotationException;
15
16
/**
17
 * PhpDoc reader.
18
 *
19
 * @author Andrzej Kostrzewa <[email protected]>
20
 */
21
class PhpDocReader
22
{
23
    /**
24
     * @var array
25
     */
26
    protected static $ignoredTypes = [
27
        'bool',
28
        'boolean',
29
        'string',
30
        'int',
31
        'integer',
32
        'float',
33
        'double',
34
        'array',
35
        'object',
36
        'callable',
37
        'resource',
38
        '',
39
    ];
40
41
    /**
42
     * @var string
43
     */
44
    private $namespace;
45
46
    /**
47
     * Parse the docblock of the property to get the parameters of the param annotation.
48
     *
49
     * @param \ReflectionMethod $method
50
     *
51
     * @return array|null
52
     */
53
    public function getMethodParameters(\ReflectionMethod $method)
54
    {
55
        $methodComment = $method->getDocComment();
56
        if (!preg_match_all('/@param\s+([^\s\*\/]+)/', $methodComment, $matches)) {
57
            return;
58
        }
59
60
        $classNames = end($matches);
61
62
        if (!is_array($classNames)) {
63
            return;
64
        }
65
66
        $parameters = [];
67
        foreach ($classNames as $type) {
68
            $values = explode('(', $type);
69
            if (in_array($values[0], static::$ignoredTypes)) {
70
                $value = end($values);
71
                $value = trim($value, ') ');
72
                $type  = static::parseValue($value);
73
            }
74
            $parameters[] = $type;
75
        }
76
77
        return $parameters;
78
    }
79
80
    /**
81
     * Parse the docblock of the property to get the class of the var annotation.
82
     *
83
     * @param \ReflectionProperty $property
84
     *
85
     * @throws AnnotationException Non exists class.
86
     *
87
     * @return null|ObjectDefinition
88
     */
89
    public function getPropertyClass(\ReflectionProperty $property)
90
    {
91
        $propertyComment = $property->getDocComment();
92
        if (!preg_match('/@var\s+([^\s\(\*\/]+)/', $propertyComment, $matches)) {
93
            return;
94
        }
95
96
        $className = end($matches);
97
98
        if (!is_string($className) || in_array($className, static::$ignoredTypes)) {
99
            return;
100
        }
101
102
        $classWithNamespace = $className;
103
        if ($this->namespaceExists($classWithNamespace) === false) {
104
            $classWithNamespace = $this->namespace.'\\'.$className;
105
        }
106
107
        if (!$this->classExists($classWithNamespace)) {
108
            $declaringClass = $property->getDeclaringClass();
109
            throw new AnnotationException(sprintf(
110
                'The @var annotation on %s::%s contains a non existent class "%s"',
111
                $declaringClass->name,
112
                $property->getName(),
113
                $className
114
            ));
115
        }
116
117
        $createNewObject = function ($propertyComment, $className, $classWithNamespace) {
118
            $classParameters = $this->propertyClassParameters($propertyComment, $className);
119
            if (is_array($classParameters)) {
120
                $values = [];
121
                foreach ($classParameters as $value) {
122
                    $values[] = static::parseValue($value);
123
                }
124
125
                $object = new ObjectDefinition($classWithNamespace, $className);
126
                $object->setConstructorInjection($values);
127
128
                return $object;
129
            }
130
131
            return new $classWithNamespace();
132
        };
133
134
        return $createNewObject($propertyComment, $className, $classWithNamespace);
135
    }
136
137
    /**
138
     * @param string|false $className
139
     *
140
     * @return bool
141
     */
142
    private function classExists($className)
143
    {
144
        return class_exists($className) || interface_exists($className);
145
    }
146
147
    /**
148
     * @param string|false $className
149
     *
150
     * @return int
151
     */
152
    private function namespaceExists($className)
153
    {
154
        return strpos($className, $this->namespace);
155
    }
156
157
    /**
158
     * Parse value by type and return.
159
     *
160
     * @param $value
161
     *
162
     * @return string
163
     */
164
    protected static function parseValue($value)
165
    {
166
        $value = trim($value, ', ');
167
168
        $isNumberOrBool = function (&$value) {
169
            if (is_numeric($value)) {
170
                $value = (float) $value;
171
172
                if ((float) $value == (int) $value) {
173
                    $value = (int) $value;
174
                }
175
176
                return true;
177
            }
178
179
            $isBool = function (&$value) {
180
                if (strtolower($value) == 'true') {
181
                    $value = true;
182
183
                    return true;
184
                }
185
186
                if (strtolower($value) == 'false') {
187
                    $value = false;
188
189
                    return true;
190
                }
191
192
                return false;
193
            };
194
195
            return $isBool($value);
196
        };
197
198
        $isArrayOrOther = function (&$value) {
199
            if (substr($value, 0, 1) === '[' && substr($value, -1) === ']') {
200
                $valuesArray = explode(',', substr($value, 1, -1));
201
                $value       = [];
202
                foreach ($valuesArray as $val) {
203
                    $value[] = static::parseValue($val);
204
                }
205
206
                return true;
207
            }
208
209
            if (substr($value, 0, 1) == '"' && substr($value, -1) == '"' ||
210
                substr($value, 0, 1) == '\'' && substr($value, -1) == '\'') {
211
                $value = substr($value, 1, -1);
212
                $value = static::parseValue($value);
213
214
                return true;
215
            }
216
217
            return false;
218
        };
219
220
        if ($isArrayOrOther($value)) {
221
            return $value;
222
        }
223
224
        if ($isNumberOrBool($value)) {
225
            return $value;
226
        }
227
228
        return $value;
229
    }
230
231
    /**
232
     * Get property class parameters.
233
     *
234
     * @param string       $property
235
     * @param string|false $className
236
     *
237
     * @return string[]|false|null
238
     */
239
    private function propertyClassParameters($property, $className)
240
    {
241
        $classNamePosition = strpos($property, $className);
242
        if ($classNamePosition !== false) {
243
            $classNameLength = mb_strlen($className);
244
            $property        = ltrim(substr($property, $classNamePosition + $classNameLength));
245
            if (preg_match_all('/([\w,$\[\]\'\"]+)/', $property, $matches)) {
246
                return end($matches);
247
            }
248
        }
249
    }
250
251
    /**
252
     * Set default namespace.
253
     *
254
     * @param $namespace
255
     */
256
    public function setNamespace($namespace)
257
    {
258
        $this->namespace = $namespace;
259
    }
260
}
261