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 classes like ActionsMenuBuilder 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 ActionsMenuBuilder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class ActionsMenuBuilder |
||
22 | { |
||
23 | /** |
||
24 | * @var FactoryInterface |
||
25 | */ |
||
26 | private $factory; |
||
27 | |||
28 | /** |
||
29 | * @var NodeVersion |
||
30 | */ |
||
31 | private $activeNodeVersion; |
||
32 | |||
33 | /** |
||
34 | * @var EntityManager |
||
35 | */ |
||
36 | private $em; |
||
37 | |||
38 | /** |
||
39 | * @var RouterInterface |
||
40 | */ |
||
41 | private $router; |
||
42 | |||
43 | /** |
||
44 | * @var EventDispatcherInterface |
||
45 | */ |
||
46 | private $dispatcher; |
||
47 | |||
48 | /** |
||
49 | * @var AuthorizationCheckerInterface |
||
50 | */ |
||
51 | private $authorizationChecker; |
||
52 | |||
53 | /** |
||
54 | * @var PagesConfiguration |
||
55 | */ |
||
56 | private $pagesConfiguration; |
||
57 | |||
58 | /** |
||
59 | * @var bool |
||
60 | */ |
||
61 | private $isEditableNode = true; |
||
62 | |||
63 | /** |
||
64 | * @var bool |
||
65 | */ |
||
66 | private $enableExportPageTemplate; |
||
67 | |||
68 | /** @var bool */ |
||
69 | private $showDuplicateWithChildren; |
||
70 | |||
71 | /** |
||
72 | * @param FactoryInterface $factory |
||
73 | * @param EntityManager $em |
||
74 | * @param RouterInterface $router |
||
75 | * @param EventDispatcherInterface $dispatcher |
||
76 | * @param AuthorizationCheckerInterface $authorizationChecker |
||
77 | * @param PagesConfiguration $pagesConfiguration |
||
78 | * @param bool $enableExportPageTemplate |
||
79 | * @param bool $showDuplicateWithChildren |
||
80 | */ |
||
81 | 8 | public function __construct( |
|
82 | FactoryInterface $factory, |
||
83 | EntityManager $em, |
||
|
|||
84 | RouterInterface $router, |
||
85 | EventDispatcherInterface $dispatcher, |
||
86 | AuthorizationCheckerInterface $authorizationChecker, |
||
87 | PagesConfiguration $pagesConfiguration, |
||
88 | $enableExportPageTemplate = true, |
||
89 | bool $showDuplicateWithChildren = false |
||
90 | ) { |
||
91 | 8 | $this->factory = $factory; |
|
92 | 8 | $this->em = $em; |
|
93 | 8 | $this->router = $router; |
|
94 | 8 | $this->dispatcher = $dispatcher; |
|
95 | 8 | $this->authorizationChecker = $authorizationChecker; |
|
96 | 8 | $this->pagesConfiguration = $pagesConfiguration; |
|
97 | 8 | $this->enableExportPageTemplate = $enableExportPageTemplate; |
|
98 | 8 | $this->showDuplicateWithChildren = $showDuplicateWithChildren; |
|
99 | 8 | } |
|
100 | |||
101 | /** |
||
102 | * @return ItemInterface |
||
103 | */ |
||
104 | 1 | public function createSubActionsMenu() |
|
105 | { |
||
106 | 1 | $activeNodeVersion = $this->getActiveNodeVersion(); |
|
107 | 1 | $menu = $this->factory->createItem('root'); |
|
108 | 1 | $menu->setChildrenAttribute('class', 'page-sub-actions'); |
|
109 | |||
110 | 1 | if (null !== $activeNodeVersion && $this->isEditableNode) { |
|
111 | 1 | $menu->addChild( |
|
112 | 1 | 'subaction.versions', |
|
113 | [ |
||
114 | 'linkAttributes' => [ |
||
115 | 1 | 'data-toggle' => 'modal', |
|
116 | 'data-keyboard' => 'true', |
||
117 | 'data-target' => '#versions', |
||
118 | ], |
||
119 | ] |
||
120 | ); |
||
121 | } |
||
122 | |||
123 | 1 | $this->dispatch( |
|
124 | 1 | new ConfigureActionMenuEvent( |
|
125 | 1 | $this->factory, |
|
126 | $menu, |
||
127 | $activeNodeVersion |
||
128 | ), |
||
129 | 1 | Events::CONFIGURE_SUB_ACTION_MENU |
|
130 | ); |
||
131 | |||
132 | 1 | return $menu; |
|
133 | } |
||
134 | |||
135 | /** |
||
136 | * @return ItemInterface |
||
137 | */ |
||
138 | 6 | public function createActionsMenu() |
|
139 | { |
||
140 | 6 | $activeNodeVersion = $this->getActiveNodeVersion(); |
|
141 | |||
142 | 6 | $translations = $activeNodeVersion->getNodeTranslation()->getNode()->getNodeTranslations(true); |
|
143 | 6 | $canRecopy = false; |
|
144 | 6 | foreach ($translations as $translation) { |
|
145 | 1 | if ($translation->getLang() != $activeNodeVersion->getNodeTranslation()->getLang()) { |
|
146 | 1 | $canRecopy = true; |
|
147 | } |
||
148 | } |
||
149 | |||
150 | 6 | $menu = $this->factory->createItem('root'); |
|
151 | 6 | $menu->setChildrenAttribute( |
|
152 | 6 | 'class', |
|
153 | 6 | 'page-main-actions js-auto-collapse-buttons' |
|
154 | ); |
||
155 | 6 | $menu->setChildrenAttribute( |
|
156 | 6 | 'data-visible-buttons', |
|
157 | 6 | '3' |
|
158 | ); |
||
159 | |||
160 | 6 | if (null === $activeNodeVersion) { |
|
161 | $this->dispatch( |
||
162 | new ConfigureActionMenuEvent( |
||
163 | $this->factory, |
||
164 | $menu, |
||
165 | $activeNodeVersion |
||
166 | ), |
||
167 | Events::CONFIGURE_ACTION_MENU |
||
168 | ); |
||
169 | |||
170 | return $menu; |
||
171 | } |
||
172 | |||
173 | 6 | $activeNodeTranslation = $activeNodeVersion->getNodeTranslation(); |
|
174 | 6 | $node = $activeNodeTranslation->getNode(); |
|
175 | 6 | $queuedNodeTranslationAction = $this->em->getRepository(QueuedNodeTranslationAction::class)->findOneBy(['nodeTranslation' => $activeNodeTranslation]); |
|
176 | |||
177 | 6 | $isFirst = true; |
|
178 | 6 | $canEdit = $this->authorizationChecker->isGranted(PermissionMap::PERMISSION_EDIT, $node); |
|
179 | 6 | $canPublish = $this->authorizationChecker->isGranted(PermissionMap::PERMISSION_PUBLISH, $node); |
|
180 | 6 | $isSuperAdmin = $this->authorizationChecker->isGranted(UserInterface::ROLE_SUPER_ADMIN); |
|
181 | |||
182 | 6 | if ($activeNodeVersion->isDraft() && $this->isEditableNode) { |
|
183 | 1 | View Code Duplication | if ($canEdit) { |
184 | 1 | $menu->addChild( |
|
185 | 1 | 'action.saveasdraft', |
|
186 | [ |
||
187 | 'linkAttributes' => [ |
||
188 | 1 | 'type' => 'submit', |
|
189 | 'class' => 'js-save-btn btn btn--raise-on-hover btn-primary', |
||
190 | 'value' => 'save', |
||
191 | 'name' => 'save', |
||
192 | ], |
||
193 | 'extras' => ['renderType' => 'button'], |
||
194 | ] |
||
195 | ); |
||
196 | 1 | if ($this->enableExportPageTemplate && $isSuperAdmin && is_subclass_of($node->getRefEntityName(), HasPageTemplateInterface::class)) { |
|
197 | $menu->addChild( |
||
198 | 'action.exportpagetemplate', |
||
199 | [ |
||
200 | 'linkAttributes' => [ |
||
201 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
202 | 'data-toggle' => 'modal', |
||
203 | 'data-keyboard' => 'true', |
||
204 | 'data-target' => '#exportPagetemplate', |
||
205 | ], |
||
206 | ] |
||
207 | ); |
||
208 | } |
||
209 | 1 | if ($canRecopy) { |
|
210 | $menu->addChild( |
||
211 | 'action.recopyfromlanguage', |
||
212 | [ |
||
213 | 'linkAttributes' => [ |
||
214 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
215 | 'data-toggle' => 'modal', |
||
216 | 'data-keyboard' => 'true', |
||
217 | 'data-target' => '#recopy', |
||
218 | ], |
||
219 | ] |
||
220 | ); |
||
221 | } |
||
222 | 1 | $isFirst = false; |
|
223 | } |
||
224 | |||
225 | 1 | $menu->addChild( |
|
226 | 1 | 'action.preview', |
|
227 | [ |
||
228 | 1 | 'uri' => $this->router->generate( |
|
229 | 1 | '_slug_preview', |
|
230 | [ |
||
231 | 1 | 'url' => $activeNodeTranslation->getUrl(), |
|
232 | 1 | 'version' => $activeNodeVersion->getId(), |
|
233 | ] |
||
234 | ), |
||
235 | 'linkAttributes' => [ |
||
236 | 'target' => '_blank', |
||
237 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
238 | ], |
||
239 | ] |
||
240 | ); |
||
241 | |||
242 | 1 | View Code Duplication | if (empty($queuedNodeTranslationAction) && $canPublish) { |
243 | 1 | $menu->addChild( |
|
244 | 1 | 'action.publish', |
|
245 | [ |
||
246 | 'linkAttributes' => [ |
||
247 | 1 | 'data-toggle' => 'modal', |
|
248 | 1 | 'data-target' => '#pub', |
|
249 | 1 | 'class' => 'btn btn--raise-on-hover'.($isFirst ? ' btn-primary btn-save' : ' btn-default'), |
|
250 | ], |
||
251 | ] |
||
252 | ); |
||
253 | } |
||
254 | } else { |
||
255 | 5 | if ($canEdit && $canPublish) { |
|
256 | 5 | $menu->addChild( |
|
257 | 5 | 'action.save', |
|
258 | [ |
||
259 | 'linkAttributes' => [ |
||
260 | 5 | 'type' => 'submit', |
|
261 | 'class' => 'js-save-btn btn btn--raise-on-hover btn-primary', |
||
262 | 'value' => 'save', |
||
263 | 'name' => 'save', |
||
264 | ], |
||
265 | 'extras' => ['renderType' => 'button'], |
||
266 | ] |
||
267 | ); |
||
268 | 5 | $isFirst = false; |
|
269 | } |
||
270 | |||
271 | 5 | if ($this->isEditableNode) { |
|
272 | 4 | $menu->addChild( |
|
273 | 4 | 'action.preview', |
|
274 | [ |
||
275 | 4 | 'uri' => $this->router->generate( |
|
276 | 4 | '_slug_preview', |
|
277 | 4 | ['url' => $activeNodeTranslation->getUrl()] |
|
278 | ), |
||
279 | 'linkAttributes' => [ |
||
280 | 'target' => '_blank', |
||
281 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
282 | ], |
||
283 | ] |
||
284 | ); |
||
285 | |||
286 | 4 | if (empty($queuedNodeTranslationAction) |
|
287 | 4 | && $activeNodeTranslation->isOnline() |
|
288 | 1 | && $this->authorizationChecker->isGranted( |
|
289 | 4 | PermissionMap::PERMISSION_UNPUBLISH, |
|
290 | $node |
||
291 | ) |
||
292 | ) { |
||
293 | 1 | $menu->addChild( |
|
294 | 1 | 'action.unpublish', |
|
295 | [ |
||
296 | 'linkAttributes' => [ |
||
297 | 1 | 'class' => 'btn btn-default btn--raise-on-hover', |
|
298 | 'data-toggle' => 'modal', |
||
299 | 'data-keyboard' => 'true', |
||
300 | 'data-target' => '#unpub', |
||
301 | ], |
||
302 | ] |
||
303 | ); |
||
304 | 4 | View Code Duplication | } elseif (empty($queuedNodeTranslationAction) |
305 | 4 | && !$activeNodeTranslation->isOnline() |
|
306 | 4 | && $canPublish |
|
307 | ) { |
||
308 | 4 | $menu->addChild( |
|
309 | 4 | 'action.publish', |
|
310 | [ |
||
311 | 'linkAttributes' => [ |
||
312 | 4 | 'class' => 'btn btn-default btn--raise-on-hover', |
|
313 | 'data-toggle' => 'modal', |
||
314 | 'data-keyboard' => 'true', |
||
315 | 'data-target' => '#pub', |
||
316 | ], |
||
317 | ] |
||
318 | ); |
||
319 | } |
||
320 | |||
321 | 4 | View Code Duplication | if ($canEdit) { |
322 | 4 | $menu->addChild( |
|
323 | 4 | 'action.saveasdraft', |
|
324 | [ |
||
325 | 'linkAttributes' => [ |
||
326 | 4 | 'type' => 'submit', |
|
327 | 4 | 'class' => 'btn btn--raise-on-hover'.($isFirst ? ' btn-primary btn-save' : ' btn-default'), |
|
328 | 4 | 'value' => 'saveasdraft', |
|
329 | 4 | 'name' => 'saveasdraft', |
|
330 | ], |
||
331 | 'extras' => ['renderType' => 'button'], |
||
332 | ] |
||
333 | ); |
||
334 | 4 | if ($this->enableExportPageTemplate && $isSuperAdmin && is_subclass_of($node->getRefEntityName(), HasPageTemplateInterface::class)) { |
|
335 | $menu->addChild( |
||
336 | 'action.exportpagetemplate', |
||
337 | [ |
||
338 | 'linkAttributes' => [ |
||
339 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
340 | 'data-toggle' => 'modal', |
||
341 | 'data-keyboard' => 'true', |
||
342 | 'data-target' => '#exportPagetemplate', |
||
343 | ], |
||
344 | ] |
||
345 | ); |
||
346 | } |
||
347 | 4 | if ($canRecopy) { |
|
348 | 1 | $menu->addChild( |
|
349 | 1 | 'action.recopyfromlanguage', |
|
350 | [ |
||
351 | 'linkAttributes' => [ |
||
352 | 1 | 'class' => 'btn btn-default btn--raise-on-hover', |
|
353 | 'data-toggle' => 'modal', |
||
354 | 'data-keyboard' => 'true', |
||
355 | 'data-target' => '#recopy', |
||
356 | ], |
||
357 | ] |
||
358 | ); |
||
359 | } |
||
360 | } |
||
361 | } |
||
362 | } |
||
363 | |||
364 | 6 | View Code Duplication | if ($this->pagesConfiguration->getPossibleChildTypes( |
365 | 6 | $node->getRefEntityName() |
|
366 | ) |
||
367 | ) { |
||
368 | $menu->addChild( |
||
369 | 'action.addsubpage', |
||
370 | [ |
||
371 | 'linkAttributes' => [ |
||
372 | 'type' => 'button', |
||
373 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
374 | 'data-toggle' => 'modal', |
||
375 | 'data-keyboard' => 'true', |
||
376 | 'data-target' => '#add-subpage-modal', |
||
377 | ], |
||
378 | 'extras' => ['renderType' => 'button'], |
||
379 | ] |
||
380 | ); |
||
381 | } |
||
382 | |||
383 | 6 | if (null !== $node->getParent() && $canEdit) { |
|
384 | 1 | $menu->addChild( |
|
385 | 1 | 'action.duplicate', |
|
386 | [ |
||
387 | 'linkAttributes' => [ |
||
388 | 1 | 'type' => 'button', |
|
389 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
390 | 'data-toggle' => 'modal', |
||
391 | 'data-keyboard' => 'true', |
||
392 | 'data-target' => '#duplicate-page-modal', |
||
393 | ], |
||
394 | 'extras' => ['renderType' => 'button'], |
||
395 | ] |
||
396 | ); |
||
397 | |||
398 | 1 | View Code Duplication | if ($this->showDuplicateWithChildren) { |
399 | $menu->addChild( |
||
400 | 'action.duplicate_with_children', |
||
401 | [ |
||
402 | 'linkAttributes' => [ |
||
403 | 'type' => 'button', |
||
404 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
405 | 'data-toggle' => 'modal', |
||
406 | 'data-keyboard' => 'true', |
||
407 | 'data-target' => '#duplicate-with-children-page-modal', |
||
408 | ], |
||
409 | 'extras' => ['renderType' => 'button'], |
||
410 | ] |
||
411 | ); |
||
412 | } |
||
413 | } |
||
414 | |||
415 | 6 | if ((null !== $node->getParent() || $node->getChildren()->isEmpty()) |
|
416 | 6 | && $this->authorizationChecker->isGranted( |
|
417 | 6 | PermissionMap::PERMISSION_DELETE, |
|
418 | $node |
||
419 | ) |
||
420 | ) { |
||
421 | 6 | $menu->addChild( |
|
422 | 6 | 'action.delete', |
|
423 | [ |
||
424 | 'linkAttributes' => [ |
||
425 | 6 | 'type' => 'button', |
|
426 | 'class' => 'btn btn-default btn--raise-on-hover', |
||
427 | 'onClick' => 'oldEdited = isEdited; isEdited=false', |
||
428 | 'data-toggle' => 'modal', |
||
429 | 'data-keyboard' => 'true', |
||
430 | 'data-target' => '#delete-page-modal', |
||
431 | ], |
||
432 | 'extras' => ['renderType' => 'button'], |
||
433 | ] |
||
434 | ); |
||
435 | } |
||
436 | |||
437 | 6 | $this->dispatch( |
|
438 | 6 | new ConfigureActionMenuEvent( |
|
439 | 6 | $this->factory, |
|
440 | $menu, |
||
441 | $activeNodeVersion |
||
442 | ), |
||
443 | 6 | Events::CONFIGURE_ACTION_MENU |
|
444 | ); |
||
445 | |||
446 | 6 | return $menu; |
|
447 | } |
||
448 | |||
449 | /** |
||
450 | * @return ItemInterface |
||
451 | */ |
||
452 | 1 | public function createTopActionsMenu() |
|
463 | |||
464 | /** |
||
465 | * @return ItemInterface |
||
466 | */ |
||
467 | public function createHomeActionsMenu() |
||
490 | |||
491 | /** |
||
492 | * @return ItemInterface |
||
493 | */ |
||
494 | public function createTopHomeActionsMenu() |
||
505 | |||
506 | /** |
||
507 | * Set activeNodeVersion |
||
508 | * |
||
509 | * @param NodeVersion $activeNodeVersion |
||
510 | * |
||
511 | * @return ActionsMenuBuilder |
||
512 | */ |
||
513 | 8 | public function setActiveNodeVersion(NodeVersion $activeNodeVersion) |
|
519 | |||
520 | /** |
||
521 | * Get activeNodeVersion |
||
522 | * |
||
523 | * @return NodeVersion |
||
524 | */ |
||
525 | 8 | public function getActiveNodeVersion() |
|
529 | |||
530 | /** |
||
531 | * @param bool $value |
||
532 | */ |
||
533 | 1 | public function setEditableNode($value) |
|
537 | |||
538 | /** |
||
539 | * @param object $event |
||
540 | * @param string $eventName |
||
541 | * |
||
542 | * @return object |
||
543 | */ |
||
544 | 7 | View Code Duplication | private function dispatch($event, string $eventName) |
554 | } |
||
555 |
The
EntityManager
might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.