Completed
Push — master ( 9d7f2b...8fe48d )
by Lars
24:45
created

Property::parseDocTypeObject()   C

Complexity

Conditions 14
Paths 14

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 21.257

Importance

Changes 0
Metric Value
cc 14
nc 14
nop 1
dl 0
loc 57
ccs 20
cts 30
cp 0.6667
crap 21.257
rs 6.2666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Arrayy;
4
5
use phpDocumentor\Reflection\Type;
6
7
/**
8
 * Class Property
9
 *
10
 * inspired by https://github.com/spatie/value-object
11
 */
12
class Property extends \ReflectionProperty
13
{
14
  /**
15
   * @var array
16
   */
17
  protected static $typeMapping = [
18
      'int'  => 'integer',
19
      'bool' => 'boolean',
20
  ];
21
22
  /**
23
   * @var Arrayy
24
   */
25
  protected $valueObject;
26
27
  /**
28
   * @var bool
29
   */
30
  protected $hasTypeDeclaration = false;
31
32
  /**
33
   * @var bool
34
   */
35
  protected $isNullable = false;
36
37
  /**
38
   * @var bool
39
   */
40
  protected $isInitialised = false;
41
42
  /**
43
   * @var array
44
   */
45
  protected $types = [];
46
47
  /**
48
   * Property constructor.
49
   *
50
   * @param Arrayy              $valueObject
51
   * @param null|\ReflectionProperty $reflectionProperty
52
   * @param object              $fakeObject
53
   *
54
   * @throws \ReflectionException
55
   */
56 2
  public function __construct(Arrayy $valueObject, $reflectionProperty, $fakeObject = null)
57
  {
58 2
    parent::__construct($fakeObject, $reflectionProperty->getName());
0 ignored issues
show
Bug introduced by
It seems like $reflectionProperty is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
59
60 2
    $this->valueObject = $valueObject;
61 2
  }
62
63
  /**
64
   * @param string $type
65
   * @param mixed  $value
66
   *
67
   * @return bool
68
   */
69 6
  protected function assertTypeEquals(string $type, $value): bool
70
  {
71 6
    if (strpos($type, '[]') !== false) {
72
      return $this->isValidGenericCollection($type, $value);
73
    }
74
75 6
    if ($type === 'mixed' && $value !== null) {
76
      return true;
77
    }
78
79 6
    return $value instanceof $type
80
           ||
81 6
           \gettype($value) === (self::$typeMapping[$type] ?? $type);
82
  }
83
84 2
  public static function fromPhpDocumentorProperty(Arrayy $valueObject, \phpDocumentor\Reflection\DocBlock\Tags\Property $phpDocumentorReflectionProperty): self
85
  {
86 2
    $tmpProperty = $phpDocumentorReflectionProperty->getVariableName();
87 2
    $tmpObject = new \stdClass();
88 2
    $tmpObject->{$tmpProperty} = null;
89
90 2
    $tmpReflection = new self($valueObject, new \ReflectionProperty($tmpObject, $tmpProperty), $tmpObject);
91
92 2
    $type = $phpDocumentorReflectionProperty->getType();
93
94 2
    if ($type) {
95 2
      $tmpReflection->hasTypeDeclaration = true;
96
97 2
      $docTypes = self::parseDocTypeObject($type);
98 2
      if (\is_array($docTypes) === true) {
99 2
        foreach ($docTypes as $docType) {
100 2
          $tmpReflection->types[] = $docType;
101
        }
102
      } else {
103 2
        $tmpReflection->types[] = $docTypes;
104
      }
105
106 2
      if (\in_array('null', $tmpReflection->types, true)) {
107 2
        $tmpReflection->isNullable = true;
108
      }
109
    }
110
111 2
    return $tmpReflection;
112
  }
113
114
  /**
115
   * @param Type $type
116
   *
117
   * @return string[]|string
118
   */
119 2
  protected static function parseDocTypeObject(Type $type)
120
  {
121 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Object_) {
122 1
      return (string)$type->getFqsen();
123
    }
124
125 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Array_) {
126
      $value = $type->getValueType();
127
      if ($value instanceof \phpDocumentor\Reflection\Types\Mixed_) {
128
        return 'mixed';
129
      }
130
131
      return 'array';
132
    }
133
134 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Null_) {
135 2
      return 'null';
136
    }
137
138 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Mixed_) {
139
      return 'mixed';
140
    }
141
142 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Scalar) {
143
      return 'string|int|float|bool';
144
    }
145
146 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Boolean) {
147
      return 'bool';
148
    }
149
150 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Callable_) {
151
      return 'callable';
152
    }
153
154 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Compound) {
155 2
      $types = [];
156 2
      foreach ($type as $subType) {
157 2
        $types[] = self::parseDocTypeObject($subType);
158
      }
159 2
      return $types;
160
    }
161
162 2
    if ($type instanceof \phpDocumentor\Reflection\Types\Float_) {
163
      return 'float';
164
    }
165
166 2
    if ($type instanceof \phpDocumentor\Reflection\Types\String_) {
167 2
      return 'string';
168
    }
169
170 1
    if ($type instanceof \phpDocumentor\Reflection\Types\Integer) {
171 1
      return 'int';
172
    }
173
174
    throw new \Exception('Unhandled PhpDoc type: ' . get_class($type));
175
  }
176
177 2
  public function getTypes(): array
178
  {
179 2
    return $this->types;
180
  }
181
182
  protected function isValidGenericCollection(string $type, $collection): bool
183
  {
184
    if (!\is_array($collection)) {
185
      return false;
186
    }
187
188
    $valueType = \str_replace('[]', '', $type);
189
190
    foreach ($collection as $value) {
191
      if (!$this->assertTypeEquals($valueType, $value)) {
192
        return false;
193
      }
194
    }
195
196
    return true;
197
  }
198
199
  /**
200
   * @param mixed $value
201
   *
202
   * @return bool
203
   */
204 6
  protected function isValidType($value): bool
205
  {
206 6
    if (!$this->hasTypeDeclaration) {
207
      return true;
208
    }
209
210 6
    if ($this->isNullable && $value === null) {
211 3
      return true;
212
    }
213
214 6
    foreach ($this->types as $currentType) {
215 6
      $isValidType = $this->assertTypeEquals($currentType, $value);
216
217 6
      if ($isValidType) {
218 6
        return true;
219
      }
220
    }
221
222 2
    return false;
223
  }
224
225 6
  public function set($value)
226
  {
227 6
    if (!$this->isValidType($value)) {
228 2
      $this->throwInvalidType($value);
229
    }
230
231 5
    $this->isInitialised = true;
232
233 5
    $this->valueObject->{$this->getName()} = $value;
234 5
  }
235
236
  /**
237
   * @param mixed $value
238
   */
239 2
  protected function throwInvalidType($value)
240
  {
241 2
    $type = \gettype($value);
242
243 2
    if ($type === 'NULL') {
244
      $value = 'null';
245 2
    } elseif ($type === 'object') {
246 1
      $value = \get_class($value);
247 1
    } elseif ($type === 'array') {
248
      $value = 'array';
249
    }
250
251 2
    $expectedTypes = \implode('|', $this->getTypes());
252
253 2
    throw new \InvalidArgumentException("Invalid type: expected {$this->name} to be of type {{$expectedTypes}}, instead got value `{$value}` with type {{$type}}.");
254
  }
255
}
256