Completed
Branch feature/split-orm (60a911)
by Anton
03:15
created

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