Completed
Push — master ( 22b422...fbd4ac )
by Peter
05:54
created

Meta::method()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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