Completed
Branch dbal-improvement (e43d29)
by Anton
06:02
created

SchemaBuilder::locateDocuments()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 12
rs 9.2
cc 4
eloc 6
nc 3
nop 1
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 * @copyright �2009-2015
8
 */
9
namespace Spiral\ODM\Entities;
10
11
use Spiral\Core\Component;
12
use Spiral\ODM\Configs\ODMConfig;
13
use Spiral\ODM\Document;
14
use Spiral\ODM\DocumentEntity;
15
use Spiral\ODM\Entities\Schemas\CollectionSchema;
16
use Spiral\ODM\Entities\Schemas\DocumentSchema;
17
use Spiral\ODM\Exceptions\DefinitionException;
18
use Spiral\ODM\Exceptions\SchemaException;
19
use Spiral\ODM\ODM;
20
use Spiral\Tokenizer\ClassLocatorInterface;
21
22
/**
23
 * Schema builder responsible for static analysis of existed Documents, their schemas, validations,
24
 * requested indexes and etc.
25
 */
26
class SchemaBuilder extends Component
27
{
28
    /**
29
     * @var DocumentSchema[]
30
     */
31
    private $documents = [];
32
33
    /**
34
     * @var CollectionSchema[]
35
     */
36
    private $collections = [];
37
38
    /**
39
     * @var ODMConfig
40
     */
41
    protected $config = null;
42
43
    /**
44
     * @invisible
45
     * @var ODM
46
     */
47
    protected $odm = null;
48
49
    /**
50
     * @param ODM                   $odm
51
     * @param ODMConfig             $config
52
     * @param ClassLocatorInterface $locator
53
     */
54
    public function __construct(ODM $odm, ODMConfig $config, ClassLocatorInterface $locator)
55
    {
56
        $this->config = $config;
57
        $this->odm = $odm;
58
59
        $this->locateDocuments($locator)->locateSources($locator);
60
        $this->describeCollections();
61
    }
62
63
    /**
64
     * @return ODM
65
     */
66
    public function odm()
67
    {
68
        return $this->odm;
69
    }
70
71
    /**
72
     * Resolve database alias.
73
     *
74
     * @param string $database
75
     * @return string
76
     */
77
    public function databaseAlias($database)
78
    {
79
        if (empty($database)) {
80
            $database = $this->config->defaultDatabase();
81
        }
82
83
        //Spiral support ability to link multiple virtual databases together using aliases
84
        return $this->config->resolveAlias($database);
85
    }
86
87
    /**
88
     * Check if Document class known to schema builder.
89
     *
90
     * @param string $class
91
     * @return bool
92
     */
93
    public function hasDocument($class)
94
    {
95
        return isset($this->documents[$class]);
96
    }
97
98
    /**
99
     * Instance of DocumentSchema associated with given class name.
100
     *
101
     * @param string $class
102
     * @return DocumentSchema
103
     * @throws SchemaException
104
     */
105
    public function document($class)
106
    {
107
        if ($class == DocumentEntity::class || $class == Document::class) {
108
            //No need to remember schema for abstract Document
109
            return new DocumentSchema($this, DocumentEntity::class);
110
        }
111
112
        if (!isset($this->documents[$class])) {
113
            throw new SchemaException("Unknown document class '{$class}'.");
114
        }
115
116
        return $this->documents[$class];
117
    }
118
119
    /**
120
     * @return DocumentSchema[]
121
     */
122
    public function getDocuments()
123
    {
124
        return $this->documents;
125
    }
126
127
    /**
128
     * @return CollectionSchema[]
129
     */
130
    public function getCollections()
131
    {
132
        return $this->collections;
133
    }
134
135
    /**
136
     * Create every requested collection index.
137
     *
138
     * @throws \MongoException
139
     */
140
    public function createIndexes()
141
    {
142
        foreach ($this->getCollections() as $collection) {
143
            if (empty($indexes = $collection->getIndexes())) {
144
                continue;
145
            }
146
147
            $odmCollection = $this->odm->database(
148
                $collection->getDatabase()
149
            )->selectCollection(
150
                $collection->getName()
151
            );
152
153
            foreach ($indexes as $index) {
154
                $options = [];
155
                if (isset($index[DocumentEntity::INDEX_OPTIONS])) {
156
                    $options = $index[DocumentEntity::INDEX_OPTIONS];
157
                    unset($index[DocumentEntity::INDEX_OPTIONS]);
158
                }
159
160
                $odmCollection->createIndex($index, $options);
161
            }
162
        }
163
    }
164
165
    /**
166
     * Normalize document schema in lighter structure to be saved in ODM component memory.
167
     *
168
     * @return array
169
     * @throws SchemaException
170
     * @throws DefinitionException
171
     */
172
    public function normalizeSchema()
173
    {
174
        $result = [];
175
176
        //Pre-packing collections
177
        foreach ($this->getCollections() as $collection) {
178
            $name = $collection->getDatabase() . '/' . $collection->getName();
179
            $result[$name] = $collection->getParent()->getName();
180
        }
181
182
        foreach ($this->getDocuments() as $document) {
183
            if ($document->isAbstract()) {
184
                continue;
185
            }
186
187
            $schema = [
188
                ODM::D_DEFINITION   => $this->packDefinition($document->classDefinition()),
189
                ODM::D_SOURCE       => $document->getSource(),
190
                ODM::D_HIDDEN       => $document->getHidden(),
191
                ODM::D_SECURED      => $document->getSecured(),
192
                ODM::D_FILLABLE     => $document->getFillable(),
193
                ODM::D_MUTATORS     => $document->getMutators(),
194
                ODM::D_VALIDATES    => $document->getValidates(),
195
                ODM::D_DEFAULTS     => $document->getDefaults(),
196
                ODM::D_AGGREGATIONS => $this->packAggregations($document->getAggregations()),
197
                ODM::D_COMPOSITIONS => array_keys($document->getCompositions())
198
            ];
199
200
            if (!$document->isEmbeddable()) {
201
                $schema[ODM::D_COLLECTION] = $document->getCollection();
202
                $schema[ODM::D_DB] = $document->getDatabase();
203
            }
204
205
            ksort($schema);
206
            $result[$document->getName()] = $schema;
207
        }
208
209
        return $result;
210
    }
211
212
    /**
213
     * Get all mutators associated with field type.
214
     *
215
     * @param string $type Field type.
216
     * @return array
217
     */
218
    public function getMutators($type)
219
    {
220
        return $this->config->getMutators($type);
221
    }
222
223
    /**
224
     * Get mutator alias if presented. Aliases used to simplify schema definition.
225
     *
226
     * @param string $alias
227
     * @return string|array
228
     */
229
    public function mutatorAlias($alias)
230
    {
231
        return $this->config->mutatorAlias($alias);
232
    }
233
234
    /**
235
     * Locate every available Document class.
236
     *
237
     * @param ClassLocatorInterface $locator
238
     * @return $this
239
     */
240
    protected function locateDocuments(ClassLocatorInterface $locator)
241
    {
242
        foreach ($locator->getClasses(DocumentEntity::class) as $class => $definition) {
243
            if ($class == DocumentEntity::class || $class == Document::class) {
244
                continue;
245
            }
246
247
            $this->documents[$class] = new DocumentSchema($this, $class);
248
        }
249
250
        return $this;
251
    }
252
253
    /**
254
     * Locate ORM entities sources.
255
     *
256
     * @param ClassLocatorInterface $locator
257
     * @return $this
258
     */
259 View Code Duplication
    protected function locateSources(ClassLocatorInterface $locator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
260
    {
261
        foreach ($locator->getClasses(DocumentSource::class) as $class => $definition) {
262
            $reflection = new \ReflectionClass($class);
263
264
            if (
265
                $reflection->isAbstract()
266
                || empty($document = $reflection->getConstant('DOCUMENT'))
267
            ) {
268
                continue;
269
            }
270
271
            if ($this->hasDocument($document)) {
272
                //Associating source with record
273
                $this->document($document)->setSource($class);
274
            }
275
        }
276
277
        return $this;
278
    }
279
280
    /**
281
     * Create instances of CollectionSchema associated with found DocumentSchema instances.
282
     *
283
     * @throws SchemaException
284
     */
285
    protected function describeCollections()
286
    {
287
        foreach ($this->getDocuments() as $document) {
288
            if ($document->isEmbeddable()) {
289
                //Skip embedded models
290
                continue;
291
            }
292
293
            //Getting fully specified collection name (with specified db)
294
            $collection = $document->getDatabase() . '/' . $document->getCollection();
295
            if (isset($this->collections[$collection])) {
296
                //Already described by parent
297
                continue;
298
            }
299
300
            //Collection must be described by parent Document
301
            $parent = $document->getParent(true);
302
            $this->collections[$collection] = new CollectionSchema($parent);
303
        }
304
    }
305
306
    /**
307
     * Pack (normalize) class definition.
308
     *
309
     * @param mixed $definition
310
     * @return array|string
311
     */
312
    private function packDefinition($definition)
313
    {
314
        if (is_string($definition)) {
315
            //Single collection class
316
            return $definition;
317
        }
318
319
        return [
320
            ODM::DEFINITION         => $definition['type'],
321
            ODM::DEFINITION_OPTIONS => $definition['options']
322
        ];
323
    }
324
325
    /**
326
     * Pack (normalize) document aggregations.
327
     *
328
     * @param array $aggregations
329
     * @return array
330
     */
331
    private function packAggregations(array $aggregations)
332
    {
333
        $result = [];
334
        foreach ($aggregations as $name => $aggregation) {
335
            $result[$name] = [
336
                ODM::AGR_TYPE  => $aggregation['type'],
337
                ODM::ARG_CLASS => $aggregation['class'],
338
                ODM::AGR_QUERY => $aggregation['query']
339
            ];
340
        }
341
342
        return $result;
343
    }
344
}