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 AbstractEditHandler 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 AbstractEditHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
40 | abstract class AbstractEditHandler |
||
41 | { |
||
42 | use TranslatorTrait; |
||
43 | |||
44 | /** |
||
45 | * Name of treated object type. |
||
46 | * |
||
47 | * @var string |
||
48 | */ |
||
49 | protected $objectType; |
||
50 | |||
51 | /** |
||
52 | * Name of treated object type starting with upper case. |
||
53 | * |
||
54 | * @var string |
||
55 | */ |
||
56 | protected $objectTypeCapital; |
||
57 | |||
58 | /** |
||
59 | * Lower case version. |
||
60 | * |
||
61 | * @var string |
||
62 | */ |
||
63 | protected $objectTypeLower; |
||
64 | |||
65 | /** |
||
66 | * Permission component based on object type. |
||
67 | * |
||
68 | * @var string |
||
69 | */ |
||
70 | protected $permissionComponent; |
||
71 | |||
72 | /** |
||
73 | * Reference to treated entity instance. |
||
74 | * |
||
75 | * @var EntityAccess |
||
76 | */ |
||
77 | protected $entityRef = null; |
||
78 | |||
79 | /** |
||
80 | * List of identifier names. |
||
81 | * |
||
82 | * @var array |
||
83 | */ |
||
84 | protected $idFields = []; |
||
85 | |||
86 | /** |
||
87 | * List of identifiers of treated entity. |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | protected $idValues = []; |
||
92 | |||
93 | /** |
||
94 | * Code defining the redirect goal after command handling. |
||
95 | * |
||
96 | * @var string |
||
97 | */ |
||
98 | protected $returnTo = null; |
||
99 | |||
100 | /** |
||
101 | * Whether a create action is going to be repeated or not. |
||
102 | * |
||
103 | * @var boolean |
||
104 | */ |
||
105 | protected $repeatCreateAction = false; |
||
106 | |||
107 | /** |
||
108 | * Url of current form with all parameters for multiple creations. |
||
109 | * |
||
110 | * @var string |
||
111 | */ |
||
112 | protected $repeatReturnUrl = null; |
||
113 | |||
114 | /** |
||
115 | * Whether an existing item is used as template for a new one. |
||
116 | * |
||
117 | * @var boolean |
||
118 | */ |
||
119 | protected $hasTemplateId = false; |
||
120 | |||
121 | /** |
||
122 | * Whether the PageLock extension is used for this entity type or not. |
||
123 | * |
||
124 | * @var boolean |
||
125 | */ |
||
126 | protected $hasPageLockSupport = false; |
||
127 | |||
128 | /** |
||
129 | * @var ZikulaHttpKernelInterface |
||
130 | */ |
||
131 | protected $kernel; |
||
132 | |||
133 | /** |
||
134 | * @var FormFactoryInterface |
||
135 | */ |
||
136 | protected $formFactory; |
||
137 | |||
138 | /** |
||
139 | * The current request. |
||
140 | * |
||
141 | * @var Request |
||
142 | */ |
||
143 | protected $request; |
||
144 | |||
145 | /** |
||
146 | * The router. |
||
147 | * |
||
148 | * @var RouterInterface |
||
149 | */ |
||
150 | protected $router; |
||
151 | |||
152 | /** |
||
153 | * @var LoggerInterface |
||
154 | */ |
||
155 | protected $logger; |
||
156 | |||
157 | /** |
||
158 | * @var PermissionApiInterface |
||
159 | */ |
||
160 | protected $permissionApi; |
||
161 | |||
162 | /** |
||
163 | * @var CurrentUserApiInterface |
||
164 | */ |
||
165 | protected $currentUserApi; |
||
166 | |||
167 | /** |
||
168 | * @var RoutesFactory |
||
169 | */ |
||
170 | protected $entityFactory; |
||
171 | |||
172 | /** |
||
173 | * @var ControllerHelper |
||
174 | */ |
||
175 | protected $controllerHelper; |
||
176 | |||
177 | /** |
||
178 | * @var ModelHelper |
||
179 | */ |
||
180 | protected $modelHelper; |
||
181 | |||
182 | /** |
||
183 | * @var WorkflowHelper |
||
184 | */ |
||
185 | protected $workflowHelper; |
||
186 | |||
187 | /** |
||
188 | * Reference to optional locking api. |
||
189 | * |
||
190 | * @var LockingApiInterface |
||
191 | */ |
||
192 | protected $lockingApi = null; |
||
193 | |||
194 | /** |
||
195 | * The handled form type. |
||
196 | * |
||
197 | * @var AbstractType |
||
198 | */ |
||
199 | protected $form; |
||
200 | |||
201 | /** |
||
202 | * Template parameters. |
||
203 | * |
||
204 | * @var array |
||
205 | */ |
||
206 | protected $templateParameters = []; |
||
207 | |||
208 | /** |
||
209 | * EditHandler constructor. |
||
210 | * |
||
211 | * @param ZikulaHttpKernelInterface $kernel Kernel service instance |
||
212 | * @param TranslatorInterface $translator Translator service instance |
||
213 | * @param FormFactoryInterface $formFactory FormFactory service instance |
||
214 | * @param RequestStack $requestStack RequestStack service instance |
||
215 | * @param RouterInterface $router Router service instance |
||
216 | * @param LoggerInterface $logger Logger service instance |
||
217 | * @param PermissionApiInterface $permissionApi PermissionApi service instance |
||
218 | * @param CurrentUserApiInterface $currentUserApi CurrentUserApi service instance |
||
219 | * @param RoutesFactory $entityFactory RoutesFactory service instance |
||
220 | * @param ControllerHelper $controllerHelper ControllerHelper service instance |
||
221 | * @param ModelHelper $modelHelper ModelHelper service instance |
||
222 | * @param WorkflowHelper $workflowHelper WorkflowHelper service instance |
||
223 | */ |
||
224 | public function __construct( |
||
251 | |||
252 | /** |
||
253 | * Sets the translator. |
||
254 | * |
||
255 | * @param TranslatorInterface $translator Translator service instance |
||
256 | */ |
||
257 | public function setTranslator(/*TranslatorInterface */$translator) |
||
261 | |||
262 | /** |
||
263 | * Initialise form handler. |
||
264 | * |
||
265 | * This method takes care of all necessary initialisation of our data and form states. |
||
266 | * |
||
267 | * @param array $templateParameters List of preassigned template variables |
||
268 | * |
||
269 | * @return boolean False in case of initialisation errors, otherwise true |
||
270 | * |
||
271 | * @throws RuntimeException Thrown if the workflow actions can not be determined |
||
272 | */ |
||
273 | public function processForm(array $templateParameters) |
||
274 | { |
||
275 | $this->templateParameters = $templateParameters; |
||
276 | |||
277 | // initialise redirect goal |
||
278 | $this->returnTo = $this->request->query->get('returnTo', null); |
||
279 | // default to referer |
||
280 | $refererSessionVar = 'zikularoutesmodule' . $this->objectTypeCapital . 'Referer'; |
||
281 | if (null === $this->returnTo && $this->request->headers->has('referer')) { |
||
282 | $currentReferer = $this->request->headers->get('referer'); |
||
283 | View Code Duplication | if ($currentReferer != $this->request->getUri()) { |
|
|
|||
284 | $this->returnTo = $currentReferer; |
||
285 | $this->request->getSession()->set($refererSessionVar, $this->returnTo); |
||
286 | } |
||
287 | } |
||
288 | View Code Duplication | if (null === $this->returnTo && $this->request->getSession()->has($refererSessionVar)) { |
|
289 | $this->returnTo = $this->request->getSession()->get($refererSessionVar); |
||
290 | } |
||
291 | // store current uri for repeated creations |
||
292 | $this->repeatReturnUrl = $this->request->getSchemeAndHttpHost() . $this->request->getBasePath() . $this->request->getPathInfo(); |
||
293 | |||
294 | $this->permissionComponent = 'ZikulaRoutesModule:' . $this->objectTypeCapital . ':'; |
||
295 | |||
296 | $this->idFields = $this->entityFactory->getIdFields($this->objectType); |
||
297 | |||
298 | // retrieve identifier of the object we wish to edit |
||
299 | $this->idValues = $this->controllerHelper->retrieveIdentifier($this->request, [], $this->objectType); |
||
300 | $hasIdentifier = $this->controllerHelper->isValidIdentifier($this->idValues); |
||
301 | |||
302 | $entity = null; |
||
303 | $this->templateParameters['mode'] = $hasIdentifier ? 'edit' : 'create'; |
||
304 | |||
305 | if ($this->templateParameters['mode'] == 'edit') { |
||
306 | if (!$this->permissionApi->hasPermission($this->permissionComponent, $this->createCompositeIdentifier() . '::', ACCESS_EDIT)) { |
||
307 | throw new AccessDeniedException(); |
||
308 | } |
||
309 | |||
310 | $entity = $this->initEntityForEditing(); |
||
311 | if (null !== $entity) { |
||
312 | if (true === $this->hasPageLockSupport && $this->kernel->isBundle('ZikulaPageLockModule') && null !== $this->lockingApi) { |
||
313 | // try to guarantee that only one person at a time can be editing this entity |
||
314 | $lockName = 'ZikulaRoutesModule' . $this->objectTypeCapital . $this->createCompositeIdentifier(); |
||
315 | $this->lockingApi->addLock($lockName, $this->getRedirectUrl(null)); |
||
316 | } |
||
317 | } |
||
318 | } else { |
||
319 | if (!$this->permissionApi->hasPermission($this->permissionComponent, '::', ACCESS_EDIT)) { |
||
320 | throw new AccessDeniedException(); |
||
321 | } |
||
322 | |||
323 | $entity = $this->initEntityForCreation(); |
||
324 | |||
325 | // set default values from request parameters |
||
326 | foreach ($this->request->query->all() as $key => $value) { |
||
327 | if (strlen($key) < 5 || substr($key, 0, 4) != 'set_') { |
||
328 | continue; |
||
329 | } |
||
330 | $fieldName = str_replace('set_', '', $key); |
||
331 | $setterName = 'set' . ucfirst($fieldName); |
||
332 | if (!method_exists($entity, $setterName)) { |
||
333 | continue; |
||
334 | } |
||
335 | $entity[$fieldName] = $value; |
||
336 | } |
||
337 | } |
||
338 | |||
339 | if (null === $entity) { |
||
340 | $this->request->getSession()->getFlashBag()->add('error', $this->__('No such item found.')); |
||
341 | |||
342 | return new RedirectResponse($this->getRedirectUrl(['commandName' => 'cancel']), 302); |
||
343 | } |
||
344 | |||
345 | // save entity reference for later reuse |
||
346 | $this->entityRef = $entity; |
||
347 | |||
348 | |||
349 | $actions = $this->workflowHelper->getActionsForObject($entity); |
||
350 | if (false === $actions || !is_array($actions)) { |
||
351 | $this->request->getSession()->getFlashBag()->add('error', $this->__('Error! Could not determine workflow actions.')); |
||
352 | $logArgs = ['app' => 'ZikulaRoutesModule', 'user' => $this->currentUserApi->get('uname'), 'entity' => $this->objectType, 'id' => $entity->createCompositeIdentifier()]; |
||
353 | $this->logger->error('{app}: User {user} tried to edit the {entity} with id {id}, but failed to determine available workflow actions.', $logArgs); |
||
354 | throw new \RuntimeException($this->__('Error! Could not determine workflow actions.')); |
||
355 | } |
||
356 | |||
357 | $this->templateParameters['actions'] = $actions; |
||
358 | |||
359 | $this->form = $this->createForm(); |
||
360 | if (!is_object($this->form)) { |
||
361 | return false; |
||
362 | } |
||
363 | |||
364 | // handle form request and check validity constraints of edited entity |
||
365 | if ($this->form->handleRequest($this->request) && $this->form->isSubmitted()) { |
||
366 | if ($this->form->isValid()) { |
||
367 | $result = $this->handleCommand(); |
||
368 | if (false === $result) { |
||
369 | $this->templateParameters['form'] = $this->form->createView(); |
||
370 | } |
||
371 | |||
372 | return $result; |
||
373 | } |
||
374 | if ($this->form->get('cancel')->isClicked()) { |
||
375 | return new RedirectResponse($this->getRedirectUrl(['commandName' => 'cancel']), 302); |
||
376 | } |
||
377 | } |
||
378 | |||
379 | $this->templateParameters['form'] = $this->form->createView(); |
||
380 | |||
381 | // everything okay, no initialisation errors occured |
||
382 | return true; |
||
383 | } |
||
384 | |||
385 | /** |
||
386 | * Creates the form type. |
||
387 | */ |
||
388 | protected function createForm() |
||
393 | |||
394 | /** |
||
395 | * Returns the template parameters. |
||
396 | * |
||
397 | * @return array |
||
398 | */ |
||
399 | public function getTemplateParameters() |
||
403 | |||
404 | /** |
||
405 | * Create concatenated identifier string (for composite keys). |
||
406 | * |
||
407 | * @return String concatenated identifiers |
||
408 | */ |
||
409 | protected function createCompositeIdentifier() |
||
425 | |||
426 | /** |
||
427 | * Initialise existing entity for editing. |
||
428 | * |
||
429 | * @return EntityAccess|null Desired entity instance or null |
||
430 | */ |
||
431 | protected function initEntityForEditing() |
||
435 | |||
436 | /** |
||
437 | * Initialise new entity for creation. |
||
438 | * |
||
439 | * @return EntityAccess|null Desired entity instance or null |
||
440 | */ |
||
441 | protected function initEntityForCreation() |
||
474 | |||
475 | /** |
||
476 | * Get list of allowed redirect codes. |
||
477 | * |
||
478 | * @return array list of possible redirect codes |
||
479 | */ |
||
480 | protected function getRedirectCodes() |
||
488 | |||
489 | /** |
||
490 | * Command event handler. |
||
491 | * |
||
492 | * @param array $args List of arguments |
||
493 | * |
||
494 | * @return mixed Redirect or false on errors |
||
495 | */ |
||
496 | public function handleCommand($args = []) |
||
533 | |||
534 | /** |
||
535 | * Get success or error message for default operations. |
||
536 | * |
||
537 | * @param array $args arguments from handleCommand method |
||
538 | * @param Boolean $success true if this is a success, false for default error |
||
539 | * |
||
540 | * @return String desired status or error message |
||
541 | */ |
||
542 | protected function getDefaultMessage($args, $success = false) |
||
571 | |||
572 | /** |
||
573 | * Add success or error message to session. |
||
574 | * |
||
575 | * @param array $args arguments from handleCommand method |
||
576 | * @param Boolean $success true if this is a success, false for default error |
||
577 | * |
||
578 | * @throws RuntimeException Thrown if executing the workflow action fails |
||
579 | */ |
||
580 | protected function addDefaultMessage($args, $success = false) |
||
596 | |||
597 | /** |
||
598 | * Input data processing called by handleCommand method. |
||
599 | * |
||
600 | * @param array $args Additional arguments |
||
601 | */ |
||
602 | public function fetchInputData($args) |
||
627 | |||
628 | /** |
||
629 | * This method executes a certain workflow action. |
||
630 | * |
||
631 | * @param array $args Arguments from handleCommand method |
||
632 | * |
||
633 | * @return bool Whether everything worked well or not |
||
634 | */ |
||
635 | public function applyAction(array $args = []) |
||
640 | |||
641 | /** |
||
642 | * Sets optional locking api reference. |
||
643 | * |
||
644 | * @param LockingApiInterface $lockingApi |
||
645 | */ |
||
646 | public function setLockingApi(LockingApiInterface $lockingApi) |
||
650 | } |
||
651 |
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.