Completed
Push — master ( e6a0d8...015d17 )
by Peter
62:09
created

Meta::properties()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 22
cp 0
rs 8.439
c 0
b 0
f 0
cc 6
eloc 19
nc 12
nop 2
crap 42
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
	 * @param string|object|AnnotatedInterface $model
63
	 * @param MetaOptions $options
64
	 * @throws Exception
65
	 */
66 29
	protected function __construct($model = null, MetaOptions $options = null)
67
	{
68
		// For internal use
69 29
		if (null === $model)
70 29
		{
71
			return;
72
		}
73 29
		if (null === $options)
74 29
		{
75 26
			$options = new MetaOptions();
76 26
		}
77
78
		// TODO Use adapter here
79
		// ?TODO Abstract from component meta, so other kinds of meta extractors could be used
80
		// For example, for development annotation based extractor could be used, which could compile
81
		// Metadata to arrays, and for production environment, compiled arrays could be used
82 29
		$annotations = [];
83 29
		$mes = [];
84
85
		// Get reflection data
86 29
		$ad = Addendum::fly($options->instanceId);
87 29
		$ad->addNamespaces($options->namespaces);
88 29
		$info = $ad->annotate($model);
89
90
		// Class name of working component
91 24
		$className = is_object($model) ? get_class($model) : $model;
92
93 24
		if (!$info instanceof ReflectionAnnotatedClass)
94 24
		{
95
			throw new Exception(sprintf('Could not annotate `%s`', $className));
96
		}
97
98 24
		$properties = $info->getProperties(ReflectionProperty::IS_PUBLIC);
99 22
		$defaults = $info->getDefaultProperties();
100
101 22
		$methods = $info->getMethods(ReflectionMethod::IS_PUBLIC);
102
103
		// Setup type
104
		/**
105
		 * @todo Fix it: $this->_meta->{$this->name}->...
106
		 * ^-- _meta __get and __set is only for fields AND use _fields field,
107
		 * for class OR methods it should use different fields
108
		 * for class should be _main
109
		 * for methods should be _methods
110
		 * __get and __set should distinguish it somehow... - maybe by field type EComponentMetaProperty for fieltds etc.
111
		 * Currently disabled
112
		 * OR add function to Annotation to setEntity, which should point to _field, _main or _method?
113
		 */
114
		// Setup class annotations
115 22
		$this->_type = new $options->typeClass($info);
116 22
		foreach ($info->getAllAnnotations() as $annotation)
117
		{
118 8
			if (!$annotation instanceof MetaAnnotationInterface)
119 8
			{
120
				continue;
121
			}
122 8
			$annotation->setName($info->name);
123 8
			$annotation->setEntity($this->_type);
124 8
			$annotation->setMeta($this);
125 8
			$annotation->init();
126 8
			$annotations[] = $annotation;
127 22
		}
128
		// Setup methods
129 22
		foreach ($methods as $method)
130
		{
131 3
			if (!$method instanceof ReflectionAnnotatedMethod)
132 3
			{
133
				throw new Exception(sprintf('Could not annotate `%s::%s()`', $className, $method->name));
134
			}
135
136
			// Ignore magic methods
137 3
			if (preg_match('~^__~', $method->name))
138 3
			{
139
				continue;
140
			}
141
142
			// Ignore @Ignored marked methods
143 3
			if (IgnoredChecker::check($method))
144 3
			{
145 1
				continue;
146
			}
147
148
			// Create method holder class based on options
149 3
			$methodMeta = new $options->methodClass($method);
150 3
			foreach ($method->getAllAnnotations() as $annotation)
151
			{
152 3
				if (!$annotation instanceof MetaAnnotationInterface)
153 3
				{
154 1
					continue;
155
				}
156
157 2
				$annotation->setName($method->name);
158 2
				$annotation->setEntity($methodMeta);
159 2
				$annotation->setMeta($this);
160 2
				$annotation->init();
161 2
				$annotations[] = $annotation;
162 3
			}
163
164
			// Put it to metadata object
165 3
			$this->_methods[$method->name] = $methodMeta;
166
167
			// Get getters and setters for properties setup
168 3
			if (preg_match('~^[gs]et~', $method->name) && !$method->isStatic())
169 3
			{
170 1
				$mes[$method->name] = true;
171 1
			}
172 22
		}
173
174
		// Setup properties
175 22
		foreach ($properties as $property)
176
		{
177 14
			if (!$property instanceof ReflectionAnnotatedProperty)
178 14
			{
179
				throw new Exception(sprintf('Could not annotate `%s::%s`', $className, $property->name));
180
			}
181
182 14
			if (IgnoredChecker::check($property))
183 14
			{
184 1
				continue;
185
			}
186 14
			$name = $property->name;
187
			/* @var $property ReflectionAnnotatedProperty */
188 14
			$field = new $options->propertyClass($property);
189
190
			// Access options
191 14
			$field->callGet = isset($mes[$field->methodGet]) && $mes[$field->methodGet];
192 14
			$field->callSet = isset($mes[$field->methodSet]) && $mes[$field->methodSet];
193 14
			$field->direct = !($field->callGet || $field->callSet);
194 14
			$field->isStatic = $property->isStatic();
195
196
			// Other
197 14
			if (array_key_exists($name, $defaults))
198 14
			{
199 14
				$field->default = $defaults[$name];
200 14
			}
201
			// Put it to metadata object
202 14
			$this->_fields[$field->name] = $field;
203
204 14
			foreach ($property->getAllAnnotations() as $annotation)
205
			{
206 13
				if (!$annotation instanceof MetaAnnotationInterface)
207 13
				{
208 1
					continue;
209
				}
210
211 13
				$annotation->setName($field->name);
212 13
				$annotation->setEntity($field);
213 13
				$annotation->setMeta($this);
214 13
				$annotation->init();
215 13
				$annotations[] = $annotation;
216 14
			}
217 22
		}
218 22
		foreach ($annotations as $annotation)
219
		{
220 22
			$annotation->afterInit();
221 22
		}
222 22
	}
223
224
	/**
225
	 * Set state implementation
226
	 * @param mixed $data
227
	 * @return static
228
	 */
229
	public static function __set_state($data)
230
	{
231
		$obj = new static(null);
232
		foreach ($data as $field => $value)
233
		{
234
			$obj->$field = $value;
235
		}
236
		return $obj;
237
	}
238
239
	/**
240
	 * Create flyghtweight instace of `Meta`.
241
	 * Calling this function will create new instance only if it's not stored in cache.
242
	 * This allows very effective retrieving of `Meta` container's meta data, without need of parsing annotations.
243
	 * @param string|object|AnnotatedInterface $model
244
	 * @return static
245
	 */
246 36
	public static function create($model, MetaOptions $options = null)
247
	{
248 36
		$cache = FlyCache::instance(static::class, $model, $options);
249
250 36
		$cached = $cache->get();
251
		if ($cached)
252 36
		{
253 7
			return $cached;
254
		}
255
256 29
		return $cache->set(new static($model, $options));
257
	}
258
259
	/**
260
	 * Get array of properties values for property field
261
	 *
262
	 * @param string $fieldName
263
	 * @param enum $type type of entities to return Meta::Type|Meta::Field|Meta::Method
264
	 * @return type
265
	 */
266
	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...
267
	{
268
		$result = [];
269
		switch ($type)
270
		{
271
			case self::Type:
272
				$from = $this->_type;
273
				break;
274
			case self::Field:
275
				$from = $this->_fields;
276
				break;
277
			case self::Method:
278
				$from = $this->_methods;
279
				break;
280
			default:
281
				$from = $this->_fields;
282
				break;
283
		}
284
		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...
285
		{
286
			if (isset($field->$fieldName))
287
			{
288
				$result[$name] = $field->$fieldName;
289
			}
290
		}
291
		return $result;
292
	}
293
294
	/**
295
	 * Get fields annotations for selected field or for all fields
296
	 * @param string $fieldName
297
	 * @return mixed[]
298
	 * @todo Remove this
299
	 * @deprecated since version number
300
	 */
301
	public function annotations($fieldName = null)
302
	{
303
		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...
304
		{
305
			return $this->_annotations[$fieldName];
306
		}
307
		return $this->_annotations;
308
	}
309
310
	/**
311
	 * Get class metadata
312
	 * @return MetaType
313
	 */
314 9
	public function type()
315
	{
316 9
		return $this->_type;
317
	}
318
319
	/**
320
	 * Get all fields metadata with field name as key
321
	 * @return MetaProperty[]
322
	 */
323 1
	public function fields()
324
	{
325 1
		return $this->_fields;
326
	}
327
328
	/**
329
	 * Get field by name
330
	 * @param string $name
331
	 * @return MetaProperty
332
	 */
333
	public function field($name)
334
	{
335
		return $this->_fields[$name];
336
	}
337
338
	/**
339
	 * Get all methods metadata
340
	 * @return MetaMethod[]
341
	 */
342
	public function methods()
343
	{
344
		return $this->_methods;
345
	}
346
347
	/**
348
	 * Get method metadata by name
349
	 * @param string $name
350
	 * @return MetaMethod|bool
351
	 */
352 4
	public function method($name)
353
	{
354 4
		if (!isset($this->_methods[$name]))
355 4
		{
356 1
			return false;
357
		}
358 4
		return $this->_methods[$name];
359
	}
360
361
	/**
362
	 * Get fields directly-like
363
	 * @param string $name
364
	 * @return MetaProperty|boolean
365
	 */
366 16
	public function __get($name)
367
	{
368 16
		if (isset($this->_fields[$name]))
369 16
		{
370 16
			return $this->_fields[$name];
371
		}
372
		else
373
		{
374 1
			return false;
375
		}
376
	}
377
378
}
379