Completed
Pull Request — 1.0.x (#1431)
by Andreas
05:30
created

SchemaManager   D

Complexity

Total Complexity 101

Size/Duplication

Total Lines 466
Duplicated Lines 20.82 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 82.76%

Importance

Changes 0
Metric Value
wmc 101
lcom 1
cbo 7
dl 97
loc 466
ccs 192
cts 232
cp 0.8276
rs 4.8717
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A ensureIndexes() 9 9 4
A updateIndexes() 9 9 4
C updateDocumentIndexes() 0 45 8
A getDocumentIndexes() 0 5 1
C doGetDocumentIndexes() 0 52 16
B prepareIndexes() 0 26 4
C ensureDocumentIndexes() 0 35 14
A deleteIndexes() 9 9 4
A deleteDocumentIndexes() 8 8 3
A createCollections() 9 9 4
B createDocumentCollection() 0 22 4
A dropCollections() 9 9 4
A dropDocumentCollection() 10 10 3
A dropDatabases() 9 9 4
A dropDocumentDatabase() 8 8 3
A createDatabases() 9 9 4
A createDocumentDatabase() 8 8 3
C isMongoIndexEquivalentToDocumentIndex() 0 36 13

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SchemaManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB;
21
22
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
23
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
24
25
class SchemaManager
26
{
27
    /**
28
     * @var DocumentManager
29
     */
30
    protected $dm;
31
32
    /**
33
     *
34
     * @var ClassMetadataFactory
35
     */
36
    protected $metadataFactory;
37
38
    /**
39
     * @param DocumentManager $dm
40
     * @param ClassMetadataFactory $cmf
41
     */
42 957
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
43
    {
44 957
        $this->dm = $dm;
45 957
        $this->metadataFactory = $cmf;
46 957
    }
47
48
    /**
49
     * Ensure indexes are created for all documents that can be loaded with the
50
     * metadata factory.
51
     *
52
     * @param integer $timeout Timeout (ms) for acknowledged index creation
53
     */
54 1 View Code Duplication
    public function ensureIndexes($timeout = null)
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...
55
    {
56 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
57 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
58 1
                continue;
59
            }
60 1
            $this->ensureDocumentIndexes($class->name, $timeout);
61 1
        }
62 1
    }
63
64
    /**
65
     * Ensure indexes exist for all mapped document classes.
66
     *
67
     * Indexes that exist in MongoDB but not the document metadata will be
68
     * deleted.
69
     *
70
     * @param integer $timeout Timeout (ms) for acknowledged index creation
71
     */
72 View Code Duplication
    public function updateIndexes($timeout = null)
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...
73
    {
74
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
75
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
76
                continue;
77
            }
78
            $this->updateDocumentIndexes($class->name, $timeout);
79
        }
80
    }
81
82
    /**
83
     * Ensure indexes exist for the mapped document class.
84
     *
85
     * Indexes that exist in MongoDB but not the document metadata will be
86
     * deleted.
87
     *
88
     * @param string $documentName
89
     * @param integer $timeout Timeout (ms) for acknowledged index creation
90
     * @throws \InvalidArgumentException
91
     */
92 3
    public function updateDocumentIndexes($documentName, $timeout = null)
93
    {
94 3
        $class = $this->dm->getClassMetadata($documentName);
95
96 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
97
            throw new \InvalidArgumentException('Cannot update document indexes for mapped super classes or embedded documents.');
98
        }
99
100 3
        $documentIndexes = $this->getDocumentIndexes($documentName);
101 3
        $collection = $this->dm->getDocumentCollection($documentName);
102 3
        $mongoIndexes = $collection->getIndexInfo();
103
104
        /* Determine which Mongo indexes should be deleted. Exclude the ID index
105
         * and those that are equivalent to any in the class metadata.
106
         */
107 3
        $self = $this;
108 3
        $mongoIndexes = array_filter($mongoIndexes, function ($mongoIndex) use ($documentIndexes, $self) {
109 1
            if ('_id_' === $mongoIndex['name']) {
110
                return false;
111
            }
112
113 1
            foreach ($documentIndexes as $documentIndex) {
114 1
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
115
                    return false;
116
                }
117 1
            }
118
119 1
            return true;
120 3
        });
121
122
        // Delete indexes that do not exist in class metadata
123 3
        foreach ($mongoIndexes as $mongoIndex) {
124 1
            if (isset($mongoIndex['name'])) {
125
                /* Note: MongoCollection::deleteIndex() cannot delete
126
                 * custom-named indexes, so use the deleteIndexes command.
127
                 */
128 1
                $collection->getDatabase()->command(array(
129 1
                    'deleteIndexes' => $collection->getName(),
130 1
                    'index' => $mongoIndex['name'],
131 1
                ));
132 1
            }
133 3
        }
134
135 3
        $this->ensureDocumentIndexes($documentName, $timeout);
136 3
    }
137
138
    /**
139
     * @param string $documentName
140
     * @return array
141
     */
142 47
    public function getDocumentIndexes($documentName)
143
    {
144 47
        $visited = array();
145 47
        return $this->doGetDocumentIndexes($documentName, $visited);
146
    }
147
148
    /**
149
     * @param string $documentName
150
     * @param array $visited
151
     * @return array
152
     */
153 47
    private function doGetDocumentIndexes($documentName, array &$visited)
154
    {
155 47
        if (isset($visited[$documentName])) {
156 1
            return array();
157
        }
158
159 47
        $visited[$documentName] = true;
160
161 47
        $class = $this->dm->getClassMetadata($documentName);
162 47
        $indexes = $this->prepareIndexes($class);
163 47
        $embeddedDocumentIndexes = array();
164
165
        // Add indexes from embedded & referenced documents
166 47
        foreach ($class->fieldMappings as $fieldMapping) {
167 47
            if (isset($fieldMapping['embedded'])) {
168 31
                if (isset($fieldMapping['targetDocument'])) {
169 31
                    $possibleEmbeds = array($fieldMapping['targetDocument']);
170 31
                } elseif (isset($fieldMapping['discriminatorMap'])) {
171 1
                    $possibleEmbeds = array_unique($fieldMapping['discriminatorMap']);
172 1
                } else {
173 1
                    continue;
174
                }
175 31
                foreach ($possibleEmbeds as $embed) {
176 31
                    if (isset($embeddedDocumentIndexes[$embed])) {
177 25
                        $embeddedIndexes = $embeddedDocumentIndexes[$embed];
178 25
                    } else {
179 31
                        $embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
180 31
                        $embeddedDocumentIndexes[$embed] = $embeddedIndexes;
181
                    }
182 31
                    foreach ($embeddedIndexes as $embeddedIndex) {
183 25
                        foreach ($embeddedIndex['keys'] as $key => $value) {
184 25
                            $embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value;
185 25
                            unset($embeddedIndex['keys'][$key]);
186 25
                        }
187 25
                        $indexes[] = $embeddedIndex;
188 31
                    }
189 31
                }
190 47
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
191 31
                foreach ($indexes as $idx => $index) {
192 31
                    $newKeys = array();
193 31
                    foreach ($index['keys'] as $key => $v) {
194 31
                        if ($key == $fieldMapping['name']) {
195 2
                            $key = $fieldMapping['simple'] ? $key : $key . '.$id';
196 2
                        }
197 31
                        $newKeys[$key] = $v;
198 31
                    }
199 31
                    $indexes[$idx]['keys'] = $newKeys;
200 31
                }
201 31
            }
202 47
        }
203 47
        return $indexes;
204
    }
205
206
    /**
207
     * @param ClassMetadata $class
208
     * @return array
209
     */
210 47
    private function prepareIndexes(ClassMetadata $class)
211
    {
212 47
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
213 47
        $indexes = $class->getIndexes();
214 47
        $newIndexes = array();
215
216 47
        foreach ($indexes as $index) {
217
            $newIndex = array(
218 47
                'keys' => array(),
219 47
                'options' => $index['options']
220 47
            );
221 47
            foreach ($index['keys'] as $key => $value) {
222 47
                $key = $persister->prepareFieldName($key);
223 47
                if ($class->hasField($key)) {
224 45
                    $mapping = $class->getFieldMapping($key);
225 45
                    $newIndex['keys'][$mapping['name']] = $value;
226 45
                } else {
227 5
                    $newIndex['keys'][$key] = $value;
228
                }
229 47
            }
230
231 47
            $newIndexes[] = $newIndex;
232 47
        }
233
234 47
        return $newIndexes;
235
    }
236
237
    /**
238
     * Ensure the given document's indexes are created.
239
     *
240
     * @param string $documentName
241
     * @param integer $timeout Timeout (ms) for acknowledged index creation
242
     * @throws \InvalidArgumentException
243
     */
244 43
    public function ensureDocumentIndexes($documentName, $timeout = null)
245
    {
246 43
        $class = $this->dm->getClassMetadata($documentName);
247 43
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
248
            throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes or embedded documents.');
249
        }
250 43
        if ($indexes = $this->getDocumentIndexes($documentName)) {
251 43
            $collection = $this->dm->getDocumentCollection($class->name);
252 43
            foreach ($indexes as $index) {
253 43
                $keys = $index['keys'];
254 43
                $options = $index['options'];
255
256 43
                if ( ! isset($options['safe']) && ! isset($options['w'])) {
257 42
                    if (version_compare(phpversion('mongo'), '1.3.0', '<')) {
258
                        $options['safe'] = true;
259
                    } else {
260 42
                        $options['w'] = 1;
261
                    }
262 42
                }
263
264 43
                if (isset($options['safe']) && ! isset($options['w']) &&
265 43
                    version_compare(phpversion('mongo'), '1.3.0', '>=')) {
266
267 1
                    $options['w'] = is_bool($options['safe']) ? (integer) $options['safe'] : $options['safe'];
268 1
                    unset($options['safe']);
269 1
                }
270
271 43
                if ( ! isset($options['timeout']) && isset($timeout)) {
272 1
                    $options['timeout'] = $timeout;
273 1
                }
274
275 43
                $collection->ensureIndex($keys, $options);
276 43
            }
277 43
        }
278 43
    }
279
280
    /**
281
     * Delete indexes for all documents that can be loaded with the
282
     * metadata factory.
283
     */
284 1 View Code Duplication
    public function deleteIndexes()
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...
285
    {
286 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
287 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
288 1
                continue;
289
            }
290 1
            $this->deleteDocumentIndexes($class->name);
291 1
        }
292 1
    }
293
294
    /**
295
     * Delete the given document's indexes.
296
     *
297
     * @param string $documentName
298
     * @throws \InvalidArgumentException
299
     */
300 2 View Code Duplication
    public function deleteDocumentIndexes($documentName)
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...
301
    {
302 2
        $class = $this->dm->getClassMetadata($documentName);
303 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
304
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes or embedded documents.');
305
        }
306 2
        $this->dm->getDocumentCollection($documentName)->deleteIndexes();
307 2
    }
308
309
    /**
310
     * Create all the mapped document collections in the metadata factory.
311
     */
312 1 View Code Duplication
    public function createCollections()
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...
313
    {
314 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
315 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
316 1
                continue;
317
            }
318 1
            $this->createDocumentCollection($class->name);
319 1
        }
320 1
    }
321
322
    /**
323
     * Create the document collection for a mapped class.
324
     *
325
     * @param string $documentName
326
     * @throws \InvalidArgumentException
327
     */
328 4
    public function createDocumentCollection($documentName)
329
    {
330 4
        $class = $this->dm->getClassMetadata($documentName);
331
332 4
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
333
            throw new \InvalidArgumentException('Cannot create document collection for mapped super classes or embedded documents.');
334
        }
335
336 4
        if ($class->isFile()) {
337 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.files');
338 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.chunks');
339
340 2
            return;
341
        }
342
343 3
        $this->dm->getDocumentDatabase($documentName)->createCollection(
344 3
            $class->getCollection(),
345 3
            $class->getCollectionCapped(),
346 3
            $class->getCollectionSize(),
347 3
            $class->getCollectionMax()
348 3
        );
349 3
    }
350
351
    /**
352
     * Drop all the mapped document collections in the metadata factory.
353
     */
354 1 View Code Duplication
    public function dropCollections()
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...
355
    {
356 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
357 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
358 1
                continue;
359
            }
360 1
            $this->dropDocumentCollection($class->name);
361 1
        }
362 1
    }
363
364
    /**
365
     * Drop the document collection for a mapped class.
366
     *
367
     * @param string $documentName
368
     * @throws \InvalidArgumentException
369
     */
370 3 View Code Duplication
    public function dropDocumentCollection($documentName)
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...
371
    {
372 3
        $class = $this->dm->getClassMetadata($documentName);
373 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
374
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes or embedded documents.');
375
        }
376 3
        $this->dm->getDocumentDatabase($documentName)->dropCollection(
377 3
            $class->getCollection()
378 3
        );
379 3
    }
380
381
    /**
382
     * Drop all the mapped document databases in the metadata factory.
383
     */
384 1 View Code Duplication
    public function dropDatabases()
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...
385
    {
386 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
387 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
388 1
                continue;
389
            }
390 1
            $this->dropDocumentDatabase($class->name);
391 1
        }
392 1
    }
393
394
    /**
395
     * Drop the document database for a mapped class.
396
     *
397
     * @param string $documentName
398
     * @throws \InvalidArgumentException
399
     */
400 2 View Code Duplication
    public function dropDocumentDatabase($documentName)
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...
401
    {
402 2
        $class = $this->dm->getClassMetadata($documentName);
403 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
404
            throw new \InvalidArgumentException('Cannot drop document database for mapped super classes or embedded documents.');
405
        }
406 2
        $this->dm->getDocumentDatabase($documentName)->drop();
407 2
    }
408
409
    /**
410
     * Create all the mapped document databases in the metadata factory.
411
     */
412 View Code Duplication
    public function createDatabases()
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...
413
    {
414
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
415
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
416
                continue;
417
            }
418
            $this->createDocumentDatabase($class->name);
419
        }
420
    }
421
422
    /**
423
     * Create the document database for a mapped class.
424
     *
425
     * @param string $documentName
426
     * @throws \InvalidArgumentException
427
     */
428 1 View Code Duplication
    public function createDocumentDatabase($documentName)
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...
429
    {
430 1
        $class = $this->dm->getClassMetadata($documentName);
431 1
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
432
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes or embedded documents.');
433
        }
434 1
        $this->dm->getDocumentDatabase($documentName)->execute("function() { return true; }");
435 1
    }
436
437
    /**
438
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
439
     * considered equivalent to an index in class metadata.
440
     *
441
     * Indexes are considered different if:
442
     *
443
     *   (a) Key/direction pairs differ or are not in the same order
444
     *   (b) Sparse or unique options differ
445
     *   (c) Mongo index is unique without dropDups and mapped index is unique
446
     *       with dropDups
447
     *   (d) Geospatial options differ (bits, max, min)
448
     *
449
     * Regarding (c), the inverse case is not a reason to delete and
450
     * recreate the index, since dropDups only affects creation of
451
     * the unique index. Additionally, the background option is only
452
     * relevant to index creation and is not considered.
453
     */
454 1
    public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)
455
    {
456 1
        $documentIndexOptions = $documentIndex['options'];
457
458 1
        if ($mongoIndex['key'] != $documentIndex['keys']) {
459 1
            return false;
460
        }
461
462
        if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
463
            return false;
464
        }
465
466
        if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
467
            return false;
468
        }
469
470
        if ( ! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
471
            ! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) {
472
473
            return false;
474
        }
475
476
        foreach (array('bits', 'max', 'min') as $option) {
477
            if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
478
                return false;
479
            }
480
481
            if (isset($mongoIndex[$option]) && isset($documentIndexOptions[$option]) &&
482
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
483
484
                return false;
485
            }
486
        }
487
488
        return true;
489
    }
490
}
491