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:
1 | <?php |
||
42 | class ReferencePrimer |
||
43 | { |
||
44 | /** |
||
45 | * The default primer Closure. |
||
46 | * |
||
47 | * @var \Closure |
||
48 | */ |
||
49 | private $defaultPrimer; |
||
50 | |||
51 | /** |
||
52 | * The DocumentManager instance. |
||
53 | * |
||
54 | * @var DocumentManager $dm |
||
55 | */ |
||
56 | private $dm; |
||
57 | |||
58 | /** |
||
59 | * The UnitOfWork instance. |
||
60 | * |
||
61 | * @var UnitOfWork |
||
62 | */ |
||
63 | private $uow; |
||
64 | |||
65 | /** |
||
66 | * Initializes this instance with the specified document manager and unit of work. |
||
67 | * |
||
68 | * @param DocumentManager $dm Document manager. |
||
69 | * @param UnitOfWork $uow Unit of work. |
||
70 | */ |
||
71 | 24 | public function __construct(DocumentManager $dm, UnitOfWork $uow) |
|
91 | |||
92 | |||
93 | /** |
||
94 | * Prime references within a mapped field of one or more documents. |
||
95 | * |
||
96 | * If a $primer callable is provided, it should have the same signature as |
||
97 | * the default primer defined in the constructor. If $primer is not |
||
98 | * callable, the default primer will be used. |
||
99 | * |
||
100 | * @param ClassMetadata $class Class metadata for the document |
||
101 | * @param array|\Traversable $documents Documents containing references to prime |
||
102 | * @param string $fieldName Field name containing references to prime |
||
103 | * @param array $hints UnitOfWork hints for priming queries |
||
104 | * @param callable $primer Optional primer callable |
||
105 | * @throws \InvalidArgumentException If the mapped field is not the owning |
||
106 | * side of a reference relationship. |
||
107 | * @throws \InvalidArgumentException If $primer is not callable |
||
108 | * @throws \LogicException If the mapped field is a simple reference and is |
||
109 | * missing a target document class. |
||
110 | */ |
||
111 | 21 | public function primeReferences(ClassMetadata $class, $documents, $fieldName, array $hints = array(), $primer = null) |
|
112 | { |
||
113 | 21 | $data = $this->parseDotSyntaxForPrimer($fieldName, $class, $documents); |
|
114 | 20 | $mapping = $data['mapping']; |
|
115 | 20 | $fieldName = $data['fieldName']; |
|
116 | 20 | $class = $data['class']; |
|
117 | 20 | $documents = $data['documents']; |
|
118 | |||
119 | /* Inverse-side references would need to be populated before we can |
||
120 | * collect references to be primed. This is not supported. |
||
121 | */ |
||
122 | 20 | if ( ! isset($mapping['reference']) || ! $mapping['isOwningSide']) { |
|
123 | 1 | throw new \InvalidArgumentException(sprintf('Field "%s" is not the owning side of a reference relationship in class "%s"', $fieldName, $class->name)); |
|
124 | } |
||
125 | |||
126 | /* Simple reference require a target document class so we can construct |
||
127 | * the priming query. |
||
128 | */ |
||
129 | 19 | if ($mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID && empty($mapping['targetDocument'])) { |
|
130 | throw new \LogicException(sprintf('Field "%s" is a simple reference without a target document class in class "%s"', $fieldName, $class->name)); |
||
131 | } |
||
132 | |||
133 | 19 | if ($primer !== null && ! is_callable($primer)) { |
|
134 | throw new \InvalidArgumentException('$primer is not callable'); |
||
135 | } |
||
136 | |||
137 | 19 | $primer = $primer ?: $this->defaultPrimer; |
|
138 | 19 | $groupedIds = array(); |
|
139 | |||
140 | /* @var $document PersistentCollectionInterface */ |
||
141 | 19 | foreach ($documents as $document) { |
|
142 | 19 | $fieldValue = $class->getFieldValue($document, $fieldName); |
|
143 | |||
144 | /* The field will need to be either a Proxy (reference-one) or |
||
145 | * PersistentCollection (reference-many) in order to prime anything. |
||
146 | */ |
||
147 | 19 | if ( ! is_object($fieldValue)) { |
|
148 | 2 | continue; |
|
149 | } |
||
150 | |||
151 | 17 | if ($mapping['type'] === 'one' && $fieldValue instanceof Proxy && ! $fieldValue->__isInitialized()) { |
|
152 | 12 | $refClass = $this->dm->getClassMetadata(get_class($fieldValue)); |
|
153 | 12 | $id = $this->uow->getDocumentIdentifier($fieldValue); |
|
154 | 12 | $groupedIds[$refClass->name][serialize($id)] = $id; |
|
155 | 10 | } elseif ($mapping['type'] == 'many' && $fieldValue instanceof PersistentCollectionInterface) { |
|
156 | 17 | $this->addManyReferences($fieldValue, $groupedIds); |
|
157 | } |
||
158 | } |
||
159 | |||
160 | 19 | foreach ($groupedIds as $className => $ids) { |
|
161 | 16 | $refClass = $this->dm->getClassMetadata($className); |
|
162 | 16 | call_user_func($primer, $this->dm, $refClass, array_values($ids), $hints); |
|
163 | } |
||
164 | 19 | } |
|
165 | |||
166 | /** |
||
167 | * If you are priming references inside an embedded document you'll need to parse the dot syntax. |
||
168 | * This method will traverse through embedded documents to find the reference to prime. |
||
169 | * However this method will not traverse through multiple layers of references. |
||
170 | * I.e. you can prime this: myDocument.embeddedDocument.embeddedDocuments.embeddedDocuments.referencedDocument(s) |
||
171 | * ... but you cannot prime this: myDocument.embeddedDocument.referencedDocuments.referencedDocument(s) |
||
172 | * This addresses Issue #624. |
||
173 | * |
||
174 | * @param string $fieldName |
||
175 | * @param ClassMetadata $class |
||
176 | * @param array|\Traversable $documents |
||
177 | * @param array $mapping |
||
178 | * @return array |
||
179 | */ |
||
180 | 21 | private function parseDotSyntaxForPrimer($fieldName, $class, $documents, $mapping = null) |
|
243 | |||
244 | /** |
||
245 | * Adds identifiers from a PersistentCollection to $groupedIds. |
||
246 | * |
||
247 | * If the relation contains simple references, the mapping is assumed to |
||
248 | * have a target document class defined. Without that, there is no way to |
||
249 | * infer the class of the referenced documents. |
||
250 | * |
||
251 | * @param PersistentCollectionInterface $persistentCollection |
||
252 | * @param array $groupedIds |
||
253 | */ |
||
254 | 10 | private function addManyReferences(PersistentCollectionInterface $persistentCollection, array &$groupedIds) |
|
280 | } |
||
281 |
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.