Completed
Branch feature/split-orm (fa2f8e)
by Anton
03:35
created

DocumentSchema::getFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\ODM\Schemas;
8
9
use Doctrine\Common\Inflector\Inflector;
10
use Spiral\Models\Reflections\ReflectionEntity;
11
use Spiral\ODM\Configs\MutatorsConfig;
12
use Spiral\ODM\Document;
13
use Spiral\ODM\DocumentEntity;
14
use Spiral\ODM\Entities\DocumentInstantiator;
15
use Spiral\ODM\Exceptions\SchemaException;
16
17
class DocumentSchema implements SchemaInterface
18
{
19
    /**
20
     * @var ReflectionEntity
21
     */
22
    private $reflection;
23
24
    /**
25
     * @var MutatorsConfig
26
     */
27
    private $mutators;
28
29
    /**
30
     * @param ReflectionEntity $reflection
31
     * @param MutatorsConfig   $config
32
     */
33
    public function __construct(ReflectionEntity $reflection, MutatorsConfig $config)
34
    {
35
        $this->reflection = $reflection;
36
        $this->mutators = $config;
37
    }
38
39
    /**
40
     * @return string
41
     */
42
    public function getClass(): string
43
    {
44
        return $this->reflection->getName();
45
    }
46
47
    /**
48
     * @return ReflectionEntity
49
     */
50
    public function getReflection(): ReflectionEntity
51
    {
52
        return $this->reflection;
53
    }
54
55
    /**
56
     * @return string
57
     */
58
    public function getInstantiator(): string
59
    {
60
        return $this->reflection->getConstant('INSTANTIATOR') ?? DocumentInstantiator::class;
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function isEmbedded(): bool
67
    {
68
        return !$this->reflection->isSubclassOf(Document::class)
69
            && $this->reflection->isSubclassOf(DocumentEntity::class);
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function getDatabase()
76
    {
77
        if ($this->isEmbedded()) {
78
            throw new SchemaException(
79
                "Unable to get database name for embedded model {$this->reflection}"
80
            );
81
        }
82
83
        $database = $this->reflection->getConstant('DATABASE');
84
        if (empty($database)) {
85
            //Empty database to be used
86
            return null;
87
        }
88
89
        return $database;
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function getCollection(): string
96
    {
97
        if ($this->isEmbedded()) {
98
            throw new SchemaException(
99
                "Unable to get collection name for embedded model {$this->reflection}"
100
            );
101
        }
102
103
        $collection = $this->reflection->getConstant('COLLECTION');
104
        if (empty($collection)) {
105
            //Generate collection using short class name
106
            $collection = Inflector::camelize($this->reflection->getShortName());
107
            $collection = Inflector::pluralize($collection);
108
        }
109
110
        return $collection;
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function getIndexes(): array
117
    {
118
        if ($this->isEmbedded()) {
119
            throw new SchemaException(
120
                "Unable to get indexes for embedded model {$this->reflection}"
121
            );
122
        }
123
124
        $indexes = $this->reflection->getProperty('indexes', true);
125
        if (empty($indexes) || !is_array($indexes)) {
126
            return [];
127
        }
128
129
        $result = [];
130
        foreach ($indexes as $index) {
131
            $options = [];
132
            if (isset($index['@options'])) {
133
                $options = $index['@options'];
134
                unset($index['@options']);
135
            }
136
137
            $result[] = new IndexDefinition($index, $options);
138
        }
139
140
        return array_unique($result);
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function resolvePrimary(SchemaBuilder $builder): string
147
    {
148
        //Let's define a way how to separate one model from another based on given fields
149
        $helper = new InheritanceHelper($this, $builder->getSchemas());
150
151
        return $helper->findPrimary();
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function packSchema(SchemaBuilder $builder): array
158
    {
159
        return [
160
            //Instantion options and behaviour (if any)
161
            DocumentEntity::SH_INSTANTIATION => $this->instantiationOptions($builder),
162
163
            //Default entity state (builder is needed to resolve recursive defaults)
164
            DocumentEntity::SH_DEFAULTS      => $this->buildDefaults($builder),
165
166
            //Entity behaviour
167
            DocumentEntity::SH_HIDDEN        => $this->reflection->getHidden(),
168
            DocumentEntity::SH_SECURED       => $this->reflection->getSecured(),
169
            DocumentEntity::SH_FILLABLE      => $this->reflection->getFillable(),
170
171
            //Mutators can be altered based on ODM\SchemasConfig
172
            DocumentEntity::SH_MUTATORS      => $this->buildMutators(),
173
174
            //Document behaviours (we can mix them with accessors due potential inheritance)
175
            DocumentEntity::SH_COMPOSITIONS  => $this->buildCompositions($builder),
176
            DocumentEntity::SH_AGGREGATIONS  => $this->buildAggregations($builder),
177
        ];
178
    }
179
180
    /**
181
     * Define instantiator specific options (usually needed to resolve class inheritance). Might
182
     * return null if associated instantiator is unknown to DocumentSchema.
183
     *
184
     * @param SchemaBuilder $builder
185
     *
186
     * @return mixed
187
     */
188
    protected function instantiationOptions(SchemaBuilder $builder)
189
    {
190
        if ($this->getInstantiator() != DocumentInstantiator::class) {
191
            //Unable to define options for non default inheritance based instantiator
192
            return null;
193
        }
194
195
        //Let's define a way how to separate one model from another based on given fields
196
        $helper = new InheritanceHelper($this, $builder->getSchemas());
197
198
        return $helper->makeDefinition();
199
    }
200
201
    /**
202
     * Entity default values.
203
     *
204
     * @param SchemaBuilder $builder
205
     *
206
     * @return array
207
     */
208
    protected function buildDefaults(SchemaBuilder $builder): array
0 ignored issues
show
Unused Code introduced by
The parameter $builder is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
209
    {
210
        //User defined default values
211
        $userDefined = $this->reflection->getProperty('defaults');
212
213
        //We need mutators to normalize default values
214
        $mutators = $this->buildMutators();
215
216
        $defaults = [];
217
        foreach ($this->getFields() as $field => $type) {
218
            $default = is_array($type) ? [] : null;
219
220
            if (array_key_exists($field, $userDefined)) {
221
                //No merge to keep fields order intact
222
                $default = $userDefined[$field];
223
            }
224
225
            if (array_key_exists($field, $defaults)) {
226
                //Default value declared in model schema
227
                $default = $defaults[$field];
228
            }
229
230
            //Let's process default value using associated setter
231
            if (isset($mutators[DocumentEntity::MUTATOR_SETTER][$field])) {
232
                try {
233
                    $setter = $mutators[DocumentEntity::MUTATOR_SETTER][$field];
234
                    $default = call_user_func($setter, $default);
235
                } catch (\Exception $exception) {
236
                    //Unable to generate default value, use null or empty array as fallback
237
                }
238
            }
239
240
            //todo: default accessors
241
            //if (isset($accessors[$field])) {
1 ignored issue
show
Unused Code Comprehensibility introduced by
85% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
242
            //    $default = $this->accessorDefaults($accessors[$field], $type, $default);
1 ignored issue
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
243
            //}
244
245
            //todo: compositions
246
            //Using composition to resolve default value
247
            //if (!empty($this->getCompositions()[$field])) {
1 ignored issue
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
248
            //    $default = $this->compositionDefaults($field, $default);
1 ignored issue
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
249
            //}
250
251
            //Registering default values
252
            $defaults[$field] = $default;
253
        }
254
255
        return $defaults;
256
    }
257
258
    /**
259
     * Generate set of mutators associated with entity fields using user defined and automatic
260
     * mutators.
261
     *
262
     * @see MutatorsConfig
263
     * @return array
264
     */
265
    protected function buildMutators(): array
266
    {
267
        $mutators = $this->reflection->getMutators();
268
269
        //Trying to resolve mutators based on field type
270
        foreach ($this->getFields() as $field => $type) {
271
            //Resolved mutators
272
            $resolved = [];
273
274
            if (
275
                is_array($type)
276
                && is_scalar($type[0])
277
                && $filter = $this->mutators->getMutators('array::' . $type[0])
278
            ) {
279
                //Mutator associated to array with specified type
280
                $resolved += $filter;
281
            } elseif (is_array($type) && $filter = $this->mutators->getMutators('array')) {
282
                //Default array mutator
283
                $resolved += $filter;
284
            } elseif (!is_array($type) && $filter = $this->mutators->getMutators($type)) {
285
                //Mutator associated with type directly
286
                $resolved += $filter;
287
            }
288
289
            //Merging mutators and default mutators
290
            foreach ($resolved as $mutator => $filter) {
291
                if (!array_key_exists($field, $mutators[$mutator])) {
292
                    $mutators[$mutator][$field] = $filter;
293
                }
294
            }
295
        }
296
297
        //Some mutators may be described using aliases (for shortness)
1 ignored issue
show
Unused Code Comprehensibility introduced by
44% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
298
//        $mutators = $this->normalizeMutators($mutators);
299
//
300
//        //Every composition is counted as field accessor :)
301
//        foreach ($this->getCompositions() as $field => $composition) {
302
//            $mutators[AbstractEntity::MUTATOR_ACCESSOR][$field] = [
303
//                $composition['type'] == ODM::CMP_MANY ? Compositor::class : ODM::CMP_ONE,
304
//                $composition['class'],
305
//            ];
306
//        }
307
308
        return $mutators;
309
    }
310
311
    protected function buildCompositions(SchemaBuilder $builder): array
0 ignored issues
show
Unused Code introduced by
The parameter $builder is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
312
    {
313
        return [];
314
    }
315
316
    protected function buildAggregations(SchemaBuilder $builder): array
0 ignored issues
show
Unused Code introduced by
The parameter $builder is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
317
    {
318
319
        return [];
320
    }
321
322
    /**
323
     * Get every embedded entity field (excluding declarations of aggregations).
324
     *
325
     * @return array
326
     */
327
    protected function getFields(): array
328
    {
329
        return $this->reflection->getFields();
330
    }
331
}