1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework. |
4
|
|
|
* |
5
|
|
|
* @license MIT |
6
|
|
|
* @author Anton Titov (Wolfy-J) |
7
|
|
|
*/ |
8
|
|
|
namespace Spiral\Models\Reflections; |
9
|
|
|
|
10
|
|
|
use Spiral\Models\Prototypes\AbstractEntity; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Provides ability to generate entity schema based on given entity class and default property |
14
|
|
|
* values, support value inheritance! |
15
|
|
|
* |
16
|
|
|
* @method bool isAbstract() |
17
|
|
|
* @method string getName() |
18
|
|
|
* @method string getShortName() |
19
|
|
|
* @method bool isSubclassOf($class) |
20
|
|
|
* @method bool hasConstant($name) |
21
|
|
|
* @method mixed getConstant($name) |
22
|
|
|
* @method \ReflectionMethod[] getMethods() |
23
|
|
|
* @method \ReflectionClass|null getParentClass() |
24
|
|
|
*/ |
25
|
|
|
class ReflectionEntity |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* Required to validly merge parent and children attributes. |
29
|
|
|
*/ |
30
|
|
|
const BASE_CLASS = AbstractEntity::class; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Properties cache. |
34
|
|
|
* |
35
|
|
|
* @invisible |
36
|
|
|
* |
37
|
|
|
* @var array |
38
|
|
|
*/ |
39
|
|
|
private $cache = []; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var \ReflectionClass |
43
|
|
|
*/ |
44
|
|
|
private $reflection = null; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Only support SchematicEntity classes! |
48
|
|
|
* |
49
|
|
|
* @param string $class |
50
|
|
|
*/ |
51
|
|
|
public function __construct(string $class) |
52
|
|
|
{ |
53
|
|
|
$this->reflection = new \ReflectionClass($class); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @return \ReflectionClass |
58
|
|
|
*/ |
59
|
|
|
public function getReflection(): \ReflectionClass |
60
|
|
|
{ |
61
|
|
|
return $this->reflection; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @return array|string |
66
|
|
|
*/ |
67
|
|
|
public function getSecured() |
68
|
|
|
{ |
69
|
|
|
if ($this->getProperty('secured', true) === '*') { |
70
|
|
|
return $this->getProperty('secured', true); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
return array_unique((array)$this->getProperty('secured', true)); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @return array |
78
|
|
|
*/ |
79
|
|
|
public function getFillable(): array |
80
|
|
|
{ |
81
|
|
|
return array_unique((array)$this->getProperty('fillable', true)); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @return array |
86
|
|
|
*/ |
87
|
|
|
public function getHidden(): array |
88
|
|
|
{ |
89
|
|
|
return array_unique((array)$this->getProperty('hidden', true)); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @return array |
94
|
|
|
*/ |
95
|
|
|
public function getSetters(): array |
96
|
|
|
{ |
97
|
|
|
return $this->getMutators()[AbstractEntity::MUTATOR_SETTER]; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @return array |
102
|
|
|
*/ |
103
|
|
|
public function getGetters(): array |
104
|
|
|
{ |
105
|
|
|
return $this->getMutators()[AbstractEntity::MUTATOR_GETTER]; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @return array |
110
|
|
|
*/ |
111
|
|
|
public function getAccessors(): array |
112
|
|
|
{ |
113
|
|
|
return $this->getMutators()[AbstractEntity::MUTATOR_ACCESSOR]; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Get methods declared in current class and exclude methods declared in parents. |
118
|
|
|
* |
119
|
|
|
* @return \ReflectionMethod[] |
120
|
|
|
*/ |
121
|
|
|
public function declareMethods(): array |
122
|
|
|
{ |
123
|
|
|
$methods = []; |
124
|
|
|
foreach ($this->getMethods() as $method) { |
125
|
|
|
if ($method->getDeclaringClass()->getName() != $this->getName()) { |
126
|
|
|
continue; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
$methods[] = $method; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return $methods; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Fields associated with their type. |
137
|
|
|
* |
138
|
|
|
* @return array |
139
|
|
|
*/ |
140
|
|
|
public function getFields(): array |
141
|
|
|
{ |
142
|
|
|
//Default property to store schema |
143
|
|
|
return (array)$this->getProperty('schema', true); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Model mutators grouped by their type. |
148
|
|
|
* |
149
|
|
|
* @return array |
150
|
|
|
*/ |
151
|
|
|
public function getMutators(): array |
152
|
|
|
{ |
153
|
|
|
$mutators = [ |
154
|
|
|
AbstractEntity::MUTATOR_GETTER => [], |
155
|
|
|
AbstractEntity::MUTATOR_SETTER => [], |
156
|
|
|
AbstractEntity::MUTATOR_ACCESSOR => [], |
157
|
|
|
]; |
158
|
|
|
|
159
|
|
View Code Duplication |
foreach ((array)$this->getProperty('getters', true) as $field => $filter) { |
|
|
|
|
160
|
|
|
$mutators[AbstractEntity::MUTATOR_GETTER][$field] = $filter; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
View Code Duplication |
foreach ((array)$this->getProperty('setters', true) as $field => $filter) { |
|
|
|
|
164
|
|
|
$mutators[AbstractEntity::MUTATOR_SETTER][$field] = $filter; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
View Code Duplication |
foreach ((array)$this->getProperty('accessors', true) as $field => $filter) { |
|
|
|
|
168
|
|
|
$mutators[AbstractEntity::MUTATOR_ACCESSOR][$field] = $filter; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
return $mutators; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Read default model property value, will read "protected" and "private" properties. Method |
176
|
|
|
* raises entity event "describe" to allow it traits modify needed values. |
177
|
|
|
* |
178
|
|
|
* @param string $property Property name. |
179
|
|
|
* @param bool $merge If true value will be merged with all parent declarations. |
180
|
|
|
* |
181
|
|
|
* @return mixed |
182
|
|
|
*/ |
183
|
|
|
public function getProperty(string $property, bool $merge = false) |
184
|
|
|
{ |
185
|
|
|
if (isset($this->cache[$property])) { |
186
|
|
|
//Property merging and trait events are pretty slow |
187
|
|
|
return $this->cache[$property]; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$properties = $this->reflection->getDefaultProperties(); |
191
|
|
|
$constants = $this->reflection->getConstants(); |
192
|
|
|
|
193
|
|
|
if (isset($properties[$property])) { |
194
|
|
|
//Read from default value |
195
|
|
|
$value = $properties[$property]; |
196
|
|
|
} elseif (isset($constants[strtoupper($property)])) { |
197
|
|
|
//Read from a constant |
198
|
|
|
$value = $constants[strtoupper($property)]; |
199
|
|
|
} else { |
200
|
|
|
return null; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
//Merge with parent value requested |
204
|
|
|
if ($merge && is_array($value) && !empty($parent = $this->parentReflection())) { |
205
|
|
|
$parentValue = $parent->getProperty($property, $merge); |
206
|
|
|
|
207
|
|
|
if (is_array($parentValue)) { |
208
|
|
|
//Class values prior to parent values |
209
|
|
|
$value = array_merge($parentValue, $value); |
210
|
|
|
} |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
//To let traits apply schema changes |
214
|
|
|
return $this->cache[$property] = call_user_func( |
215
|
|
|
[$this->getName(), 'describeProperty'], $this, $property, $value |
216
|
|
|
); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Parent entity schema/ |
221
|
|
|
* |
222
|
|
|
* @return ReflectionEntity|null |
223
|
|
|
*/ |
224
|
|
|
public function parentReflection() |
225
|
|
|
{ |
226
|
|
|
$parentClass = $this->reflection->getParentClass(); |
227
|
|
|
|
228
|
|
|
if (!empty($parentClass) && $parentClass->getName() != static::BASE_CLASS) { |
229
|
|
|
$parent = clone $this; |
230
|
|
|
$parent->reflection = $this->getParentClass(); |
231
|
|
|
|
232
|
|
|
return $parent; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return null; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Bypassing call to reflection. |
240
|
|
|
* |
241
|
|
|
* @param string $name |
242
|
|
|
* @param array $arguments |
243
|
|
|
* |
244
|
|
|
* @return mixed |
245
|
|
|
*/ |
246
|
|
|
public function __call(string $name, array $arguments) |
247
|
|
|
{ |
248
|
|
|
return call_user_func_array([$this->reflection, $name], $arguments); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* @return string |
253
|
|
|
*/ |
254
|
|
|
public function __toString(): string |
255
|
|
|
{ |
256
|
|
|
return $this->getName(); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Cloning and flushing cache. |
261
|
|
|
*/ |
262
|
|
|
public function __clone() |
263
|
|
|
{ |
264
|
|
|
$this->cache = []; |
265
|
|
|
} |
266
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.