Passed
Push — master ( db5c4e...adaaa2 )
by Kirill
03:03
created

ReflectionEntity::getProperty()   B

Complexity

Conditions 9
Paths 14

Size

Total Lines 36
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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