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 |
||
24 | final class SchemaManager |
||
25 | { |
||
26 | private const GRIDFS_FILE_COLLECTION_INDEX = ['files_id' => 1, 'n' => 1]; |
||
27 | |||
28 | private const GRIDFS_CHUNKS_COLLECTION_INDEX = ['filename' => 1, 'uploadDate' => 1]; |
||
29 | |||
30 | private const CODE_SHARDING_ALREADY_INITIALIZED = 23; |
||
31 | |||
32 | private const ALLOWED_MISSING_INDEX_OPTIONS = [ |
||
33 | 'partialFilterExpression', |
||
34 | 'sparse', |
||
35 | 'unique', |
||
36 | 'weights', |
||
37 | 'default_language', |
||
38 | 'language_override', |
||
39 | 'textIndexVersion', |
||
40 | ]; |
||
41 | |||
42 | /** @var DocumentManager */ |
||
43 | protected $dm; |
||
44 | |||
45 | /** @var ClassMetadataFactory */ |
||
46 | protected $metadataFactory; |
||
47 | |||
48 | 1769 | public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf) |
|
53 | |||
54 | /** |
||
55 | * Ensure indexes are created for all documents that can be loaded with the |
||
56 | * metadata factory. |
||
57 | */ |
||
58 | 5 | public function ensureIndexes(?int $maxTimeMs = null, ?WriteConcern $writeConcern = null, bool $background = false) : void |
|
59 | { |
||
60 | 5 | foreach ($this->metadataFactory->getAllMetadata() as $class) { |
|
61 | 5 | assert($class instanceof ClassMetadata); |
|
62 | 5 | if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
|
63 | 5 | continue; |
|
64 | } |
||
65 | |||
66 | 5 | $this->ensureDocumentIndexes($class->name, $maxTimeMs, $writeConcern, $background); |
|
67 | } |
||
68 | 5 | } |
|
69 | |||
70 | /** |
||
71 | * Ensure indexes exist for all mapped document classes. |
||
72 | * |
||
73 | * Indexes that exist in MongoDB but not the document metadata will be |
||
74 | * deleted. |
||
75 | */ |
||
76 | public function updateIndexes(?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
||
77 | { |
||
78 | foreach ($this->metadataFactory->getAllMetadata() as $class) { |
||
79 | assert($class instanceof ClassMetadata); |
||
80 | if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
||
81 | continue; |
||
82 | } |
||
83 | |||
84 | $this->updateDocumentIndexes($class->name, $maxTimeMs, $writeConcern); |
||
85 | } |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Ensure indexes exist for the mapped document class. |
||
90 | * |
||
91 | * Indexes that exist in MongoDB but not the document metadata will be |
||
92 | * deleted. |
||
93 | * |
||
94 | * @throws InvalidArgumentException |
||
95 | */ |
||
96 | 9 | public function updateDocumentIndexes(string $documentName, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
97 | { |
||
98 | 9 | $class = $this->dm->getClassMetadata($documentName); |
|
99 | |||
100 | 9 | if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
|
101 | throw new InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.'); |
||
102 | } |
||
103 | |||
104 | 9 | $documentIndexes = $this->getDocumentIndexes($documentName); |
|
105 | 9 | $collection = $this->dm->getDocumentCollection($documentName); |
|
106 | 9 | $mongoIndexes = iterator_to_array($collection->listIndexes()); |
|
107 | |||
108 | /* Determine which Mongo indexes should be deleted. Exclude the ID index |
||
109 | * and those that are equivalent to any in the class metadata. |
||
110 | */ |
||
111 | 9 | $self = $this; |
|
112 | $mongoIndexes = array_filter($mongoIndexes, static function (IndexInfo $mongoIndex) use ($documentIndexes, $self) { |
||
113 | 4 | if ($mongoIndex['name'] === '_id_') { |
|
114 | return false; |
||
115 | } |
||
116 | |||
117 | 4 | foreach ($documentIndexes as $documentIndex) { |
|
118 | 4 | if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) { |
|
119 | return false; |
||
120 | } |
||
121 | } |
||
122 | |||
123 | 4 | return true; |
|
124 | 9 | }); |
|
125 | |||
126 | // Delete indexes that do not exist in class metadata |
||
127 | 9 | foreach ($mongoIndexes as $mongoIndex) { |
|
128 | 4 | if (! isset($mongoIndex['name'])) { |
|
129 | continue; |
||
130 | } |
||
131 | |||
132 | 4 | $collection->dropIndex($mongoIndex['name'], $this->getWriteOptions($maxTimeMs, $writeConcern)); |
|
133 | } |
||
134 | |||
135 | 9 | $this->ensureDocumentIndexes($documentName, $maxTimeMs, $writeConcern); |
|
136 | 9 | } |
|
137 | |||
138 | 47 | public function getDocumentIndexes(string $documentName) : array |
|
143 | |||
144 | 47 | private function doGetDocumentIndexes(string $documentName, array &$visited) : array |
|
206 | |||
207 | 47 | private function prepareIndexes(ClassMetadata $class) : array |
|
208 | { |
||
209 | 47 | $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name); |
|
234 | |||
235 | /** |
||
236 | * Ensure the given document's indexes are created. |
||
237 | * |
||
238 | * @throws InvalidArgumentException |
||
239 | */ |
||
240 | 43 | public function ensureDocumentIndexes(string $documentName, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null, bool $background = false) : void |
|
261 | |||
262 | /** |
||
263 | * Delete indexes for all documents that can be loaded with the |
||
264 | * metadata factory. |
||
265 | */ |
||
266 | 4 | public function deleteIndexes(?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
277 | |||
278 | /** |
||
279 | * Delete the given document's indexes. |
||
280 | * |
||
281 | * @throws InvalidArgumentException |
||
282 | */ |
||
283 | 8 | public function deleteDocumentIndexes(string $documentName, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
292 | |||
293 | /** |
||
294 | * Create all the mapped document collections in the metadata factory. |
||
295 | */ |
||
296 | 4 | public function createCollections(?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
306 | |||
307 | /** |
||
308 | * Create the document collection for a mapped class. |
||
309 | * |
||
310 | * @throws InvalidArgumentException |
||
311 | */ |
||
312 | 14 | public function createDocumentCollection(string $documentName, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
340 | |||
341 | /** |
||
342 | * Drop all the mapped document collections in the metadata factory. |
||
343 | */ |
||
344 | 4 | public function dropCollections(?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
355 | |||
356 | /** |
||
357 | * Drop the document collection for a mapped class. |
||
358 | * |
||
359 | * @throws InvalidArgumentException |
||
360 | */ |
||
361 | 14 | public function dropDocumentCollection(string $documentName, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
378 | |||
379 | /** |
||
380 | * Drop all the mapped document databases in the metadata factory. |
||
381 | */ |
||
382 | 4 | public function dropDatabases(?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
393 | |||
394 | /** |
||
395 | * Drop the document database for a mapped class. |
||
396 | * |
||
397 | * @throws InvalidArgumentException |
||
398 | */ |
||
399 | 8 | public function dropDocumentDatabase(string $documentName, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null) : void |
|
408 | |||
409 | 47 | public function isMongoIndexEquivalentToDocumentIndex(IndexInfo $mongoIndex, array $documentIndex) : bool |
|
413 | |||
414 | /** |
||
415 | * Determine if the keys for a MongoDB index can be considered equivalent to |
||
416 | * those for an index in class metadata. |
||
417 | */ |
||
418 | 47 | private function isEquivalentIndexKeys(IndexInfo $mongoIndex, array $documentIndex) : bool |
|
442 | |||
443 | /** |
||
444 | * Determine if an index returned by MongoCollection::getIndexInfo() can be |
||
445 | * considered equivalent to an index in class metadata based on options. |
||
446 | * |
||
447 | * Indexes are considered different if: |
||
448 | * |
||
449 | * (a) Key/direction pairs differ or are not in the same order |
||
450 | * (b) Sparse or unique options differ |
||
451 | * (c) Geospatial options differ (bits, max, min) |
||
452 | * (d) The partialFilterExpression differs |
||
453 | * |
||
454 | * The background option is only relevant to index creation and is not |
||
455 | * considered. |
||
456 | */ |
||
457 | 40 | private function isEquivalentIndexOptions(IndexInfo $mongoIndex, array $documentIndex) : bool |
|
507 | |||
508 | /** |
||
509 | * Checks if any index options are missing. |
||
510 | * |
||
511 | * Options added to the ALLOWED_MISSING_INDEX_OPTIONS constant are ignored |
||
512 | * and are expected to be checked later |
||
513 | */ |
||
514 | 40 | private function indexOptionsAreMissing(array $mongoIndexOptions, array $documentIndexOptions) : bool |
|
522 | |||
523 | /** |
||
524 | * Determine if the text index weights for a MongoDB index can be considered |
||
525 | * equivalent to those for an index in class metadata. |
||
526 | */ |
||
527 | 14 | private function isEquivalentTextIndexWeights(IndexInfo $mongoIndex, array $documentIndex) : bool |
|
552 | |||
553 | /** |
||
554 | * Ensure collections are sharded for all documents that can be loaded with the |
||
555 | * metadata factory. |
||
556 | * |
||
557 | * @throws MongoDBException |
||
558 | */ |
||
559 | public function ensureSharding(?WriteConcern $writeConcern = null) : void |
||
570 | |||
571 | /** |
||
572 | * Ensure sharding for collection by document name. |
||
573 | * |
||
574 | * @throws MongoDBException |
||
575 | */ |
||
576 | 12 | public function ensureDocumentSharding(string $documentName, ?WriteConcern $writeConcern = null) : void |
|
595 | |||
596 | /** |
||
597 | * Enable sharding for database which contains documents with given name. |
||
598 | * |
||
599 | * @throws MongoDBException |
||
600 | */ |
||
601 | 12 | public function enableShardingForDbByDocumentName(string $documentName) : void |
|
617 | |||
618 | 12 | private function runShardCollectionCommand(string $documentName, ?WriteConcern $writeConcern = null) : array |
|
651 | |||
652 | 10 | private function ensureGridFSIndexes(ClassMetadata $class, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null, bool $background = false) : void |
|
657 | |||
658 | 10 | private function ensureChunksIndex(ClassMetadata $class, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null, bool $background = false) : void |
|
672 | |||
673 | 10 | private function ensureFilesIndex(ClassMetadata $class, ?int $maxTimeMs = null, ?WriteConcern $writeConcern = null, bool $background = false) : void |
|
684 | |||
685 | 12 | private function collectionIsSharded(string $documentName) : bool |
|
698 | |||
699 | 93 | private function getWriteOptions(?int $maxTimeMs = null, ?WriteConcern $writeConcern = null, array $options = []) : array |
|
713 | } |
||
714 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.