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 Admin 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 Admin, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class Admin implements AdminInterface |
||
27 | { |
||
28 | use AdminTrait; |
||
29 | |||
30 | /** |
||
31 | * Entities collection. |
||
32 | * |
||
33 | * @var ArrayCollection |
||
34 | */ |
||
35 | protected $entities; |
||
36 | |||
37 | /** |
||
38 | * @var MessageHandlerInterface |
||
39 | */ |
||
40 | protected $messageHandler; |
||
41 | |||
42 | /** |
||
43 | * @var EntityManagerInterface |
||
44 | */ |
||
45 | protected $entityManager; |
||
46 | |||
47 | /** |
||
48 | * @var DataProviderInterface |
||
49 | */ |
||
50 | protected $dataProvider; |
||
51 | |||
52 | /** |
||
53 | * Admin configuration object |
||
54 | * |
||
55 | * @var AdminConfiguration |
||
56 | */ |
||
57 | protected $configuration; |
||
58 | |||
59 | /** |
||
60 | * Admin configured actions |
||
61 | * |
||
62 | * @var ActionInterface[] |
||
63 | */ |
||
64 | protected $actions = []; |
||
65 | |||
66 | /** |
||
67 | * Admin current action. It will be set after calling the handleRequest() |
||
68 | * |
||
69 | * @var ActionInterface |
||
70 | */ |
||
71 | protected $currentAction; |
||
72 | |||
73 | /** |
||
74 | * Admin name |
||
75 | * |
||
76 | * @var string |
||
77 | */ |
||
78 | protected $name; |
||
79 | |||
80 | /** |
||
81 | * Admin constructor. |
||
82 | * |
||
83 | * @param string $name |
||
84 | * @param DataProviderInterface $dataProvider |
||
85 | * @param AdminConfiguration $configuration |
||
86 | * @param MessageHandlerInterface $messageHandler |
||
87 | */ |
||
88 | 17 | public function __construct( |
|
100 | |||
101 | /** |
||
102 | * Load entities and set current action according to request |
||
103 | * |
||
104 | * @param Request $request |
||
105 | * @param null $user |
||
106 | * @return void |
||
107 | * @throws AdminException |
||
108 | */ |
||
109 | 5 | public function handleRequest(Request $request, $user = null) |
|
142 | |||
143 | /** |
||
144 | * Check if user is allowed to be here |
||
145 | * |
||
146 | * @param UserInterface|string $user |
||
147 | * @throws Exception |
||
148 | */ |
||
149 | 5 | public function checkPermissions($user) |
|
150 | { |
||
151 | 5 | if (!($user instanceof UserInterface)) { |
|
152 | 5 | return; |
|
153 | } |
||
154 | 1 | if ($this->currentAction === null) { |
|
155 | 1 | throw new Exception('Current action should be set before checking the permissions'); |
|
156 | } |
||
157 | 1 | $roles = $user->getRoles(); |
|
158 | 1 | $actionName = $this |
|
159 | 1 | ->getCurrentAction() |
|
160 | 1 | ->getName(); |
|
161 | |||
162 | 1 | if (!$this->isActionGranted($actionName, $roles)) { |
|
163 | 1 | $rolesStringArray = []; |
|
164 | |||
165 | 1 | foreach ($roles as $role) { |
|
166 | |||
167 | 1 | if ($role instanceof Role) { |
|
168 | $rolesStringArray[] = $role->getRole(); |
||
169 | } else { |
||
170 | 1 | $rolesStringArray[] = $role; |
|
171 | } |
||
172 | 1 | } |
|
173 | |||
174 | 1 | $message = sprintf('User with roles %s not allowed for action "%s"', |
|
175 | 1 | implode(', ', $rolesStringArray), |
|
176 | $actionName |
||
177 | 1 | ); |
|
178 | 1 | throw new NotFoundHttpException($message); |
|
179 | } |
||
180 | 1 | } |
|
181 | |||
182 | /** |
||
183 | * Create and return a new entity. |
||
184 | * |
||
185 | * @return object |
||
186 | */ |
||
187 | 3 | public function create() |
|
188 | { |
||
189 | // create an entity from the data provider |
||
190 | 3 | $entity = $this |
|
191 | ->dataProvider |
||
192 | 3 | ->create(); |
|
193 | |||
194 | // add it to the collection |
||
195 | 3 | $this |
|
196 | ->entities |
||
197 | 3 | ->add($entity); |
|
198 | |||
199 | 3 | return $entity; |
|
200 | } |
||
201 | |||
202 | /** |
||
203 | * Save entity via admin manager. Error are catch, logged and a flash message is added to session |
||
204 | * |
||
205 | * @return bool true if the entity was saved without errors |
||
206 | */ |
||
207 | 1 | View Code Duplication | public function save() |
208 | { |
||
209 | try { |
||
210 | 1 | foreach ($this->entities as $entity) { |
|
211 | 1 | $this |
|
212 | ->dataProvider |
||
213 | 1 | ->save($entity); |
|
214 | 1 | } |
|
215 | // inform user everything went fine |
||
216 | 1 | $this |
|
217 | ->messageHandler |
||
218 | 1 | ->handleSuccess('lag.admin.'.$this->name.'.saved'); |
|
219 | 1 | $success = true; |
|
220 | 1 | } catch (Exception $e) { |
|
221 | 1 | $this |
|
222 | ->messageHandler |
||
223 | 1 | ->handleError( |
|
224 | 1 | 'lag.admin.saved_errors', |
|
225 | 1 | "An error has occurred while saving an entity : {$e->getMessage()}, stackTrace: {$e->getTraceAsString()}" |
|
226 | 1 | ); |
|
227 | 1 | $success = false; |
|
228 | } |
||
229 | 1 | return $success; |
|
230 | } |
||
231 | |||
232 | /** |
||
233 | * Remove an entity with data provider |
||
234 | * |
||
235 | * @return bool true if the entity was saved without errors |
||
236 | */ |
||
237 | 1 | View Code Duplication | public function remove() |
238 | { |
||
239 | try { |
||
240 | 1 | foreach ($this->entities as $entity) { |
|
241 | 1 | $this |
|
242 | ->dataProvider |
||
243 | 1 | ->remove($entity); |
|
244 | 1 | } |
|
245 | // inform user everything went fine |
||
246 | 1 | $this |
|
247 | ->messageHandler |
||
248 | 1 | ->handleSuccess('lag.admin.'.$this->name.'.deleted'); |
|
249 | 1 | $success = true; |
|
250 | 1 | } catch (Exception $e) { |
|
251 | 1 | $this |
|
252 | ->messageHandler |
||
253 | 1 | ->handleError( |
|
254 | 1 | 'lag.admin.deleted_errors', |
|
255 | 1 | "An error has occurred while deleting an entity : {$e->getMessage()}, stackTrace: {$e->getTraceAsString()} " |
|
256 | 1 | ); |
|
257 | 1 | $success = false; |
|
258 | } |
||
259 | 1 | return $success; |
|
260 | } |
||
261 | |||
262 | /** |
||
263 | * Generate a route for admin and action name (like lag.admin.my_admin) |
||
264 | * |
||
265 | * @param $actionName |
||
266 | * |
||
267 | * @return string |
||
268 | * |
||
269 | * @throws Exception |
||
270 | */ |
||
271 | 8 | public function generateRouteName($actionName) |
|
272 | { |
||
273 | 8 | if (!array_key_exists($actionName, $this->getConfiguration()->getParameter('actions'))) { |
|
274 | 2 | throw new Exception( |
|
275 | 2 | sprintf('Invalid action name %s for admin %s (available action are: %s)', |
|
276 | 2 | $actionName, |
|
277 | 2 | $this->getName(), |
|
278 | 2 | implode(', ', $this->getActionNames())) |
|
279 | 2 | ); |
|
280 | } |
||
281 | // get routing name pattern |
||
282 | 8 | $routingPattern = $this->getConfiguration()->getParameter('routing_name_pattern'); |
|
283 | // replace admin and action name in pattern |
||
284 | 8 | $routeName = str_replace('{admin}', Container::underscore($this->getName()), $routingPattern); |
|
285 | 8 | $routeName = str_replace('{action}', $actionName, $routeName); |
|
286 | |||
287 | 8 | return $routeName; |
|
288 | } |
||
289 | |||
290 | /** |
||
291 | * Load entities manually according to criteria. |
||
292 | * |
||
293 | * @param array $criteria |
||
294 | * @param array $orderBy |
||
295 | * @param int $limit |
||
296 | * @param int $offset |
||
297 | * @throws Exception |
||
298 | */ |
||
299 | 5 | public function load(array $criteria, $orderBy = [], $limit = 25, $offset = 1) |
|
300 | { |
||
301 | 5 | $pager = $this |
|
302 | 5 | ->getCurrentAction() |
|
303 | 5 | ->getConfiguration() |
|
304 | 5 | ->getParameter('pager'); |
|
305 | |||
306 | 5 | if ($pager == 'pagerfanta') { |
|
307 | // adapter to pager fanta |
||
308 | 5 | $adapter = new PagerFantaAdminAdapter($this->dataProvider, $criteria, $orderBy); |
|
309 | // create pager |
||
310 | 5 | $this->pager = new Pagerfanta($adapter); |
|
311 | 5 | $this->pager->setMaxPerPage($limit); |
|
312 | 5 | $this->pager->setCurrentPage($offset); |
|
313 | |||
314 | 5 | $entities = $this |
|
315 | ->pager |
||
316 | 5 | ->getCurrentPageResults(); |
|
317 | 5 | } else { |
|
318 | $entities = $this |
||
319 | ->dataProvider |
||
320 | ->findBy($criteria, $orderBy, $limit, $offset); |
||
321 | } |
||
322 | 5 | if (!is_array($entities) && !($entities instanceof Collection)) { |
|
323 | 1 | throw new Exception('The data provider should return either a collection or an array. Got '.gettype($entities).' instead'); |
|
324 | } |
||
325 | |||
326 | 5 | if (is_array($entities)) { |
|
327 | 5 | $entities = new ArrayCollection($entities); |
|
328 | 5 | } |
|
329 | 5 | $this->entities = $entities; |
|
330 | 5 | } |
|
331 | |||
332 | /** |
||
333 | * Return loaded entities |
||
334 | * |
||
335 | * @return Collection |
||
336 | */ |
||
337 | 2 | public function getEntities() |
|
341 | |||
342 | /** |
||
343 | * Return entity for current admin. If entity does not exist, it throws an exception. |
||
344 | * |
||
345 | * @return mixed |
||
346 | * |
||
347 | * @throws Exception |
||
348 | */ |
||
349 | 1 | public function getUniqueEntity() |
|
359 | |||
360 | /** |
||
361 | * Return admin name |
||
362 | * |
||
363 | * @return string |
||
364 | */ |
||
365 | 14 | public function getName() |
|
369 | |||
370 | /** |
||
371 | * Return true if current action is granted for user. |
||
372 | * |
||
373 | * @param string $actionName Le plus grand de tous les héros |
||
374 | * @param array $roles |
||
375 | * |
||
376 | * @return bool |
||
377 | */ |
||
378 | 1 | public function isActionGranted($actionName, array $roles) |
|
401 | |||
402 | /** |
||
403 | * @return ActionInterface[] |
||
404 | */ |
||
405 | 6 | public function getActions() |
|
409 | |||
410 | /** |
||
411 | * @return integer[] |
||
412 | */ |
||
413 | 2 | public function getActionNames() |
|
417 | |||
418 | /** |
||
419 | * @param $name |
||
420 | * @return ActionInterface |
||
421 | * @throws Exception |
||
422 | */ |
||
423 | 5 | public function getAction($name) |
|
433 | |||
434 | /** |
||
435 | * Return if an action with specified name exists form this admin. |
||
436 | * |
||
437 | * @param $name |
||
438 | * @return bool |
||
439 | */ |
||
440 | public function hasAction($name) |
||
444 | |||
445 | /** |
||
446 | * @param ActionInterface $action |
||
447 | * @return void |
||
448 | */ |
||
449 | 12 | public function addAction(ActionInterface $action) |
|
453 | |||
454 | /** |
||
455 | * Return the current action or an exception if it is not set. |
||
456 | * |
||
457 | * @return ActionInterface |
||
458 | * @throws Exception |
||
459 | */ |
||
460 | 5 | public function getCurrentAction() |
|
471 | |||
472 | /** |
||
473 | * Return if the current action has been initialized and set. |
||
474 | * |
||
475 | * @return boolean |
||
476 | */ |
||
477 | public function isCurrentActionDefined() |
||
481 | |||
482 | /** |
||
483 | * Return admin configuration object |
||
484 | * |
||
485 | * @return AdminConfiguration |
||
486 | */ |
||
487 | 14 | public function getConfiguration() |
|
491 | } |
||
492 |