Completed
Push — master ( 04f2f4...d19ae9 )
by Peter
33:36 queued 24:28
created

Meta::field()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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 30
	protected function __construct($model = null, MetaOptions $options = null)
67
	{
68
		// For internal use
69 30
		if (null === $model)
70
		{
71
			return;
72
		}
73 30
		if (null === $options)
74
		{
75 27
			$options = new MetaOptions();
76
		}
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 30
		$annotations = [];
83 30
		$mes = [];
84
85
		// Get reflection data
86 30
		$ad = Addendum::fly($options->instanceId);
87 30
		$ad->addNamespaces($options->namespaces);
88 30
		$info = $ad->annotate($model);
89
90
		// Class name of working component
91 25
		$className = is_object($model) ? get_class($model) : $model;
92
93 25
		if (!$info instanceof ReflectionAnnotatedClass)
94
		{
95
			throw new Exception(sprintf('Could not annotate `%s`', $className));
96
		}
97
98 25
		$properties = $info->getProperties(ReflectionProperty::IS_PUBLIC);
99 23
		$defaults = $info->getDefaultProperties();
100
101 23
		$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 23
		$this->_type = new $options->typeClass($info);
116 23
		foreach ($info->getAllAnnotations() as $annotation)
117
		{
118 8
			if (!$annotation instanceof MetaAnnotationInterface)
119
			{
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
		}
128
		// Setup methods
129 23
		foreach ($methods as $method)
130
		{
131 4
			if (!$method instanceof ReflectionAnnotatedMethod)
132
			{
133
				throw new Exception(sprintf('Could not annotate `%s::%s()`', $className, $method->name));
134
			}
135
136
			// Ignore magic methods
137 4
			if (preg_match('~^__~', $method->name))
138
			{
139
				continue;
140
			}
141
142
			// Ignore @Ignored marked methods
143 4
			if (IgnoredChecker::check($method))
144
			{
145 2
				continue;
146
			}
147
148
			// Create method holder class based on options
149 4
			$methodMeta = new $options->methodClass($method);
150 4
			foreach ($method->getAllAnnotations() as $annotation)
151
			{
152 4
				if (!$annotation instanceof MetaAnnotationInterface)
153
				{
154 2
					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
			}
163
164
			// Put it to metadata object
165 4
			$this->_methods[$method->name] = $methodMeta;
166
167
			// Get getters and setters for properties setup
168 4
			if (preg_match('~^[gs]et~', $method->name) && !$method->isStatic())
169
			{
170 4
				$mes[$method->name] = true;
171
			}
172
		}
173
174
		// Setup properties
175 23
		foreach ($properties as $property)
176
		{
177 15
			if (!$property instanceof ReflectionAnnotatedProperty)
178
			{
179
				throw new Exception(sprintf('Could not annotate `%s::%s`', $className, $property->name));
180
			}
181
182 15
			if (IgnoredChecker::check($property))
183
			{
184 2
				continue;
185
			}
186 15
			$name = $property->name;
187
			/* @var $property ReflectionAnnotatedProperty */
188 15
			$field = new $options->propertyClass($property);
189
190
			// Access options
191 15
			$field->callGet = isset($mes[$field->methodGet]) && $mes[$field->methodGet];
192 15
			$field->callSet = isset($mes[$field->methodSet]) && $mes[$field->methodSet];
193 15
			$field->direct = !($field->callGet || $field->callSet);
194 15
			$field->isStatic = $property->isStatic();
195
196
			// Other
197 15
			if (array_key_exists($name, $defaults))
198
			{
199 15
				$field->default = $defaults[$name];
200
			}
201
			// Put it to metadata object
202 15
			$this->_fields[$field->name] = $field;
203
204 15
			foreach ($property->getAllAnnotations() as $annotation)
205
			{
206 14
				if (!$annotation instanceof MetaAnnotationInterface)
207
				{
208 2
					continue;
209
				}
210
211 14
				$annotation->setName($field->name);
212 14
				$annotation->setEntity($field);
213 14
				$annotation->setMeta($this);
214 14
				$annotation->init();
215 15
				$annotations[] = $annotation;
216
			}
217
		}
218 23
		foreach ($annotations as $annotation)
219
		{
220 23
			$annotation->afterInit();
221
		}
222 23
	}
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
	 * @param MetaOption|null $options
245
	 * @return static
246
	 */
247 37
	public static function create($model, MetaOptions $options = null)
248
	{
249 37
		$cache = FlyCache::instance(static::class, $model, $options);
250
251 37
		$cached = $cache->get();
252 37
		if ($cached)
253
		{
254 7
			return $cached;
255
		}
256
257 30
		return $cache->set(new static($model, $options));
258
	}
259
260
	/**
261
	 * Get array of properties values for property field
262
	 *
263
	 * @param string $fieldName
264
	 * @param enum $type type of entities to return Meta::Type|Meta::Field|Meta::Method
265
	 * @return type
266
	 */
267
	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...
268
	{
269
		$result = [];
270
		switch ($type)
271
		{
272
			case self::Type:
273
				$from = $this->_type;
274
				break;
275
			case self::Field:
276
				$from = $this->_fields;
277
				break;
278
			case self::Method:
279
				$from = $this->_methods;
280
				break;
281
			default:
282
				$from = $this->_fields;
283
				break;
284
		}
285
		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...
286
		{
287
			if (isset($field->$fieldName))
288
			{
289
				$result[$name] = $field->$fieldName;
290
			}
291
		}
292
		return $result;
293
	}
294
295
	/**
296
	 * Get fields annotations for selected field or for all fields
297
	 * @param string $fieldName
298
	 * @return mixed[]
299
	 * @todo Remove this
300
	 * @deprecated since version number
301
	 */
302
	public function annotations($fieldName = null)
303
	{
304
		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...
305
		{
306
			return $this->_annotations[$fieldName];
307
		}
308
		return $this->_annotations;
309
	}
310
311
	/**
312
	 * Get class metadata
313
	 * @return MetaType
314
	 */
315 9
	public function type()
316
	{
317 9
		return $this->_type;
318
	}
319
320
	/**
321
	 * Get all fields metadata with field name as key
322
	 * @return MetaProperty[]
323
	 */
324 1
	public function fields()
325
	{
326 1
		return $this->_fields;
327
	}
328
329
	/**
330
	 * Get field by name
331
	 * @param string $name
332
	 * @return MetaProperty
333
	 */
334
	public function field($name)
335
	{
336
		return $this->_fields[$name];
337
	}
338
339
	/**
340
	 * Get all methods metadata
341
	 * @return MetaMethod[]
342
	 */
343
	public function methods()
344
	{
345
		return $this->_methods;
346
	}
347
348
	/**
349
	 * Get method metadata by name
350
	 * @param string $name
351
	 * @return MetaMethod|bool
352
	 */
353 5
	public function method($name)
354
	{
355 5
		if (!isset($this->_methods[$name]))
356
		{
357 2
			return false;
358
		}
359 5
		return $this->_methods[$name];
360
	}
361
362
	/**
363
	 * Get fields directly-like
364
	 * @param string $name
365
	 * @return MetaProperty|boolean
366
	 */
367 17
	public function __get($name)
368
	{
369 17
		if (isset($this->_fields[$name]))
370
		{
371 17
			return $this->_fields[$name];
372
		}
373
		else
374
		{
375 2
			return false;
376
		}
377
	}
378
379
}
380