TypeParser   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 98.75%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 12
dl 0
loc 219
ccs 79
cts 80
cp 0.9875
rs 9.3999
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A parseFromType() 0 9 2
A parseObjectName() 0 4 1
B parse() 0 29 5
B checkCollectionClose() 0 19 5
A currentValueToType() 0 10 2
C scalarToType() 0 24 11
A resolveName() 0 21 3
A checkContext() 0 19 3
1
<?php
2
/**
3
 * This file is part of the Composite Utils package.
4
 *
5
 * (c) Emily Shepherd <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the
8
 * LICENSE.md file that was distributed with this source code.
9
 *
10
 * @package spaark/composite-utils
11
 * @author Emily Shepherd <[email protected]>
12
 * @license MIT
13
 */
14
15
namespace Spaark\CompositeUtils\Factory\Reflection;
16
17
use Spaark\CompositeUtils\Model\Reflection\ReflectionComposite;
18
use Spaark\CompositeUtils\Model\Reflection\Type\BooleanType;
19
use Spaark\CompositeUtils\Model\Reflection\Type\CollectionType;
20
use Spaark\CompositeUtils\Model\Reflection\Type\IntegerType;
21
use Spaark\CompositeUtils\Model\Reflection\Type\MixedType;
22
use Spaark\CompositeUtils\Model\Reflection\Type\ObjectType;
23
use Spaark\CompositeUtils\Model\Reflection\Type\StringType;
24
use Spaark\CompositeUtils\Model\Reflection\Type\AbstractType;
25
use Spaark\CompositeUtils\Model\Reflection\Type\FloatType;
26
use Spaark\CompositeUtils\Model\Reflection\Type\NullType;
27
use Spaark\CompositeUtils\Service\RawPropertyAccessor;
28
29
/**
30
 * Parses a type string
31
 */
32
class TypeParser
33
{
34
    /**
35
     * @var ReflectionComposite
36
     */
37
    protected $context;
38
39
    /**
40
     * @var boolean
41
     */
42
    protected $nullable;
43
44
    /**
45
     * @var boolean
46
     */
47
    protected $collection;
48
49
    /**
50
     * @var string
51
     */
52
    protected $currentValue;
53
54
    /**
55
     * Constructs the TypeParser with an optional context for
56
     * interpreting classnames
57
     *
58
     * @param ReflectionComposite $context
59
     */
60 44
    public function __construct(ReflectionComposite $context = null)
61
    {
62 44
        $this->context = $context;
63 44
    }
64
65
    /**
66
     * Accepts any value and returns its type
67
     *
68
     * @param mixed $var
69
     * @return AbstractType
70
     */
71 17
    public function parseFromType($var) : AbstractType
72
    {
73 17
        if (is_object($var))
74
        {
75 6
            return $this->parseObjectName($var);
76
        }
77
        
78 14
        return $this->scalarToType(gettype($var));
79
    }
80
81
    /**
82
     * Accepts an object and returns its type
83
     *
84
     * @param mixed $var
85
     * @return ObjectType
86
     */
87 6
    public function parseObjectName($var) : ObjectType
88
    {
89 6
        return $this->parse(get_class($var));
90
    }
91
92
    /**
93
     * Sets the property's type by parsing the @type annotation
94
     *
95
     * @param string $value The value string to parse
96
     * @return AbstractType The type of this item
97
     */
98 37
    public function parse(string $value) : AbstractType
99
    {
100 37
        $this->nullable = false;
101 37
        $this->collection = false;
102 37
        $stack = new \SplStack();
0 ignored issues
show
Unused Code introduced by
$stack is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
103 37
        $this->currentValue = '';
104
105 37
        for ($i = 0; $i < strlen($value); $i++)
106
        {
107 37
            $char = $value{$i};
108
            switch ($char)
109
            {
110 37
                case '?':
111 29
                    $this->nullable = true;
112 29
                    break;
113 37
                case '[':
114 13
                    $this->collection = true;
115 13
                    $this->checkCollectionClose($value, $i);
116 10
                    $i++;
117 10
                    break;
118 36
                case ' ':
119
                    break;
120
                default:
121 36
                    $this->currentValue .= $char;
122
            }
123
        }
124
125 34
        return $this->resolveName();
126
    }
127
128
    /**
129
     * Checks that the given value at the given offset closes a
130
     * collection block correctly
131
     *
132
     * @param string $value
133
     * @param int $i
134
     * @return void
135
     */
136 13
    protected function checkCollectionClose(string $value, int $i)
137
        : void
138
    {
139 13
        if ($i + 1 === strlen($value))
140
        {
141 1
            throw new \Exception('Unexpected EOF');
142
        }
143 12
        elseif ($value{$i + 1} !== ']')
144
        {
145 1
            throw new \Exception('[ must be followed by ]');
146
        }
147 11
        elseif ($i + 2 !== strlen($value))
148
        {
149 1
            if (!in_array($value{$i + 2}, ['>',',']))
150
            {
151 1
                throw new \Exception('Unexpected char after collection');
152
            }
153
        }
154 10
    }
155
156
    /**
157
     * Interprets the currentValue and converts it to an AbstractType
158
     *
159
     * @param AbstractType
160
     */
161 34
    protected function currentValueToType() : AbstractType
162
    {
163 34
        if ($var = $this->scalarToType($this->currentValue))
164
        {
165 30
            return $var;
166
        }
167
168 25
        $context = $this->checkContext();
169 25
        return new ObjectType($context);
170
    }
171
172 41
    public function scalarToType($var) : ?AbstractType
173
    {
174 41
        switch (strtolower($var))
175
        {
176 41
            case 'string':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
177 24
                return new StringType();
178 39
            case 'int':
179 38
            case 'integer':
180 8
                return new IntegerType();
181 35
            case 'bool':
182 34
            case 'boolean':
183 26
                return new BooleanType();
184 31
            case 'float':
185 30
            case 'double':
186 2
                return new FloatType();
187 29
            case 'mixed':
188 28
            case '':
189 23
                return new MixedType();
190 27
            case 'null':
191 4
                return new NullType();
192
            default:
193 25
                return null;
194
        }
195
    }
196
197
    /**
198
     * Resolves the currentValue to an AbstractType, setting it up as
199
     * nullable or a collection as appropriate
200
     *
201
     * @return AbstractType
202
     */
203 34
    protected function resolveName() : AbstractType
204
    {
205 34
        $class = $this->currentValueToType();
206
207 34
        if ($this->nullable)
208
        {
209 29
            (new RawPropertyAccessor($class))
210 29
                ->setRawValue('nullable', true);
211
        }
212
213 34
        if ($this->collection)
214
        {
215 10
            $class = new CollectionType($class);
216
        }
217
218 34
        $this->currentValue = '';
219 34
        $this->nullable = false;
220 34
        $this->collection = false;
221
222 34
        return $class;
223
    }
224
225
    /**
226
     * Checks if the currentValue means something in the TypeParser's
227
     * context
228
     *
229
     * @return string The fully resolved classname
230
     */
231 25
    protected function checkContext()
232
    {
233 25
        if (!$this->context)
234
        {
235 24
            return $this->currentValue;
236
        }
237
238 22
        $useStatements = $this->context->namespace->useStatements;
239
240 22
        if ($useStatements->containsKey($this->currentValue))
0 ignored issues
show
Documentation introduced by
$this->currentValue is of type string, but the function expects a object<Spaark\CompositeU...Collection\Map\KeyType>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
241
        {
242 21
            return $useStatements[$this->currentValue]->classname;
243
        }
244
        else
245
        {
246 21
            return $this->context->namespace->namespace
247 21
                . '\\' . $this->currentValue;
248
        }
249
    }
250
}
251