Completed
Push — master ( fbd4ac...e6a0d8 )
by Peter
21:07
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 2
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
	 *
63
	 * @param string|object|AnnotatedInterface $model
64
	 * @param MetaOptions $options
65
	 * @return type
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

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