InheritanceHelper   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 3
dl 0
loc 210
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B makeDefinition() 0 56 9
C findChildren() 0 33 8
B findPrimary() 0 21 6
A getReflection() 0 4 1
A compareCollection() 0 8 2
A sortChildren() 0 4 1
1
<?php
2
/**
3
 * Spiral, Core Components
4
 *
5
 * @author Wolfy-J
6
 */
7
8
namespace Spiral\ODM\Schemas;
9
10
use Spiral\Models\Reflections\ReflectionEntity;
11
use Spiral\ODM\Exceptions\DefinitionException;
12
13
/**
14
 * Helps to define sequence of fields needed to clearly distinguish one model from another based on
15
 * given data.
16
 */
17
class InheritanceHelper
18
{
19
    /**
20
     * @var DocumentSchema
21
     */
22
    private $schema;
23
24
    /**
25
     * All other registered schemas.
26
     *
27
     * @var SchemaInterface[]
28
     */
29
    private $schemas = [];
30
31
    /**
32
     * @param DocumentSchema    $schema
33
     * @param SchemaInterface[] $schemas
34
     */
35
    public function __construct(DocumentSchema $schema, array $schemas)
36
    {
37
        $this->schema = $schema;
38
        $this->schemas = $schemas;
39
    }
40
41
    /**
42
     * Compile information required to resolve class instance using given set of fields. Fields
43
     * based definition will analyze unique fields in every child model to create association
44
     * between model class and required set of fields. Only document from same collection will be
45
     * involved in definition creation. Definition built only for child of first order.
46
     *
47
     * @return array|string
48
     *
49
     * @throws DefinitionException
50
     */
51
    public function makeDefinition()
52
    {
53
        //Find only first level children stored in the same collection
54
        $children = $this->findChildren(true, true);
55
56
        if (empty($children)) {
57
            //Nothing to inherit
58
            return $this->schema->getClass();
59
        }
60
61
        //We must sort child in order or unique fields
62
        uasort($children, [$this, 'sortChildren']);
63
64
        //Fields which are common for parent and child models
65
        $commonFields = $this->schema->getReflection()->getSchema();
66
67
        $definition = [];
68
        foreach ($children as $schema) {
69
            //Child document fields
70
            $fields = $schema->getReflection()->getSchema();
71
72
            if (empty($fields)) {
73
                throw new DefinitionException(
74
                    "Child document '{$schema->getClass()}' of '{$this->schema->getClass()}' does not have any fields"
75
                );
76
            }
77
78
            $uniqueField = null;
79
            if (empty($commonFields)) {
80
                //Parent did not declare any fields, happen sometimes
81
                $commonFields = $fields;
82
                $uniqueField = key($fields);
83
            } else {
84
                foreach ($fields as $field => $type) {
85
                    if (!isset($commonFields[$field])) {
86
                        if (empty($uniqueField)) {
87
                            $uniqueField = $field;
88
                        }
89
90
                        //New non unique field (must be excluded from analysis)
91
                        $commonFields[$field] = true;
92
                    }
93
                }
94
            }
95
96
            if (empty($uniqueField)) {
97
                throw new DefinitionException(
98
                    "Child document '{$schema->getClass()}' of '{$this->schema->getClass()}' does not have any unique field"
99
                );
100
            }
101
102
            $definition[$uniqueField] = $schema->getClass();
103
        }
104
105
        return $definition;
106
    }
107
108
    /**
109
     * Get Document child classes.
110
     *
111
     * Example:
112
     * Class A
113
     * Class B extends A
114
     * Class D extends A
115
     * Class E extends D
116
     *
117
     * Result: B, D, E
118
     *
119
     * @see getPrimary()
120
     *
121
     * @param bool $sameCollection Find only children related to same collection as parent.
122
     * @param bool $directChildren Only child extended directly from current document.
123
     *
124
     * @return DocumentSchema[]
125
     */
126
    public function findChildren(bool $sameCollection = false, bool $directChildren = false)
127
    {
128
        $result = [];
129
        foreach ($this->schemas as $schema) {
130
            //Only Document and DocumentEntity classes supported
131
            if (!$schema instanceof DocumentSchema) {
132
                continue;
133
            }
134
135
            //ReflectionEntity
136
            $reflection = $schema->getReflection();
137
138
            if ($reflection->isSubclassOf($this->schema->getClass())) {
139
140
                if ($sameCollection && !$this->compareCollection($schema)) {
141
                    //Child changed collection or database
142
                    continue;
143
                }
144
145
                if (
146
                    $directChildren
147
                    && $reflection->getParentClass()->getName() != $this->schema->getClass()
148
                ) {
149
                    //Grandson
150
                    continue;
151
                }
152
153
                $result[] = $schema;
154
            }
155
        }
156
157
        return $result;
158
    }
159
160
    /**
161
     * Find primary class needed to represent model and model childs.
162
     *
163
     * @param bool $sameCollection Find only parent related to same collection as model.
164
     *
165
     * @return string
166
     */
167
    public function findPrimary(bool $sameCollection = true): string
168
    {
169
        $primary = $this->schema->getClass();
170
        foreach ($this->schemas as $schema) {
171
            //Only Document and DocumentEntity classes supported
172
            if (!$schema instanceof DocumentSchema) {
173
                continue;
174
            }
175
176
            if ($this->schema->getReflection()->isSubclassOf($schema->getClass())) {
177
                if ($sameCollection && !$this->compareCollection($schema)) {
178
                    //Child changed collection or database
179
                    continue;
180
                }
181
182
                $primary = $schema->getClass();
183
            }
184
        }
185
186
        return $primary;
187
    }
188
189
    /**
190
     * @return ReflectionEntity
191
     */
192
    protected function getReflection(): ReflectionEntity
193
    {
194
        return $this->schema->getReflection();
195
    }
196
197
    /**
198
     * Check if both document schemas belongs to same collection. Documents without declared
199
     * collection must be counted as documents from same collection.
200
     *
201
     * @param DocumentSchema $document
202
     *
203
     * @return bool
204
     */
205
    protected function compareCollection(DocumentSchema $document)
206
    {
207
        if ($document->getDatabase() != $this->schema->getDatabase()) {
208
            return false;
209
        }
210
211
        return $document->getCollection() == $this->schema->getCollection();
212
    }
213
214
    /**
215
     * Sort child documents in order or declared fields.
216
     *
217
     * @param DocumentSchema $childA
218
     * @param DocumentSchema $childB
219
     *
220
     * @return int
221
     */
222
    private function sortChildren(DocumentSchema $childA, DocumentSchema $childB)
223
    {
224
        return count($childA->getReflection()->getSchema()) > count($childB->getReflection()->getSchema());
225
    }
226
}