| Total Complexity | 50 |
| Total Lines | 564 |
| Duplicated Lines | 0 % |
| Changes | 8 | ||
| Bugs | 6 | 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 |
||
| 33 | class ModulesController extends AppController |
||
| 34 | { |
||
| 35 | /** |
||
| 36 | * Object type currently used |
||
| 37 | * |
||
| 38 | * @var string |
||
| 39 | */ |
||
| 40 | protected $objectType = null; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * {@inheritDoc} |
||
| 44 | */ |
||
| 45 | public function initialize(): void |
||
| 46 | { |
||
| 47 | parent::initialize(); |
||
| 48 | |||
| 49 | $this->loadComponent('History'); |
||
| 50 | $this->loadComponent('Properties'); |
||
| 51 | $this->loadComponent('ProjectConfiguration'); |
||
| 52 | $this->loadComponent('Query'); |
||
| 53 | $this->loadComponent('Thumbs'); |
||
| 54 | $this->loadComponent('BEdita/WebTools.ApiFormatter'); |
||
| 55 | |||
| 56 | if (!empty($this->request)) { |
||
| 57 | $this->objectType = $this->request->getParam('object_type'); |
||
| 58 | $this->Modules->setConfig('currentModuleName', $this->objectType); |
||
| 59 | $this->Schema->setConfig('type', $this->objectType); |
||
| 60 | } |
||
| 61 | |||
| 62 | $this->Security->setConfig('unlockedActions', ['saveJson']); |
||
| 63 | } |
||
| 64 | |||
| 65 | /** |
||
| 66 | * {@inheritDoc} |
||
| 67 | * @codeCoverageIgnore |
||
| 68 | */ |
||
| 69 | public function beforeRender(Event $event): ?Response |
||
| 74 | } |
||
| 75 | |||
| 76 | /** |
||
| 77 | * Display resources list. |
||
| 78 | * |
||
| 79 | * @return \Cake\Http\Response|null |
||
| 80 | */ |
||
| 81 | public function index(): ?Response |
||
| 127 | } |
||
| 128 | |||
| 129 | /** |
||
| 130 | * View single resource. |
||
| 131 | * |
||
| 132 | * @param string|int $id Resource ID. |
||
| 133 | * @return \Cake\Http\Response|null |
||
| 134 | */ |
||
| 135 | public function view($id): ?Response |
||
| 178 | } |
||
| 179 | |||
| 180 | /** |
||
| 181 | * View single resource by id, doing a proper redirect (302) to resource module view by type. |
||
| 182 | * If no resource found by ID, redirect to referer. |
||
| 183 | * |
||
| 184 | * @param string|int $id Resource ID. |
||
| 185 | * @return \Cake\Http\Response|null |
||
| 186 | */ |
||
| 187 | public function uname($id): ?Response |
||
| 188 | { |
||
| 189 | try { |
||
| 190 | $response = $this->apiClient->get(sprintf('/objects/%s', $id)); |
||
| 191 | } catch (BEditaClientException $e) { |
||
| 192 | $msg = $e->getMessage(); |
||
| 193 | $error = $e->getCode() === 404 ? |
||
| 194 | sprintf(__('Resource "%s" not found', true), $id) : |
||
| 195 | sprintf(__('Resource "%s" not available. Error: %s', true), $id, $msg); |
||
| 196 | $this->Flash->error($error); |
||
| 197 | |||
| 198 | return $this->redirect($this->referer()); |
||
| 199 | } |
||
| 200 | $_name = 'modules:view'; |
||
| 201 | $object_type = $response['data']['type']; |
||
| 202 | $id = $response['data']['id']; |
||
| 203 | |||
| 204 | return $this->redirect(compact('_name', 'object_type', 'id')); |
||
| 205 | } |
||
| 206 | |||
| 207 | /** |
||
| 208 | * Display new resource form. |
||
| 209 | * |
||
| 210 | * @return \Cake\Http\Response|null |
||
| 211 | */ |
||
| 212 | public function create(): ?Response |
||
| 213 | { |
||
| 214 | $this->viewBuilder()->setTemplate('view'); |
||
| 215 | |||
| 216 | // Create stub object with empty `attributes`. |
||
| 217 | $schema = $this->Schema->getSchema(); |
||
| 218 | if (!is_array($schema)) { |
||
| 219 | $this->Flash->error(__('Cannot create abstract objects or objects without schema')); |
||
| 220 | |||
| 221 | return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]); |
||
| 222 | } |
||
| 223 | $attributes = array_fill_keys( |
||
| 224 | array_keys( |
||
| 225 | array_filter( |
||
| 226 | $schema['properties'], |
||
| 227 | function ($schema) { |
||
| 228 | return empty($schema['readOnly']); |
||
| 229 | } |
||
| 230 | ) |
||
| 231 | ), |
||
| 232 | '' |
||
| 233 | ); |
||
| 234 | $object = [ |
||
| 235 | 'type' => $this->objectType, |
||
| 236 | 'attributes' => $attributes, |
||
| 237 | ]; |
||
| 238 | |||
| 239 | $this->set(compact('object', 'schema')); |
||
| 240 | $this->set('properties', $this->Properties->viewGroups($object, $this->objectType)); |
||
| 241 | $this->ProjectConfiguration->read(); |
||
| 242 | |||
| 243 | // setup relations metadata |
||
| 244 | $relationships = (array)Hash::get($schema, 'relations'); |
||
| 245 | $this->Modules->setupRelationsMeta($this->Schema->getRelationsSchema(), $relationships); |
||
| 246 | |||
| 247 | return null; |
||
| 248 | } |
||
| 249 | |||
| 250 | /** |
||
| 251 | * Create or edit single resource. |
||
| 252 | * |
||
| 253 | * @return \Cake\Http\Response|null |
||
| 254 | */ |
||
| 255 | public function save(): ?Response |
||
| 256 | { |
||
| 257 | $this->request->allowMethod(['post']); |
||
| 258 | $requestData = $this->prepareRequest($this->objectType); |
||
| 259 | // extract related objects data |
||
| 260 | $relatedData = (array)Hash::get($requestData, '_api'); |
||
| 261 | unset($requestData['_api']); |
||
| 262 | |||
| 263 | try { |
||
| 264 | // upload file (if available) |
||
| 265 | $this->Modules->upload($requestData); |
||
| 266 | |||
| 267 | // save data |
||
| 268 | $response = $this->apiClient->save($this->objectType, $requestData); |
||
| 269 | $objectId = (string)Hash::get($response, 'data.id'); |
||
| 270 | $this->Modules->saveRelated($objectId, $this->objectType, $relatedData); |
||
| 271 | } catch (InternalErrorException | BEditaClientException | UploadException $e) { |
||
| 272 | // Error! Back to object view or index. |
||
| 273 | $this->log($e, LogLevel::ERROR); |
||
| 274 | $this->Flash->error($e->getMessage(), ['params' => $e]); |
||
| 275 | |||
| 276 | // set session data to recover form |
||
| 277 | $this->Modules->setDataFromFailedSave($this->objectType, $requestData); |
||
| 278 | |||
| 279 | if ($this->request->getData('id')) { |
||
| 280 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $this->request->getData('id')]); |
||
| 281 | } |
||
| 282 | |||
| 283 | return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]); |
||
| 284 | } |
||
| 285 | |||
| 286 | // annoying message removed, restore with https://github.com/bedita/manager/issues/71 |
||
| 287 | // $this->Flash->success(__('Object saved')); |
||
| 288 | |||
| 289 | return $this->redirect([ |
||
| 290 | '_name' => 'modules:view', |
||
| 291 | 'object_type' => $this->objectType, |
||
| 292 | 'id' => $objectId, |
||
| 293 | ]); |
||
| 294 | } |
||
| 295 | |||
| 296 | /** |
||
| 297 | * Create new object from ajax request. |
||
| 298 | * |
||
| 299 | * @return void |
||
| 300 | */ |
||
| 301 | public function saveJson(): void |
||
| 302 | { |
||
| 303 | $this->viewBuilder()->setClassName('Json'); // force json response |
||
| 304 | $this->request->allowMethod(['post']); |
||
| 305 | $requestData = $this->prepareRequest($this->objectType); |
||
| 306 | |||
| 307 | try { |
||
| 308 | // upload file (if available) |
||
| 309 | $this->Modules->upload($requestData); |
||
| 310 | |||
| 311 | // save data |
||
| 312 | $response = $this->apiClient->save($this->objectType, $requestData); |
||
| 313 | } catch (BEditaClientException $error) { |
||
| 314 | $this->log($error, LogLevel::ERROR); |
||
| 315 | |||
| 316 | $this->set(compact('error')); |
||
| 317 | $this->set('_serialize', ['error']); |
||
| 318 | |||
| 319 | return; |
||
| 320 | } |
||
| 321 | if ($response['data']) { |
||
| 322 | $response['data'] = [ $response['data'] ]; |
||
| 323 | } |
||
| 324 | |||
| 325 | $this->Thumbs->urls($response); |
||
| 326 | |||
| 327 | $this->set((array)$response); |
||
| 328 | $this->set('_serialize', array_keys($response)); |
||
| 329 | } |
||
| 330 | |||
| 331 | /** |
||
| 332 | * Clone single object. |
||
| 333 | * |
||
| 334 | * @param string|int $id Object ID. |
||
| 335 | * @return \Cake\Http\Response|null |
||
| 336 | */ |
||
| 337 | public function clone($id): ?Response |
||
| 338 | { |
||
| 339 | $this->viewBuilder()->setTemplate('view'); |
||
| 340 | |||
| 341 | $schema = $this->Schema->getSchema(); |
||
| 342 | if (!is_array($schema)) { |
||
| 343 | $this->Flash->error(__('Cannot create abstract objects or objects without schema')); |
||
| 344 | |||
| 345 | return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]); |
||
| 346 | } |
||
| 347 | try { |
||
| 348 | $response = $this->apiClient->getObject($id, $this->objectType); |
||
| 349 | $attributes = $response['data']['attributes']; |
||
| 350 | $attributes['uname'] = ''; |
||
| 351 | unset($attributes['relationships']); |
||
| 352 | $attributes['title'] = $this->request->getQuery('title'); |
||
| 353 | } catch (BEditaClientException $e) { |
||
| 354 | $this->log($e, LogLevel::ERROR); |
||
| 355 | $this->Flash->error($e->getMessage(), ['params' => $e]); |
||
| 356 | |||
| 357 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $id]); |
||
| 358 | } |
||
| 359 | $object = [ |
||
| 360 | 'type' => $this->objectType, |
||
| 361 | 'attributes' => $attributes, |
||
| 362 | ]; |
||
| 363 | $this->History->load($id, $object); |
||
| 364 | $this->set(compact('object', 'schema')); |
||
| 365 | $this->set('properties', $this->Properties->viewGroups($object, $this->objectType)); |
||
| 366 | |||
| 367 | return null; |
||
| 368 | } |
||
| 369 | |||
| 370 | /** |
||
| 371 | * Delete single resource. |
||
| 372 | * |
||
| 373 | * @return \Cake\Http\Response|null |
||
| 374 | */ |
||
| 375 | public function delete(): ?Response |
||
| 376 | { |
||
| 377 | $this->request->allowMethod(['post']); |
||
| 378 | $ids = []; |
||
| 379 | if (!empty($this->request->getData('ids'))) { |
||
| 380 | if (is_string($this->request->getData('ids'))) { |
||
| 381 | $ids = explode(',', $this->request->getData('ids')); |
||
| 382 | } |
||
| 383 | } else { |
||
| 384 | $ids = [$this->request->getData('id')]; |
||
| 385 | } |
||
| 386 | foreach ($ids as $id) { |
||
| 387 | try { |
||
| 388 | $this->apiClient->deleteObject($id, $this->objectType); |
||
| 389 | } catch (BEditaClientException $e) { |
||
| 390 | $this->log($e, LogLevel::ERROR); |
||
| 391 | $this->Flash->error($e->getMessage(), ['params' => $e]); |
||
| 392 | if (!empty($this->request->getData('id'))) { |
||
| 393 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $this->request->getData('id')]); |
||
| 394 | } |
||
| 395 | |||
| 396 | return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $id]); |
||
| 397 | } |
||
| 398 | } |
||
| 399 | $this->Flash->success(__('Object(s) deleted')); |
||
| 400 | |||
| 401 | return $this->redirect([ |
||
| 402 | '_name' => 'modules:list', |
||
| 403 | 'object_type' => $this->objectType, |
||
| 404 | ]); |
||
| 405 | } |
||
| 406 | |||
| 407 | /** |
||
| 408 | * Relation data load via API => `GET /:object_type/:id/related/:relation` |
||
| 409 | * |
||
| 410 | * @param string|int $id The object ID. |
||
| 411 | * @param string $relation The relation name. |
||
| 412 | * @return void |
||
| 413 | */ |
||
| 414 | public function relatedJson($id, string $relation): void |
||
| 415 | { |
||
| 416 | if ($id === 'new') { |
||
| 417 | $this->set('data', []); |
||
| 418 | $this->set('_serialize', ['data']); |
||
| 419 | |||
| 420 | return; |
||
| 421 | } |
||
| 422 | |||
| 423 | $this->request->allowMethod(['get']); |
||
| 424 | $query = $this->Query->prepare($this->request->getQueryParams()); |
||
| 425 | try { |
||
| 426 | $response = $this->apiClient->getRelated($id, $this->objectType, $relation, $query); |
||
| 427 | $response = $this->ApiFormatter->embedIncluded((array)$response); |
||
| 428 | } catch (BEditaClientException $error) { |
||
| 429 | $this->log($error, LogLevel::ERROR); |
||
| 430 | |||
| 431 | $this->set(compact('error')); |
||
| 432 | $this->set('_serialize', ['error']); |
||
| 433 | |||
| 434 | return; |
||
| 435 | } |
||
| 436 | |||
| 437 | $this->Thumbs->urls($response); |
||
| 438 | |||
| 439 | $this->set((array)$response); |
||
| 440 | $this->set('_serialize', array_keys($response)); |
||
| 441 | } |
||
| 442 | |||
| 443 | /** |
||
| 444 | * Load resources of $type callig api `GET /:type/` |
||
| 445 | * Json response |
||
| 446 | * |
||
| 447 | * @param string|int $id the object identifier. |
||
| 448 | * @param string $type the resource type name. |
||
| 449 | * @return void |
||
| 450 | */ |
||
| 451 | public function resourcesJson($id, string $type): void |
||
| 468 | } |
||
| 469 | |||
| 470 | /** |
||
| 471 | * Relation data load callig api `GET /:object_type/:id/relationships/:relation` |
||
| 472 | * Json response |
||
| 473 | * |
||
| 474 | * @param string|int $id The object ID. |
||
| 475 | * @param string $relation The relation name. |
||
| 476 | * @return void |
||
| 477 | */ |
||
| 478 | public function relationshipsJson($id, string $relation): void |
||
| 479 | { |
||
| 480 | $this->request->allowMethod(['get']); |
||
| 481 | $available = $this->availableRelationshipsUrl($relation); |
||
| 482 | |||
| 483 | try { |
||
| 484 | $query = $this->Query->prepare($this->request->getQueryParams()); |
||
| 485 | $response = $this->apiClient->get($available, $query); |
||
| 486 | |||
| 487 | $this->Thumbs->urls($response); |
||
| 488 | } catch (BEditaClientException $ex) { |
||
| 489 | $this->log($ex, LogLevel::ERROR); |
||
| 490 | |||
| 491 | $this->set([ |
||
| 492 | 'error' => $ex->getMessage(), |
||
| 493 | '_serialize' => ['error'], |
||
| 494 | ]); |
||
| 495 | |||
| 496 | return; |
||
| 497 | } |
||
| 498 | |||
| 499 | $this->set((array)$response); |
||
| 500 | $this->set('_serialize', array_keys($response)); |
||
| 501 | } |
||
| 502 | |||
| 503 | /** |
||
| 504 | * Retrieve URL to get objects available for a relation |
||
| 505 | * |
||
| 506 | * @param string $relation The relation name. |
||
| 507 | * @return string |
||
| 508 | */ |
||
| 509 | protected function availableRelationshipsUrl(string $relation): string |
||
| 510 | { |
||
| 511 | $defaults = [ |
||
| 512 | 'children' => '/objects', |
||
| 513 | 'parent' => '/folders', |
||
| 514 | 'parents' => '/folders', |
||
| 515 | ]; |
||
| 516 | $defaultUrl = (string)Hash::get($defaults, $relation); |
||
| 517 | if (!empty($defaultUrl)) { |
||
| 518 | return $defaultUrl; |
||
| 519 | } |
||
| 520 | |||
| 521 | $relationsSchema = $this->Schema->getRelationsSchema(); |
||
| 522 | $types = $this->Modules->relatedTypes($relationsSchema, $relation); |
||
| 523 | if (count($types) === 1) { |
||
| 524 | return sprintf('/%s', $types[0]); |
||
| 525 | } |
||
| 526 | |||
| 527 | return '/objects?filter[type][]=' . implode('&filter[type][]=', $types); |
||
| 528 | } |
||
| 529 | |||
| 530 | /** |
||
| 531 | * Bulk change actions for objects |
||
| 532 | * |
||
| 533 | * @return \Cake\Http\Response|null |
||
| 534 | */ |
||
| 535 | public function bulkActions(): ?Response |
||
| 574 | } |
||
| 575 | |||
| 576 | /** |
||
| 577 | * get object properties and format them for index |
||
| 578 | * |
||
| 579 | * @param string $objectType objecte type name |
||
| 580 | * |
||
| 581 | * @return array $schema |
||
| 582 | */ |
||
| 583 | public function getSchemaForIndex($objectType): array |
||
| 597 | } |
||
| 598 | } |
||
| 599 |
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.