Total Complexity | 62 |
Total Lines | 605 |
Duplicated Lines | 0 % |
Changes | 9 | ||
Bugs | 4 | Features | 0 |
Complex classes like ModulesController 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.
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 ModulesController, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
29 | class ModulesController extends AppController |
||
30 | { |
||
31 | protected const FIXED_RELATIONSHIPS = [ |
||
32 | 'parent', |
||
33 | 'children', |
||
34 | 'parents', |
||
35 | 'translations', |
||
36 | 'streams', |
||
37 | 'roles', |
||
38 | ]; |
||
39 | |||
40 | /** |
||
41 | * Object type currently used |
||
42 | * |
||
43 | * @var string |
||
44 | */ |
||
45 | protected $objectType = null; |
||
46 | |||
47 | /** |
||
48 | * {@inheritDoc} |
||
49 | */ |
||
50 | public function initialize() : void |
||
51 | { |
||
52 | parent::initialize(); |
||
53 | |||
54 | $this->loadComponent('Properties'); |
||
55 | $this->loadComponent('ProjectConfiguration'); |
||
56 | |||
57 | if (!empty($this->request)) { |
||
58 | $this->objectType = $this->request->getParam('object_type'); |
||
59 | $this->Modules->setConfig('currentModuleName', $this->objectType); |
||
60 | $this->Schema->setConfig('type', $this->objectType); |
||
61 | } |
||
62 | |||
63 | $this->Security->setConfig('unlockedActions', ['saveJson']); |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * {@inheritDoc} |
||
68 | * @codeCoverageIgnore |
||
69 | */ |
||
70 | public function beforeRender(Event $event) : ?Response |
||
71 | { |
||
72 | $this->set('objectType', $this->objectType); |
||
73 | |||
74 | return parent::beforeRender($event); |
||
|
|||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Display resources list. |
||
79 | * |
||
80 | * @return \Cake\Http\Response|null |
||
81 | */ |
||
82 | public function index() : ?Response |
||
83 | { |
||
84 | $this->request->allowMethod(['get']); |
||
85 | |||
86 | // handle filter and query parameters using session |
||
87 | $result = $this->applySessionFilter(); |
||
88 | if ($result != null) { |
||
89 | return $result; |
||
90 | } |
||
91 | |||
92 | try { |
||
93 | $response = $this->apiClient->getObjects($this->objectType, $this->request->getQueryParams()); |
||
94 | } catch (BEditaClientException $e) { |
||
95 | $this->log($e, LogLevel::ERROR); |
||
96 | $this->Flash->error($e->getMessage(), ['params' => $e]); |
||
97 | // remove session filter to avoid error repetition |
||
98 | $session = $this->request->getSession(); |
||
99 | $session->delete(sprintf('%s.filter', $this->Modules->getConfig('currentModuleName'))); |
||
100 | |||
101 | return $this->redirect(['_name' => 'dashboard']); |
||
102 | } |
||
103 | |||
104 | $this->ProjectConfiguration->read(); |
||
105 | |||
106 | $objects = (array)$response['data']; |
||
107 | $this->set('objects', $objects); |
||
108 | $this->set('meta', (array)$response['meta']); |
||
109 | $this->set('links', (array)$response['links']); |
||
110 | $this->set('types', ['right' => $this->descendants()]); |
||
111 | |||
112 | if (!empty($this->request->getQueryParams()['autocomplete'])) { |
||
113 | $this->render('autocomplete'); |
||
114 | } |
||
115 | |||
116 | $this->set('properties', $this->Properties->indexList($this->objectType)); |
||
117 | |||
118 | // base/custom filters for filter view |
||
119 | $this->set('filter', $this->Properties->filterList($this->objectType)); |
||
120 | |||
121 | // base/custom bulk actions for index view |
||
122 | $this->set('bulkActions', $this->Properties->bulkList($this->objectType)); |
||
123 | |||
124 | // objectTypes schema |
||
125 | $this->set('schema', $this->getSchemaForIndex($this->objectType)); |
||
126 | |||
127 | // set prevNext for views navigations |
||
128 | $this->setObjectNav($objects); |
||
129 | |||
130 | return null; |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Retrieve descendants of `$this->objectType` if any |
||
135 | * |
||
136 | * @return array |
||
137 | */ |
||
138 | protected function descendants() : array |
||
139 | { |
||
140 | if (!$this->Modules->isAbstract($this->objectType)) { |
||
141 | return []; |
||
142 | } |
||
143 | $filter = [ |
||
144 | 'parent' => $this->objectType, |
||
145 | 'enabled' => true, |
||
146 | ]; |
||
147 | $sort = 'name'; |
||
148 | |||
149 | try { |
||
150 | $descendants = $this->apiClient->get('/model/object_types', compact('filter', 'sort') + ['fields' => 'name']); |
||
151 | } catch (BEditaClientException $e) { |
||
152 | // Error! Return empty list. |
||
153 | $this->log($e, LogLevel::ERROR); |
||
154 | |||
155 | return []; |
||
156 | } |
||
157 | |||
158 | return (array)Hash::extract($descendants, 'data.{n}.attributes.name'); |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * View single resource. |
||
163 | * |
||
164 | * @param string|int $id Resource ID. |
||
165 | * @return \Cake\Http\Response|null |
||
166 | */ |
||
167 | public function view($id) : ?Response |
||
168 | { |
||
169 | $this->request->allowMethod(['get']); |
||
170 | |||
171 | try { |
||
172 | $response = $this->apiClient->getObject($id, $this->objectType); |
||
173 | } catch (BEditaClientException $e) { |
||
174 | // Error! Back to index. |
||
175 | $this->log($e, LogLevel::ERROR); |
||
176 | $this->Flash->error(__('Error retrieving the requested content'), ['params' => $e]); |
||
177 | |||
178 | return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]); |
||
179 | } |
||
180 | $this->ProjectConfiguration->read(); |
||
181 | |||
182 | $revision = Hash::get($response, 'meta.schema.' . $this->objectType . '.revision', null); |
||
183 | $schema = $this->Schema->getSchema($this->objectType, $revision); |
||
184 | |||
185 | $object = $response['data']; |
||
186 | $included = (!empty($response['included'])) ? $response['included'] : []; |
||
187 | $this->set(compact('object', 'included', 'schema')); |
||
188 | $this->set('properties', $this->Properties->viewGroups($object, $this->objectType)); |
||
189 | |||
190 | // relatinos between objects |
||
191 | $relationsSchema = array_intersect_key($this->Schema->getRelationsSchema(), $object['relationships']); |
||
192 | // relations between objects and resources |
||
193 | $resourceRelations = array_diff(array_keys($object['relationships']), array_keys($relationsSchema), self::FIXED_RELATIONSHIPS); |
||
194 | |||
195 | $this->set(compact('relationsSchema', 'resourceRelations')); |
||
196 | $this->set('objectRelations', array_keys($relationsSchema)); |
||
197 | |||
198 | // set objectNav |
||
199 | $objectNav = $this->getObjectNav((string)$id); |
||
200 | $this->set('objectNav', $objectNav); |
||
201 | |||
202 | return null; |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * View single resource by id, doing a proper redirect (302) to resource module view by type. |
||
207 | * If no resource found by ID, redirect to referer. |
||
208 | * |
||
209 | * @param string|int $id Resource ID. |
||
210 | * @return \Cake\Http\Response|null |
||
211 | */ |
||
212 | public function uname($id) : ?Response |
||
213 | { |
||
214 | try { |
||
215 | $response = $this->apiClient->get(sprintf('/objects/%s', $id)); |
||
216 | } catch (BEditaClientException $e) { |
||
217 | if ($e->getCode() === 404) { |
||
218 | $error = sprintf(__('Resource "%s" not found', true), $id); |
||
219 | } else { |
||
220 | $error = sprintf(__('Resource "%s" not available. Error: %s', true), $id, $e->getMessage()); |
||
221 | } |
||
222 | $this->Flash->error($error); |
||
223 | |||
224 | return $this->redirect($this->referer()); |
||
225 | } |
||
226 | $_name = 'modules:view'; |
||
227 | $object_type = $response['data']['type']; |
||
228 | $id = $response['data']['id']; |
||
229 | |||
230 | return $this->redirect(compact('_name', 'object_type', 'id')); |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * Display new resource form. |
||
235 | * |
||
236 | * @return \Cake\Http\Response|null |
||
237 | */ |
||
238 | public function create() : ?Response |
||
239 | { |
||
240 | $this->viewBuilder()->setTemplate('view'); |
||
241 | |||
242 | // Create stub object with empty `attributes`. |
||
243 | $schema = $this->Schema->getSchema(); |
||
244 | if (!is_array($schema)) { |
||
245 | $this->Flash->error(__('Cannot create abstract objects or objects without schema')); |
||
246 | |||
247 | return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]); |
||
248 | } |
||
249 | $attributes = array_fill_keys( |
||
250 | array_keys( |
||
251 | array_filter( |
||
252 | $schema['properties'], |
||
253 | function ($schema) { |
||
254 | return empty($schema['readOnly']); |
||
255 | } |
||
256 | ) |
||
257 | ), |
||
258 | '' |
||
259 | ); |
||
260 | $object = [ |
||
261 | 'type' => $this->objectType, |
||
262 | 'attributes' => $attributes, |
||
263 | ]; |
||
264 | |||
265 | $this->set(compact('object', 'schema')); |
||
266 | $this->set('properties', $this->Properties->viewGroups($object, $this->objectType)); |
||
267 | |||
268 | return null; |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Create or edit single resource. |
||
273 | * |
||
274 | * @return \Cake\Http\Response|null |
||
275 | */ |
||
276 | public function save() : ?Response |
||
277 | { |
||
278 | $this->request->allowMethod(['post']); |
||
279 | $requestData = $this->prepareRequest($this->objectType); |
||
280 | |||
281 | try { |
||
282 | if (!empty($requestData['_api'])) { |
||
283 | foreach ($requestData['_api'] as $api) { |
||
284 | extract($api); // method, id, type, relation, relatedIds |
||
285 | if (in_array($method, ['addRelated', 'removeRelated', 'replaceRelated'])) { |
||
286 | $this->apiClient->{$method}($id, $this->objectType, $relation, $relatedIds); |
||
287 | } |
||
288 | } |
||
289 | } |
||
290 | unset($requestData['_api']); |
||
291 | |||
292 | // upload file (if available) |
||
293 | $this->Modules->upload($requestData); |
||
294 | |||
295 | // save data |
||
296 | $response = $this->apiClient->save($this->objectType, $requestData); |
||
297 | } catch (InternalErrorException | BEditaClientException | UploadException $e) { |
||
298 | // Error! Back to object view or index. |
||
299 | $this->log($e, LogLevel::ERROR); |
||
300 | $this->Flash->error($e->getMessage(), ['params' => $e]); |
||
301 | |||
302 | if ($this->request->getData('id')) { |
||
303 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $this->request->getData('id')]); |
||
304 | } |
||
305 | |||
306 | return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]); |
||
307 | } |
||
308 | |||
309 | // annoying message removed, restore with https://github.com/bedita/manager/issues/71 |
||
310 | // $this->Flash->success(__('Object saved')); |
||
311 | |||
312 | return $this->redirect([ |
||
313 | '_name' => 'modules:view', |
||
314 | 'object_type' => $this->objectType, |
||
315 | 'id' => Hash::get($response, 'data.id'), |
||
316 | ]); |
||
317 | } |
||
318 | |||
319 | /** |
||
320 | * Create new object from ajax request. |
||
321 | * |
||
322 | * @return void |
||
323 | */ |
||
324 | public function saveJson() : void |
||
325 | { |
||
326 | $this->viewBuilder()->setClassName('Json'); // force json response |
||
327 | $this->request->allowMethod(['post']); |
||
328 | $requestData = $this->prepareRequest($this->objectType); |
||
329 | |||
330 | try { |
||
331 | // upload file (if available) |
||
332 | $this->Modules->upload($requestData); |
||
333 | |||
334 | // save data |
||
335 | $response = $this->apiClient->save($this->objectType, $requestData); |
||
336 | } catch (BEditaClientException $error) { |
||
337 | $this->log($error, LogLevel::ERROR); |
||
338 | |||
339 | $this->set(compact('error')); |
||
340 | $this->set('_serialize', ['error']); |
||
341 | |||
342 | return; |
||
343 | } |
||
344 | if ($response['data']) { |
||
345 | $response['data'] = [ $response['data'] ]; |
||
346 | } |
||
347 | |||
348 | $this->getThumbsUrls($response); |
||
349 | |||
350 | $this->set((array)$response); |
||
351 | $this->set('_serialize', array_keys($response)); |
||
352 | } |
||
353 | |||
354 | /** |
||
355 | * Clone single object. |
||
356 | * |
||
357 | * @param string|int $id Object ID. |
||
358 | * @return \Cake\Http\Response|null |
||
359 | */ |
||
360 | public function clone($id) : ?Response |
||
361 | { |
||
362 | $this->viewBuilder()->setTemplate('view'); |
||
363 | |||
364 | $schema = $this->Schema->getSchema(); |
||
365 | if (!is_array($schema)) { |
||
366 | $this->Flash->error(__('Cannot create abstract objects or objects without schema')); |
||
367 | |||
368 | return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]); |
||
369 | } |
||
370 | try { |
||
371 | $response = $this->apiClient->getObject($id, $this->objectType); |
||
372 | $attributes = $response['data']['attributes']; |
||
373 | $attributes['uname'] = ''; |
||
374 | unset($attributes['relationships']); |
||
375 | $attributes['title'] = $this->request->getQuery('title'); |
||
376 | } catch (BEditaClientException $e) { |
||
377 | $this->log($e, LogLevel::ERROR); |
||
378 | $this->Flash->error($e->getMessage(), ['params' => $e]); |
||
379 | |||
380 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $id]); |
||
381 | } |
||
382 | $object = [ |
||
383 | 'type' => $this->objectType, |
||
384 | 'attributes' => $attributes, |
||
385 | ]; |
||
386 | $this->set(compact('object', 'schema')); |
||
387 | $this->set('properties', $this->Properties->viewGroups($object, $this->objectType)); |
||
388 | |||
389 | return null; |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Delete single resource. |
||
394 | * |
||
395 | * @return \Cake\Http\Response|null |
||
396 | */ |
||
397 | public function delete() : ?Response |
||
398 | { |
||
399 | $this->request->allowMethod(['post']); |
||
400 | $ids = []; |
||
401 | if (!empty($this->request->getData('ids'))) { |
||
402 | if (is_string($this->request->getData('ids'))) { |
||
403 | $ids = explode(',', $this->request->getData('ids')); |
||
404 | } |
||
405 | } else { |
||
406 | $ids = [$this->request->getData('id')]; |
||
407 | } |
||
408 | foreach ($ids as $id) { |
||
409 | try { |
||
410 | $this->apiClient->deleteObject($id, $this->objectType); |
||
411 | } catch (BEditaClientException $e) { |
||
412 | $this->log($e, LogLevel::ERROR); |
||
413 | $this->Flash->error($e->getMessage(), ['params' => $e]); |
||
414 | if (!empty($this->request->getData('id'))) { |
||
415 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $this->request->getData('id')]); |
||
416 | } |
||
417 | |||
418 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType]); |
||
419 | } |
||
420 | } |
||
421 | $this->Flash->success(__('Object(s) deleted')); |
||
422 | |||
423 | return $this->redirect([ |
||
424 | '_name' => 'modules:list', |
||
425 | 'object_type' => $this->objectType, |
||
426 | ]); |
||
427 | } |
||
428 | |||
429 | /** |
||
430 | * Relation data load callig api `GET /:object_type/:id/related/:relation` |
||
431 | * |
||
432 | * @param string|int $id the object identifier. |
||
433 | * @param string $relation the relating name. |
||
434 | * @return void |
||
435 | */ |
||
436 | public function relatedJson($id, string $relation) : void |
||
437 | { |
||
438 | $this->request->allowMethod(['get']); |
||
439 | try { |
||
440 | $response = $this->apiClient->getRelated($id, $this->objectType, $relation, $this->request->getQueryParams()); |
||
441 | } catch (BEditaClientException $error) { |
||
442 | $this->log($error, LogLevel::ERROR); |
||
443 | |||
444 | $this->set(compact('error')); |
||
445 | $this->set('_serialize', ['error']); |
||
446 | |||
447 | return; |
||
448 | } |
||
449 | |||
450 | $this->getThumbsUrls($response); |
||
451 | |||
452 | $this->set((array)$response); |
||
453 | $this->set('_serialize', array_keys($response)); |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * Load resources of $type callig api `GET /:type/` |
||
458 | * Json response |
||
459 | * |
||
460 | * @param string|int $id the object identifier. |
||
461 | * @param string $type the resource type name. |
||
462 | * @return void |
||
463 | */ |
||
464 | public function resourcesJson($id, string $type) : void |
||
481 | } |
||
482 | |||
483 | /** |
||
484 | * Relation data load callig api `GET /:object_type/:id/relationships/:relation` |
||
485 | * Json response |
||
486 | * |
||
487 | * @param string|int $id the object identifier. |
||
488 | * @param string $relation the relating name. |
||
489 | * @return void |
||
490 | */ |
||
491 | public function relationshipsJson($id, string $relation) : void |
||
492 | { |
||
493 | $this->request->allowMethod(['get']); |
||
494 | $path = sprintf('/%s/%s/%s', $this->objectType, $id, $relation); |
||
495 | |||
496 | try { |
||
497 | switch ($relation) { |
||
498 | case 'children': |
||
499 | $available = '/objects'; |
||
500 | break; |
||
501 | case 'parent': |
||
502 | case 'parents': |
||
503 | $available = '/folders'; |
||
504 | break; |
||
505 | default: |
||
506 | $response = $this->apiClient->get($path, ['page_size' => 1]); // page_size 1: we need just the available |
||
507 | $available = $response['links']['available']; |
||
508 | } |
||
509 | |||
510 | $response = $this->apiClient->get($available, $this->request->getQueryParams()); |
||
511 | |||
512 | $this->getThumbsUrls($response); |
||
513 | } catch (BEditaClientException $ex) { |
||
514 | $this->log($ex, LogLevel::ERROR); |
||
515 | |||
516 | $this->set([ |
||
517 | 'error' => $ex->getMessage(), |
||
518 | '_serialize' => ['error'], |
||
519 | ]); |
||
520 | |||
521 | return; |
||
522 | } |
||
523 | |||
524 | $this->set((array)$response); |
||
525 | $this->set('_serialize', array_keys($response)); |
||
526 | } |
||
527 | |||
528 | /** |
||
529 | * Retrieve thumbnails URL of related objects in `meta.url` if present. |
||
530 | * |
||
531 | * @param array $response Related objects response. |
||
532 | * @return void |
||
533 | */ |
||
534 | public function getThumbsUrls(array &$response) : void |
||
563 | } |
||
564 | } |
||
565 | } |
||
566 | |||
567 | /** |
||
568 | * Bulk change actions for objects |
||
569 | * |
||
570 | * @return \Cake\Http\Response|null |
||
571 | */ |
||
572 | public function bulkActions() : ?Response |
||
611 | } |
||
612 | |||
613 | /** |
||
614 | * get object properties and format them for index |
||
615 | * |
||
616 | * @param string $objectType objecte type name |
||
617 | * |
||
618 | * @return array $schema |
||
619 | */ |
||
620 | public function getSchemaForIndex($objectType) : array |
||
634 | } |
||
635 | } |
||
636 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.