Meta::__construct()   F
last analyzed

Complexity

Conditions 25
Paths 10925

Size

Total Lines 157

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 63
CRAP Score 25.4115

Importance

Changes 0
Metric Value
dl 0
loc 157
ccs 63
cts 69
cp 0.913
rs 0
c 0
b 0
f 0
cc 25
nc 10925
nop 2
crap 25.4115

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
/**
4
 * This software package is licensed under AGPL, Commercial license.
5
 *
6
 * @package maslosoft/addendum
7
 * @licence AGPL, Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]> (Meta container, further improvements, bugfixes)
9
 * @copyright Copyright (c) Maslosoft (Meta container, further improvements, bugfixes)
10
 * @copyright Copyright (c) Jan Suchal (Original version, builder, parser)
11
 * @link https://maslosoft.com/addendum/ - maslosoft addendum
12
 * @link https://code.google.com/p/addendum/ - original addendum project
13
 */
14
15
namespace Maslosoft\Addendum\Collections;
16
17
use Exception;
18
use Maslosoft\Addendum\Addendum;
19
use Maslosoft\Addendum\Cache\FlyCache;
20
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
21
use Maslosoft\Addendum\Interfaces\MetaAnnotationInterface;
22
use Maslosoft\Addendum\Options\MetaOptions;
23
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass;
24
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedMethod;
25
use Maslosoft\Addendum\Reflection\ReflectionAnnotatedProperty;
26
use Maslosoft\Addendum\Utilities\IgnoredChecker;
27
use ReflectionMethod;
28
use ReflectionProperty;
29
30
/**
31
 * Metadata container
32
 * @property MetaProperty $field
33
 * @author Piotr
34
 */
35
class Meta
36
{
37
38
	const Type = 1;
39
	const Field = 2;
40
	const Method = 3;
41
42
	/**
43
	 * Container for type metadata
44
	 * @var MetaType
45
	 */
46
	protected $_type = null;
47
48
	/**
49
	 * Array of containers for property metadata
50
	 * @var MetaProperty[]
51
	 */
52
	protected $_fields = [];
53
54
	/**
55
	 * Array of containers for method metadata
56
	 * @var MetaMethod[]
57
	 */
58
	protected $_methods = [];
59
	private $_annotations = [];
60
61
	/**
62
	 * On-hand cache
63
	 * @var Meta[]
64
	 */
65
	private static $c = [];
66
67
	/**
68
	 * Flag to clear local cache if namespace was dynamically added
69
	 * @var bool
70
	 */
71
	public static $addNs = false;
72
73
	/**
74
	 * @param string|object|AnnotatedInterface $model
75
	 * @param MetaOptions $options
76
	 * @throws Exception
77
	 */
78 37
	protected function __construct($model = null, MetaOptions $options = null)
79
	{
80
		// For internal use
81 37
		if (null === $model)
82
		{
83
			return;
84
		}
85 37
		if (null === $options)
86
		{
87 33
			$options = new MetaOptions();
88
		}
89
90
		// TODO Use adapter here
91
		// ?TODO Abstract from component meta, so other kinds of meta extractors could be used
92
		// For example, for development annotation based extractor could be used, which could compile
93
		// Metadata to arrays, and for production environment, compiled arrays could be used
94 37
		$annotations = [];
95 37
		$mes = [];
96
97
		// Get reflection data
98 37
		$ad = Addendum::fly($options->instanceId);
99 37
		$ad->addNamespaces($options->namespaces);
100 37
		$info = $ad->annotate($model);
101
102
		// Class name of working component
103 32
		$className = is_object($model) ? get_class($model) : $model;
104
105 32
		if (!$info instanceof ReflectionAnnotatedClass)
106
		{
107
			throw new Exception(sprintf('Could not annotate `%s`', $className));
108
		}
109
110 32
		$properties = $info->getProperties(ReflectionProperty::IS_PUBLIC);
111 30
		$defaults = $info->getDefaultProperties();
112
113 30
		$methods = $info->getMethods(ReflectionMethod::IS_PUBLIC);
114
115
		// Setup type
116
		/**
117
		 * @todo Fix it: $this->_meta->{$this->name}->...
118
		 * ^-- _meta __get and __set is only for fields AND use _fields field,
119
		 * for class OR methods it should use different fields
120
		 * for class should be _main
121
		 * for methods should be _methods
122
		 * __get and __set should distinguish it somehow... - maybe by field type EComponentMetaProperty for fields etc.
123
		 * Currently disabled
124
		 * OR add function to Annotation to setEntity, which should point to _field, _main or _method?
125
		 */
126
		// Setup class annotations
127 30
		$this->_type = new $options->typeClass($info);
128 30
		foreach ($info->getAllAnnotations() as $annotation)
129
		{
130 14
			if (!$annotation instanceof MetaAnnotationInterface)
131
			{
132
				continue;
133
			}
134 14
			$annotation->setName($info->name);
135 14
			$annotation->setEntity($this->_type);
136 14
			$annotation->setMeta($this);
137 14
			$annotation->init();
138 14
			$annotations[] = $annotation;
139
		}
140
		// Setup methods
141 30
		foreach ($methods as $method)
142
		{
143 6
			if (!$method instanceof ReflectionAnnotatedMethod)
144
			{
145
				throw new Exception(sprintf('Could not annotate `%s::%s()`', $className, $method->name));
146
			}
147
148
			// Ignore magic methods
149 6
			if (preg_match('~^__~', $method->name))
150
			{
151
				continue;
152
			}
153
154
			// Ignore @Ignored marked methods
155 6
			if (IgnoredChecker::check($method))
156
			{
157 3
				continue;
158
			}
159
160
			// Create method holder class based on options
161 6
			$methodMeta = new $options->methodClass($method);
162 6
			foreach ($method->getAllAnnotations() as $annotation)
163
			{
164 6
				if (!$annotation instanceof MetaAnnotationInterface)
165
				{
166 3
					continue;
167
				}
168
169 3
				$annotation->setName($method->name);
170 3
				$annotation->setEntity($methodMeta);
171 3
				$annotation->setMeta($this);
172 3
				$annotation->init();
173 3
				$annotations[] = $annotation;
174
			}
175
176
			// Put it to metadata object
177 6
			$this->_methods[$method->name] = $methodMeta;
178
179
			// Get getters and setters for properties setup
180 6
			if (preg_match('~^[gs]et~', $method->name) && !$method->isStatic())
181
			{
182 6
				$mes[$method->name] = true;
183
			}
184
		}
185
186
		// Setup properties
187 30
		foreach ($properties as $property)
188
		{
189 17
			if (!$property instanceof ReflectionAnnotatedProperty)
190
			{
191
				throw new Exception(sprintf('Could not annotate `%s::%s`', $className, $property->name));
192
			}
193
194 17
			if (IgnoredChecker::check($property))
195
			{
196 3
				continue;
197
			}
198 17
			$name = $property->name;
199
			/* @var $property ReflectionAnnotatedProperty */
200 17
			$field = new $options->propertyClass($property);
201
202
			// Access options
203 17
			$field->callGet = isset($mes[$field->methodGet]) && $mes[$field->methodGet];
204 17
			$field->callSet = isset($mes[$field->methodSet]) && $mes[$field->methodSet];
205 17
			$field->direct = !($field->callGet || $field->callSet);
206 17
			$field->isStatic = $property->isStatic();
207
208
			// Other
209 17
			if (array_key_exists($name, $defaults))
210
			{
211 17
				$field->default = $defaults[$name];
212
			}
213
			// Put it to metadata object
214 17
			$this->_fields[$field->name] = $field;
215
216 17
			foreach ($property->getAllAnnotations() as $annotation)
217
			{
218 16
				if (!$annotation instanceof MetaAnnotationInterface)
219
				{
220 3
					continue;
221
				}
222
223 16
				$annotation->setName($field->name);
224 16
				$annotation->setEntity($field);
225 16
				$annotation->setMeta($this);
226 16
				$annotation->init();
227 17
				$annotations[] = $annotation;
228
			}
229
		}
230 30
		foreach ($annotations as $annotation)
231
		{
232 30
			$annotation->afterInit();
233
		}
234 30
	}
235
236
	/**
237
	 * Set state implementation
238
	 * @param mixed $data
239
	 * @return static
240
	 */
241
	public static function __set_state($data)
242
	{
243
		$obj = new static(null);
244
		foreach ($data as $field => $value)
245
		{
246
			$obj->$field = $value;
247
		}
248
		return $obj;
249
	}
250
251
	/**
252
	 * Create flyweight instance of `Meta`.
253
	 * Calling this function will create new instance only if it's not stored in cache.
254
	 * This allows very effective retrieving of `Meta` container's meta data, without need of parsing annotations.
255
	 * @param string|object|AnnotatedInterface $model
256
	 * @param MetaOptions|null $options
257
	 * @return static
258
	 */
259 44
	public static function create($model, MetaOptions $options = null)
260
	{
261
		// Reset local cache if dynamically added namespace
262 44
		if (self::$addNs)
263
		{
264 5
			self::$c = [];
265 5
			self::$addNs = false;
266
		}
267 44
		$class = is_object($model) ? get_class($model) : $model;
268 44
		$key = static::class . $class;
269 44
		if (isset(self::$c[$key]))
270
		{
271 7
			return self::$c[$key];
272
		}
273 37
		$cache = FlyCache::instance(static::class, $model, $options);
274
275 37
		$cached = $cache->get($class);
276 37
		if ($cached !== false)
277
		{
278
			self::$c[$key] = $cached;
279
			return $cached;
280
		}
281
282 37
		$instance = new static($model, $options);
283 30
		self::$c[$key] = $instance;
284 30
		$cache->set($class, $instance);
285 30
		return $instance;
286
	}
287
288
	/**
289
	 * Get array of properties values for property field
290
	 *
291
	 * @param string $fieldName
292
	 * @param int $type type of entities to return Meta::Type|Meta::Field|Meta::Method
293
	 * @return array
294
	 */
295
	public function properties($fieldName, $type = Meta::Field)
296
	{
297
		$result = [];
298
		switch ($type)
299
		{
300
			case self::Type:
301
				$from = $this->_type;
302
				break;
303
			case self::Field:
304
				$from = $this->_fields;
305
				break;
306
			case self::Method:
307
				$from = $this->_methods;
308
				break;
309
			default:
310
				$from = $this->_fields;
311
				break;
312
		}
313
		foreach ($from as $name => $field)
0 ignored issues
show
Bug introduced by
The expression $from of type object<Maslosoft\Addendu...lections\MetaProperty>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
314
		{
315
			if (isset($field->$fieldName))
316
			{
317
				$result[$name] = $field->$fieldName;
318
			}
319
		}
320
		return $result;
321
	}
322
323
	/**
324
	 * Get fields annotations for selected field or for all fields
325
	 * @param string $fieldName
326
	 * @return mixed[]
327
	 * @todo Remove this
328
	 * @deprecated since version number
329
	 */
330
	public function annotations($fieldName = null)
331
	{
332
		if ($fieldName)
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
333
		{
334
			return $this->_annotations[$fieldName];
335
		}
336
		return $this->_annotations;
337
	}
338
339
	/**
340
	 * Get class metadata
341
	 * @return MetaType
342
	 */
343 15
	public function type()
344
	{
345 15
		return $this->_type;
346
	}
347
348
	/**
349
	 * Get all fields metadata with field name as key
350
	 * @return MetaProperty[]
351
	 */
352 2
	public function fields()
353
	{
354 2
		return $this->_fields;
355
	}
356
357
	/**
358
	 * Get field by name
359
	 * @param string $name
360
	 * @return MetaProperty
361
	 */
362 1
	public function field($name)
363
	{
364 1
		return $this->_fields[$name];
365
	}
366
367
	/**
368
	 * Get all methods metadata
369
	 * @return MetaMethod[]
370
	 */
371
	public function methods()
372
	{
373
		return $this->_methods;
374
	}
375
376
	/**
377
	 * Get method metadata by name
378
	 * @param string $name
379
	 * @return MetaMethod|bool
380
	 */
381 7
	public function method($name)
382
	{
383 7
		if (!isset($this->_methods[$name]))
384
		{
385 3
			return false;
386
		}
387 7
		return $this->_methods[$name];
388
	}
389
390
	/**
391
	 * Get fields directly-like
392
	 * @param string $name
393
	 * @return MetaProperty|boolean
394
	 */
395 18
	public function __get($name)
396
	{
397 18
		if (isset($this->_fields[$name]))
398
		{
399 18
			return $this->_fields[$name];
400
		}
401
		else
402
		{
403 4
			return false;
404
		}
405
	}
406
407
}
408