Completed
Push — master ( 7d4d47...4dc2a1 )
by Peter
08:01
created

Meta   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 8

Test Coverage

Coverage 75.64%

Importance

Changes 6
Bugs 1 Features 0
Metric Value
wmc 48
lcom 3
cbo 8
dl 0
loc 372
ccs 118
cts 156
cp 0.7564
rs 8.4864
c 6
b 1
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A annotations() 0 8 2
A type() 0 4 1
A fields() 0 4 1
A methods() 0 4 1
F __construct() 0 157 25
A __set_state() 0 9 2
B create() 0 27 5
B properties() 0 27 6
A field() 0 4 1
A method() 0 8 2
A __get() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like Meta often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Meta, and based on these observations, apply Extract Interface, too.

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 http://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 32
	protected function __construct($model = null, MetaOptions $options = null)
79
	{
80
		// For internal use
81 32
		if (null === $model)
82 32
		{
83
			return;
84
		}
85 32
		if (null === $options)
86 32
		{
87 28
			$options = new MetaOptions();
88 28
		}
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 32
		$annotations = [];
95 32
		$mes = [];
96
97
		// Get reflection data
98 32
		$ad = Addendum::fly($options->instanceId);
99 32
		$ad->addNamespaces($options->namespaces);
100 32
		$info = $ad->annotate($model);
101
102
		// Class name of working component
103 27
		$className = is_object($model) ? get_class($model) : $model;
104
105 27
		if (!$info instanceof ReflectionAnnotatedClass)
106 27
		{
107
			throw new Exception(sprintf('Could not annotate `%s`', $className));
108
		}
109
110 27
		$properties = $info->getProperties(ReflectionProperty::IS_PUBLIC);
111 25
		$defaults = $info->getDefaultProperties();
112
113 25
		$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 fieltds 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 25
		$this->_type = new $options->typeClass($info);
128 25
		foreach ($info->getAllAnnotations() as $annotation)
129
		{
130 9
			if (!$annotation instanceof MetaAnnotationInterface)
131 9
			{
132
				continue;
133
			}
134 9
			$annotation->setName($info->name);
135 9
			$annotation->setEntity($this->_type);
136 9
			$annotation->setMeta($this);
137 9
			$annotation->init();
138 9
			$annotations[] = $annotation;
139 25
		}
140
		// Setup methods
141 25
		foreach ($methods as $method)
142
		{
143 5
			if (!$method instanceof ReflectionAnnotatedMethod)
144 5
			{
145
				throw new Exception(sprintf('Could not annotate `%s::%s()`', $className, $method->name));
146
			}
147
148
			// Ignore magic methods
149 5
			if (preg_match('~^__~', $method->name))
150 5
			{
151
				continue;
152
			}
153
154
			// Ignore @Ignored marked methods
155 5
			if (IgnoredChecker::check($method))
156 5
			{
157 3
				continue;
158
			}
159
160
			// Create method holder class based on options
161 5
			$methodMeta = new $options->methodClass($method);
162 5
			foreach ($method->getAllAnnotations() as $annotation)
163
			{
164 5
				if (!$annotation instanceof MetaAnnotationInterface)
165 5
				{
166 3
					continue;
167
				}
168
169 2
				$annotation->setName($method->name);
170 2
				$annotation->setEntity($methodMeta);
171 2
				$annotation->setMeta($this);
172 2
				$annotation->init();
173 2
				$annotations[] = $annotation;
174 5
			}
175
176
			// Put it to metadata object
177 5
			$this->_methods[$method->name] = $methodMeta;
178
179
			// Get getters and setters for properties setup
180 5
			if (preg_match('~^[gs]et~', $method->name) && !$method->isStatic())
181 5
			{
182 3
				$mes[$method->name] = true;
183 3
			}
184 25
		}
185
186
		// Setup properties
187 25
		foreach ($properties as $property)
188
		{
189 16
			if (!$property instanceof ReflectionAnnotatedProperty)
190 17
			{
191
				throw new Exception(sprintf('Could not annotate `%s::%s`', $className, $property->name));
192
			}
193
194 16
			if (IgnoredChecker::check($property))
195 16
			{
196 3
				continue;
197
			}
198 16
			$name = $property->name;
199
			/* @var $property ReflectionAnnotatedProperty */
200 16
			$field = new $options->propertyClass($property);
201
202
			// Access options
203 16
			$field->callGet = isset($mes[$field->methodGet]) && $mes[$field->methodGet];
204 16
			$field->callSet = isset($mes[$field->methodSet]) && $mes[$field->methodSet];
205 16
			$field->direct = !($field->callGet || $field->callSet);
206 16
			$field->isStatic = $property->isStatic();
207
208
			// Other
209 16
			if (array_key_exists($name, $defaults))
210 16
			{
211 16
				$field->default = $defaults[$name];
212 16
			}
213
			// Put it to metadata object
214 16
			$this->_fields[$field->name] = $field;
215
216 16
			foreach ($property->getAllAnnotations() as $annotation)
217
			{
218 15
				if (!$annotation instanceof MetaAnnotationInterface)
219 15
				{
220 3
					continue;
221
				}
222
223 15
				$annotation->setName($field->name);
224 15
				$annotation->setEntity($field);
225 15
				$annotation->setMeta($this);
226 15
				$annotation->init();
227 15
				$annotations[] = $annotation;
228 16
			}
229 25
		}
230 25
		foreach ($annotations as $annotation)
231
		{
232 25
			$annotation->afterInit();
233 25
		}
234 25
	}
235
236
	/**
237
	 * Set state implementation
238
	 * @param mixed $data
239
	 * @return static
240
	 */
241 1
	public static function __set_state($data)
242
	{
243
		$obj = new static(null);
244
		foreach ($data as $field => $value)
245
		{
246 1
			$obj->$field = $value;
247
		}
248
		return $obj;
249
	}
250
251
	/**
252
	 * Create flyghtweight instace 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 MetaOption|null $options
257
	 * @return static
258
	 */
259 39
	public static function create($model, MetaOptions $options = null)
260
	{
261
		// Reset local cache if dynamically added namespace
262 39
		if (self::$addNs)
263 39
		{
264 6
			self::$c = [];
265 6
			self::$addNs = false;
266 6
		}
267 39
		$class = is_object($model) ? get_class($model) : $model;
268 39
		$key = static::class . $class;
269 39
		if (isset(self::$c[$key]))
270 39
		{
271 7
			return self::$c[$key];
272
		}
273 32
		$cache = FlyCache::instance(static::class, $model, $options);
274
275 32
		$cached = $cache->get();
276
		if ($cached)
277 32
		{
278
			self::$c[$key] = $cached;
279
			return $cached;
280
		}
281
282 32
		$instance = new static($model, $options);
283 25
		self::$c[$key] = $instance;
284 25
		return $cache->set($instance);
285
	}
286
287
	/**
288
	 * Get array of properties values for property field
289
	 *
290
	 * @param string $fieldName
291
	 * @param enum $type type of entities to return Meta::Type|Meta::Field|Meta::Method
292
	 * @return type
293
	 */
294
	public function properties($fieldName, $type = Meta::Field)
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
295
	{
296
		$result = [];
297
		switch ($type)
298
		{
299
			case self::Type:
300
				$from = $this->_type;
301
				break;
302
			case self::Field:
303
				$from = $this->_fields;
304
				break;
305
			case self::Method:
306
				$from = $this->_methods;
307
				break;
308
			default:
309
				$from = $this->_fields;
310
				break;
311
		}
312
		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...
313
		{
314
			if (isset($field->$fieldName))
315
			{
316
				$result[$name] = $field->$fieldName;
317
			}
318
		}
319
		return $result;
320
	}
321
322
	/**
323
	 * Get fields annotations for selected field or for all fields
324
	 * @param string $fieldName
325
	 * @return mixed[]
326
	 * @todo Remove this
327
	 * @deprecated since version number
328
	 */
329
	public function annotations($fieldName = null)
330
	{
331
		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...
332
		{
333
			return $this->_annotations[$fieldName];
334
		}
335
		return $this->_annotations;
336
	}
337
338
	/**
339
	 * Get class metadata
340
	 * @return MetaType
341
	 */
342 10
	public function type()
343
	{
344 10
		return $this->_type;
345
	}
346
347
	/**
348
	 * Get all fields metadata with field name as key
349
	 * @return MetaProperty[]
350
	 */
351 1
	public function fields()
352
	{
353 1
		return $this->_fields;
354
	}
355
356
	/**
357
	 * Get field by name
358
	 * @param string $name
359
	 * @return MetaProperty
360
	 */
361
	public function field($name)
362
	{
363
		return $this->_fields[$name];
364
	}
365
366
	/**
367
	 * Get all methods metadata
368
	 * @return MetaMethod[]
369
	 */
370
	public function methods()
371
	{
372
		return $this->_methods;
373
	}
374
375
	/**
376
	 * Get method metadata by name
377
	 * @param string $name
378
	 * @return MetaMethod|bool
379
	 */
380 6
	public function method($name)
381
	{
382 6
		if (!isset($this->_methods[$name]))
383 6
		{
384 3
			return false;
385
		}
386 6
		return $this->_methods[$name];
387
	}
388
389
	/**
390
	 * Get fields directly-like
391
	 * @param string $name
392
	 * @return MetaProperty|boolean
393
	 */
394 18
	public function __get($name)
395
	{
396 18
		if (isset($this->_fields[$name]))
397 18
		{
398 18
			return $this->_fields[$name];
399
		}
400
		else
401
		{
402 3
			return false;
403
		}
404
	}
405
406
}
407