Complex classes like DefaultPersistentCollectionGenerator 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 DefaultPersistentCollectionGenerator, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
33 | final class DefaultPersistentCollectionGenerator implements PersistentCollectionGenerator |
||
34 | { |
||
35 | /** |
||
36 | * The namespace that contains all persistent collection classes. |
||
37 | * |
||
38 | * @var string |
||
39 | */ |
||
40 | private $collectionNamespace; |
||
41 | |||
42 | /** |
||
43 | * The directory that contains all persistent collection classes. |
||
44 | * |
||
45 | * @var string |
||
46 | */ |
||
47 | private $collectionDir; |
||
48 | |||
49 | /** |
||
50 | * @param string $collectionDir |
||
51 | * @param string $collectionNs |
||
52 | */ |
||
53 | 11 | public function __construct($collectionDir, $collectionNs) |
|
54 | { |
||
55 | 11 | $this->collectionDir = $collectionDir; |
|
56 | 11 | $this->collectionNamespace = $collectionNs; |
|
57 | 11 | } |
|
58 | |||
59 | /** |
||
60 | * {@inheritdoc} |
||
61 | */ |
||
62 | public function generateClass($class, $dir) |
||
63 | { |
||
64 | $collClassName = str_replace('\\', '', $class) . 'Persistent'; |
||
65 | $className = $this->collectionNamespace . '\\' . $collClassName; |
||
66 | $fileName = $dir . DIRECTORY_SEPARATOR . $collClassName . '.php'; |
||
67 | $this->generateCollectionClass($class, $className, $fileName); |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * {@inheritdoc} |
||
72 | */ |
||
73 | 10 | public function loadClass($collectionClass, $autoGenerate) |
|
74 | { |
||
75 | // These checks are not in __construct() because of BC and should be moved for 2.0 |
||
76 | 10 | if (! $this->collectionDir) { |
|
77 | throw PersistentCollectionException::directoryRequired(); |
||
78 | } |
||
79 | 10 | if (! $this->collectionNamespace) { |
|
80 | throw PersistentCollectionException::namespaceRequired(); |
||
81 | } |
||
82 | |||
83 | 10 | $collClassName = str_replace('\\', '', $collectionClass) . 'Persistent'; |
|
84 | 10 | $className = $this->collectionNamespace . '\\' . $collClassName; |
|
85 | 10 | if (! class_exists($className, false)) { |
|
86 | 6 | $fileName = $this->collectionDir . DIRECTORY_SEPARATOR . $collClassName . '.php'; |
|
87 | 6 | switch ($autoGenerate) { |
|
88 | case Configuration::AUTOGENERATE_NEVER: |
||
89 | require $fileName; |
||
90 | break; |
||
91 | |||
92 | case Configuration::AUTOGENERATE_ALWAYS: |
||
93 | 3 | $this->generateCollectionClass($collectionClass, $className, $fileName); |
|
94 | 3 | require $fileName; |
|
95 | 3 | break; |
|
96 | |||
97 | case Configuration::AUTOGENERATE_FILE_NOT_EXISTS: |
||
98 | if (! file_exists($fileName)) { |
||
99 | $this->generateCollectionClass($collectionClass, $className, $fileName); |
||
100 | } |
||
101 | require $fileName; |
||
102 | break; |
||
103 | |||
104 | case Configuration::AUTOGENERATE_EVAL: |
||
105 | 3 | $this->generateCollectionClass($collectionClass, $className, false); |
|
106 | 3 | break; |
|
107 | } |
||
108 | } |
||
109 | |||
110 | 10 | return $className; |
|
111 | } |
||
112 | |||
113 | 6 | private function generateCollectionClass($for, $targetFqcn, $fileName) |
|
114 | { |
||
115 | 6 | $exploded = explode('\\', $targetFqcn); |
|
116 | 6 | $class = array_pop($exploded); |
|
117 | 6 | $namespace = implode('\\', $exploded); |
|
118 | $code = <<<CODE |
||
119 | <?php |
||
120 | |||
121 | 6 | namespace $namespace; |
|
122 | |||
123 | use Doctrine\Common\Collections\Collection as BaseCollection; |
||
124 | use Doctrine\ODM\MongoDB\DocumentManager; |
||
125 | use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
||
126 | use Doctrine\ODM\MongoDB\MongoDBException; |
||
127 | use Doctrine\ODM\MongoDB\UnitOfWork; |
||
128 | use Doctrine\ODM\MongoDB\Utility\CollectionHelper; |
||
129 | |||
130 | /** |
||
131 | * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PERSISTENT COLLECTION GENERATOR |
||
132 | */ |
||
133 | 6 | class $class extends \\$for implements \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionInterface |
|
134 | { |
||
135 | use \\Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait; |
||
136 | |||
137 | /** |
||
138 | * @param BaseCollection \$coll |
||
139 | * @param DocumentManager \$dm |
||
140 | * @param UnitOfWork \$uow |
||
141 | */ |
||
142 | public function __construct(BaseCollection \$coll, DocumentManager \$dm, UnitOfWork \$uow) |
||
143 | { |
||
144 | \$this->coll = \$coll; |
||
145 | \$this->dm = \$dm; |
||
146 | \$this->uow = \$uow; |
||
147 | } |
||
148 | |||
149 | CODE; |
||
150 | 6 | $rc = new \ReflectionClass($for); |
|
151 | 6 | $rt = new \ReflectionClass('Doctrine\\ODM\\MongoDB\\PersistentCollection\\PersistentCollectionTrait'); |
|
152 | 6 | foreach ($rc->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { |
|
153 | 6 | if ($rt->hasMethod($method->name) || |
|
154 | 6 | $method->isConstructor() || |
|
155 | 6 | $method->isFinal() || |
|
156 | 6 | $method->isStatic() |
|
157 | ) { |
||
158 | 6 | continue; |
|
159 | } |
||
160 | 6 | $code .= $this->generateMethod($method); |
|
161 | } |
||
162 | 6 | $code .= "}\n"; |
|
163 | |||
164 | 6 | if ($fileName === false) { |
|
165 | 3 | if (! class_exists($targetFqcn)) { |
|
166 | 3 | eval(substr($code, 5)); |
|
167 | } |
||
168 | } else { |
||
169 | 3 | $parentDirectory = dirname($fileName); |
|
170 | |||
171 | 3 | if (! is_dir($parentDirectory) && (@mkdir($parentDirectory, 0775, true) === false)) { |
|
172 | throw PersistentCollectionException::directoryNotWritable(); |
||
173 | } |
||
174 | |||
175 | 3 | if (! is_writable($parentDirectory)) { |
|
176 | throw PersistentCollectionException::directoryNotWritable(); |
||
177 | } |
||
178 | |||
179 | 3 | $tmpFileName = $fileName . '.' . uniqid('', true); |
|
180 | 3 | file_put_contents($tmpFileName, $code); |
|
181 | 3 | rename($tmpFileName, $fileName); |
|
182 | } |
||
183 | 6 | } |
|
184 | |||
185 | 6 | private function generateMethod(\ReflectionMethod $method) |
|
186 | { |
||
187 | 6 | $parametersString = $this->buildParametersString($method); |
|
188 | 6 | $callParamsString = implode(', ', $this->getParameterNamesForDecoratedCall($method->getParameters())); |
|
189 | |||
190 | $method = <<<CODE |
||
191 | |||
192 | /** |
||
193 | * {@inheritDoc} |
||
194 | */ |
||
195 | 6 | public function {$method->name}($parametersString){$this->getMethodReturnType($method)} |
|
196 | { |
||
197 | \$this->initialize(); |
||
198 | if (\$this->needsSchedulingForDirtyCheck()) { |
||
199 | \$this->changed(); |
||
200 | } |
||
201 | 6 | return \$this->coll->{$method->name}($callParamsString); |
|
202 | } |
||
203 | |||
204 | CODE; |
||
205 | 6 | return $method; |
|
206 | } |
||
207 | |||
208 | /** |
||
209 | * |
||
210 | * @return string |
||
211 | */ |
||
212 | 6 | private function buildParametersString(\ReflectionMethod $method) |
|
213 | { |
||
214 | 6 | $parameters = $method->getParameters(); |
|
215 | 6 | $parameterDefinitions = []; |
|
216 | |||
217 | /* @var $param \ReflectionParameter */ |
||
218 | 6 | foreach ($parameters as $param) { |
|
219 | 6 | $parameterDefinition = ''; |
|
220 | 6 | $parameterType = $this->getParameterType($param); |
|
|
|||
221 | |||
222 | 6 | if ($parameterType) { |
|
223 | 6 | $parameterDefinition .= $parameterType . ' '; |
|
224 | } |
||
225 | |||
226 | 6 | if ($param->isPassedByReference()) { |
|
227 | $parameterDefinition .= '&'; |
||
228 | } |
||
229 | |||
230 | 6 | if (method_exists($param, 'isVariadic')) { |
|
231 | 6 | if ($param->isVariadic()) { |
|
232 | $parameterDefinition .= '...'; |
||
233 | } |
||
234 | } |
||
235 | |||
236 | 6 | $parameters[] = '$' . $param->name; |
|
237 | 6 | $parameterDefinition .= '$' . $param->name; |
|
238 | |||
239 | 6 | if ($param->isDefaultValueAvailable()) { |
|
240 | $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true); |
||
241 | } |
||
242 | |||
243 | 6 | $parameterDefinitions[] = $parameterDefinition; |
|
244 | } |
||
245 | |||
246 | 6 | return implode(', ', $parameterDefinitions); |
|
247 | } |
||
248 | |||
249 | /** |
||
250 | * |
||
251 | * @return string|null |
||
252 | */ |
||
253 | 6 | private function getParameterType(\ReflectionParameter $parameter) |
|
277 | |||
278 | /** |
||
279 | * @param \ReflectionParameter[] $parameters |
||
280 | * |
||
281 | * @return string[] |
||
282 | */ |
||
283 | 6 | private function getParameterNamesForDecoratedCall(array $parameters) |
|
302 | |||
303 | /** |
||
304 | * |
||
305 | * @return string |
||
306 | * |
||
307 | * @see \Doctrine\Common\Proxy\ProxyGenerator::getMethodReturnType() |
||
308 | */ |
||
309 | 6 | private function getMethodReturnType(\ReflectionMethod $method) |
|
316 | |||
317 | /** |
||
318 | * |
||
319 | * @return string |
||
320 | * |
||
321 | * @see \Doctrine\Common\Proxy\ProxyGenerator::formatType() |
||
322 | */ |
||
323 | 2 | private function formatType( |
|
359 | } |
||
360 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.