Completed
Branch feature/pre-split (5f3640)
by Anton
03:19
created

ReflectionEntity::getProperty()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 39
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 19
nc 14
nop 2
dl 0
loc 39
rs 4.909
c 0
b 0
f 0
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
use Spiral\Models\SchematicEntity;
12
13
/**
14
 * Provides ability to generate entity schema based on given entity class and default property
15
 * values, support value inheritance!
16
 *
17
 * @method bool isAbstract()
18
 * @method string getName()
19
 * @method string getShortName()
20
 * @method bool isSubclassOf($class)
21
 * @method bool hasConstant($name)
22
 * @method mixed getConstant($name)
23
 * @method \ReflectionMethod[] getMethods()
24
 * @method \ReflectionClass|null getParentClass()
25
 */
26
class ReflectionEntity
27
{
28
    /**
29
     * Required to validly merge parent and children attributes.
30
     */
31
    const BASE_CLASS = AbstractEntity::class;
32
33
    /**
34
     * Properties cache.
35
     *
36
     * @invisible
37
     *
38
     * @var array
39
     */
40
    private $cache = [];
41
42
    /**
43
     * @var \ReflectionClass
44
     */
45
    private $reflection = null;
46
47
    /**
48
     * Only support SchematicEntity classes!
49
     *
50
     * @param string $class
51
     */
52
    public function __construct(string $class)
53
    {
54
        $this->reflection = new \ReflectionClass($class);
55
    }
56
57
    /**
58
     * @return \ReflectionClass
59
     */
60
    public function getReflection(): \ReflectionClass
61
    {
62
        return $this->reflection;
63
    }
64
65
    /**
66
     * @return array|string
67
     */
68
    public function getSecured()
69
    {
70
        if ($this->getProperty('secured', true) === '*') {
71
            return $this->getProperty('secured', true);
72
        }
73
74
        return array_unique((array)$this->getProperty('secured', true));
75
    }
76
77
    /**
78
     * @return array
79
     */
80
    public function getFillable(): array
81
    {
82
        return array_unique((array)$this->getProperty('fillable', true));
83
    }
84
85
    /**
86
     * @return array
87
     */
88
    public function getHidden(): array
89
    {
90
        return array_unique((array)$this->getProperty('hidden', true));
91
    }
92
93
    /**
94
     * @return array
95
     */
96
    public function getSetters(): array
97
    {
98
        return $this->getMutators()[AbstractEntity::MUTATOR_SETTER];
99
    }
100
101
    /**
102
     * @return array
103
     */
104
    public function getGetters(): array
105
    {
106
        return $this->getMutators()[AbstractEntity::MUTATOR_GETTER];
107
    }
108
109
    /**
110
     * @return array
111
     */
112
    public function getAccessors(): array
113
    {
114
        return $this->getMutators()[AbstractEntity::MUTATOR_ACCESSOR];
115
    }
116
117
    /**
118
     * Get methods declared in current class and exclude methods declared in parents.
119
     *
120
     * @return \ReflectionMethod[]
121
     */
122
    public function declareMethods(): array
123
    {
124
        $methods = [];
125
        foreach ($this->getMethods() as $method) {
126
            if ($method->getDeclaringClass()->getName() != $this->getName()) {
127
                continue;
128
            }
129
130
            $methods[] = $method;
131
        }
132
133
        return $methods;
134
    }
135
136
    /**
137
     * Entity schema.
138
     *
139
     * @return array
140
     */
141
    public function getSchema(): array
142
    {
143
        //Default property to store schema
144
        return (array)$this->getProperty('schema', true);
145
    }
146
147
    /**
148
     * Model mutators grouped by their type.
149
     *
150
     * @return array
151
     */
152
    public function getMutators(): array
153
    {
154
        $mutators = [
155
            AbstractEntity::MUTATOR_GETTER   => [],
156
            AbstractEntity::MUTATOR_SETTER   => [],
157
            AbstractEntity::MUTATOR_ACCESSOR => [],
158
        ];
159
160 View Code Duplication
        foreach ((array)$this->getProperty('getters', true) as $field => $filter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
161
            $mutators[AbstractEntity::MUTATOR_GETTER][$field] = $filter;
162
        }
163
164 View Code Duplication
        foreach ((array)$this->getProperty('setters', true) as $field => $filter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
165
            $mutators[AbstractEntity::MUTATOR_SETTER][$field] = $filter;
166
        }
167
168 View Code Duplication
        foreach ((array)$this->getProperty('accessors', true) as $field => $filter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
169
            $mutators[AbstractEntity::MUTATOR_ACCESSOR][$field] = $filter;
170
        }
171
172
        return $mutators;
173
    }
174
175
    /**
176
     * Read default model property value, will read "protected" and "private" properties. Method
177
     * raises entity event "describe" to allow it traits modify needed values.
178
     *
179
     * @param string $property Property name.
180
     * @param bool   $merge    If true value will be merged with all parent declarations.
181
     *
182
     * @return mixed
183
     */
184
    public function getProperty(string $property, bool $merge = false)
185
    {
186
        if (isset($this->cache[$property])) {
187
            //Property merging and trait events are pretty slow
188
            return $this->cache[$property];
189
        }
190
191
        $properties = $this->reflection->getDefaultProperties();
192
        $constants = $this->reflection->getConstants();
193
194
        if (isset($properties[$property])) {
195
            //Read from default value
196
            $value = $properties[$property];
197
        } elseif (isset($constants[strtoupper($property)])) {
198
            //Read from a constant
199
            $value = $constants[strtoupper($property)];
200
        } else {
201
            return null;
202
        }
203
204
        //Merge with parent value requested
205
        if ($merge && is_array($value) && !empty($parent = $this->parentReflection())) {
206
            $parentValue = $parent->getProperty($property, $merge);
207
208
            if (is_array($parentValue)) {
209
                //Class values prior to parent values
210
                $value = array_merge($parentValue, $value);
211
            }
212
        }
213
214
        if (!$this->reflection->isSubclassOf(SchematicEntity::class)) {
215
            return $value;
216
        }
217
218
        //To let traits apply schema changes
219
        return $this->cache[$property] = call_user_func(
220
            [$this->getName(), 'describeProperty'], $this, $property, $value
221
        );
222
    }
223
224
    /**
225
     * Parent entity schema/
226
     *
227
     * @return ReflectionEntity|null
228
     */
229
    public function parentReflection()
230
    {
231
        $parentClass = $this->reflection->getParentClass();
232
233
        if (!empty($parentClass) && $parentClass->getName() != static::BASE_CLASS) {
234
            $parent = clone $this;
235
            $parent->reflection = $this->getParentClass();
236
237
            return $parent;
238
        }
239
240
        return null;
241
    }
242
243
    /**
244
     * Bypassing call to reflection.
245
     *
246
     * @param string $name
247
     * @param array  $arguments
248
     *
249
     * @return mixed
250
     */
251
    public function __call(string $name, array $arguments)
252
    {
253
        return call_user_func_array([$this->reflection, $name], $arguments);
254
    }
255
256
    /**
257
     * @return string
258
     */
259
    public function __toString(): string
260
    {
261
        return $this->getName();
262
    }
263
264
    /**
265
     * Cloning and flushing cache.
266
     */
267
    public function __clone()
268
    {
269
        $this->cache = [];
270
    }
271
}