| Total Complexity | 44 | 
| Total Lines | 510 | 
| Duplicated Lines | 0 % | 
| Changes | 3 | ||
| Bugs | 1 | Features | 0 | 
Complex classes like TreeController 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 TreeController, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 27 | class TreeController extends AppController  | 
            ||
| 28 | { | 
            ||
| 29 | /**  | 
            ||
| 30 | * @inheritDoc  | 
            ||
| 31 | */  | 
            ||
| 32 | public function initialize(): void  | 
            ||
| 37 | }  | 
            ||
| 38 | |||
| 39 | /**  | 
            ||
| 40 | * Get tree data.  | 
            ||
| 41 | * Use this for /tree?filter[roots]&... and /tree?filter[parent]=x&...  | 
            ||
| 42 | * Use cache to store data.  | 
            ||
| 43 | *  | 
            ||
| 44 | * @return void  | 
            ||
| 45 | */  | 
            ||
| 46 | public function get(): void  | 
            ||
| 54 | }  | 
            ||
| 55 | |||
| 56 | /**  | 
            ||
| 57 | * Get all tree data.  | 
            ||
| 58 | * Use cache to store data.  | 
            ||
| 59 | *  | 
            ||
| 60 | * @return void  | 
            ||
| 61 | */  | 
            ||
| 62 | public function loadAll(): void  | 
            ||
| 69 | }  | 
            ||
| 70 | |||
| 71 | /**  | 
            ||
| 72 | * Get children of a folder by ID.  | 
            ||
| 73 | * Use cache to store data.  | 
            ||
| 74 | *  | 
            ||
| 75 | * @param string $id The ID.  | 
            ||
| 76 | * @return void  | 
            ||
| 77 | */  | 
            ||
| 78 | public function children(string $id): void  | 
            ||
| 79 |     { | 
            ||
| 80 | $this->getRequest()->allowMethod(['get']);  | 
            ||
| 81 |         $this->viewBuilder()->setClassName('Json'); | 
            ||
| 82 | $data = $meta = [];  | 
            ||
| 83 |         try { | 
            ||
| 84 | $query = $this->getRequest()->getQueryParams();  | 
            ||
| 85 |             $response = $this->apiClient->get(sprintf('/folders/%s/children', $id), $query); | 
            ||
| 86 | $data = (array)Hash::get($response, 'data');  | 
            ||
| 87 |             foreach ($data as &$item) { | 
            ||
| 88 | $item = $this->minimalDataWithMeta((array)$item);  | 
            ||
| 89 | }  | 
            ||
| 90 | $meta = (array)Hash::get($response, 'meta');  | 
            ||
| 91 |         } catch (BEditaClientException $e) { | 
            ||
| 92 | $this->log($e->getMessage(), LogLevel::ERROR);  | 
            ||
| 93 | }  | 
            ||
| 94 |         $this->set('data', $data); | 
            ||
| 95 |         $this->set('meta', $meta); | 
            ||
| 96 | $this->setSerialize(['data', 'meta']);  | 
            ||
| 97 | }  | 
            ||
| 98 | |||
| 99 | /**  | 
            ||
| 100 | * Get node by ID.  | 
            ||
| 101 | * Use cache to store data.  | 
            ||
| 102 | *  | 
            ||
| 103 | * @param string $id The ID.  | 
            ||
| 104 | * @return void  | 
            ||
| 105 | */  | 
            ||
| 106 | public function node(string $id): void  | 
            ||
| 107 |     { | 
            ||
| 108 | $this->getRequest()->allowMethod(['get']);  | 
            ||
| 109 |         $this->viewBuilder()->setClassName('Json'); | 
            ||
| 110 | $node = $this->fetchNodeData($id);  | 
            ||
| 111 |         $this->set('node', $node); | 
            ||
| 112 | $this->setSerialize(['node']);  | 
            ||
| 113 | }  | 
            ||
| 114 | |||
| 115 | /**  | 
            ||
| 116 | * Get parent by ID.  | 
            ||
| 117 | * Use cache to store data.  | 
            ||
| 118 | *  | 
            ||
| 119 | * @param string $id The ID.  | 
            ||
| 120 | * @return void  | 
            ||
| 121 | */  | 
            ||
| 122 | public function parent(string $id): void  | 
            ||
| 129 | }  | 
            ||
| 130 | |||
| 131 | /**  | 
            ||
| 132 | * Get parents by ID and type.  | 
            ||
| 133 | * Use cache to store data.  | 
            ||
| 134 | *  | 
            ||
| 135 | * @param string $type The type.  | 
            ||
| 136 | * @param string $id The ID.  | 
            ||
| 137 | * @return void  | 
            ||
| 138 | */  | 
            ||
| 139 | public function parents(string $type, string $id): void  | 
            ||
| 146 | }  | 
            ||
| 147 | |||
| 148 | /**  | 
            ||
| 149 | * Saves the current slug  | 
            ||
| 150 | *  | 
            ||
| 151 | * @return \Cake\Http\Response|null  | 
            ||
| 152 | */  | 
            ||
| 153 | public function slug(): ?Response  | 
            ||
| 154 |     { | 
            ||
| 155 | $this->getRequest()->allowMethod(['post']);  | 
            ||
| 156 |         $this->viewBuilder()->setClassName('Json'); | 
            ||
| 157 | $response = $error = null;  | 
            ||
| 158 |         try { | 
            ||
| 159 | $data = (array)$this->getRequest()->getData();  | 
            ||
| 160 | $body = [  | 
            ||
| 161 | 'data' => [  | 
            ||
| 162 | [  | 
            ||
| 163 | 'id' => (string)Hash::get($data, 'id'),  | 
            ||
| 164 | 'type' => (string)Hash::get($data, 'type'),  | 
            ||
| 165 | 'meta' => [  | 
            ||
| 166 | 'relation' => [  | 
            ||
| 167 | 'slug' => (string)Hash::get($data, 'slug'),  | 
            ||
| 168 | ],  | 
            ||
| 169 | ],  | 
            ||
| 170 | ],  | 
            ||
| 171 | ],  | 
            ||
| 172 | ];  | 
            ||
| 173 | $response = $this->apiClient->post(  | 
            ||
| 174 |                 sprintf('/folders/%s/relationships/children', (string)Hash::get($data, 'parent')), | 
            ||
| 175 | json_encode($body)  | 
            ||
| 176 | );  | 
            ||
| 177 | // Clearing cache after successful save  | 
            ||
| 178 |             Cache::clearGroup('tree', TreeCacheEventHandler::CACHE_CONFIG); | 
            ||
| 179 |         } catch (BEditaClientException $err) { | 
            ||
| 180 | $error = $err->getMessage();  | 
            ||
| 181 | $this->log($error, 'error');  | 
            ||
| 182 |             $this->set('error', $error); | 
            ||
| 183 | }  | 
            ||
| 184 |         $this->set('response', $response); | 
            ||
| 185 |         $this->set('error', $error); | 
            ||
| 186 | $this->setSerialize(['response', 'error']);  | 
            ||
| 187 | |||
| 188 | return null;  | 
            ||
| 189 | }  | 
            ||
| 190 | |||
| 191 | /**  | 
            ||
| 192 | * Get compact tree data.  | 
            ||
| 193 | * Use cache to store data.  | 
            ||
| 194 | *  | 
            ||
| 195 | * @return array  | 
            ||
| 196 | */  | 
            ||
| 197 | public function compactTreeData(): array  | 
            ||
| 198 |     { | 
            ||
| 199 |         $objectType = $this->getRequest()->getParam('object_type'); | 
            ||
| 200 |         $key = CacheTools::cacheKey(sprintf('compact-tree-%s', $objectType)); | 
            ||
| 201 |         $noCache = (bool)$this->getRequest()->getQuery('no_cache'); | 
            ||
| 202 |         if ($noCache === true) { | 
            ||
| 203 |             Cache::clearGroup('tree', TreeCacheEventHandler::CACHE_CONFIG); | 
            ||
| 204 | }  | 
            ||
| 205 | $data = [];  | 
            ||
| 206 |         try { | 
            ||
| 207 | $data = Cache::remember(  | 
            ||
| 208 | $key,  | 
            ||
| 209 |                 function () { | 
            ||
| 210 | return $this->fetchCompactTreeData();  | 
            ||
| 211 | },  | 
            ||
| 212 | TreeCacheEventHandler::CACHE_CONFIG  | 
            ||
| 213 | );  | 
            ||
| 214 |         } catch (BEditaClientException $e) { | 
            ||
| 215 | $this->log($e->getMessage(), LogLevel::ERROR);  | 
            ||
| 216 | }  | 
            ||
| 217 | |||
| 218 | return $data;  | 
            ||
| 219 | }  | 
            ||
| 220 | |||
| 221 | /**  | 
            ||
| 222 | * Get tree data by query params.  | 
            ||
| 223 | * Use cache to store data.  | 
            ||
| 224 | *  | 
            ||
| 225 | * @param array $query Query params.  | 
            ||
| 226 | * @return array  | 
            ||
| 227 | */  | 
            ||
| 228 | public function treeData(array $query): array  | 
            ||
| 229 |     { | 
            ||
| 230 | $filter = Hash::get($query, 'filter', []);  | 
            ||
| 231 |         $subkey = !empty($filter['parent']) ? sprintf('parent-%s', $filter['parent']) : 'roots'; | 
            ||
| 232 | $tmp = array_filter(  | 
            ||
| 233 | $query,  | 
            ||
| 234 |             function ($key) { | 
            ||
| 235 | return $key !== 'filter';  | 
            ||
| 236 | },  | 
            ||
| 237 | ARRAY_FILTER_USE_KEY  | 
            ||
| 238 | );  | 
            ||
| 239 |         $key = CacheTools::cacheKey(sprintf('tree-%s-%s', $subkey, md5(serialize($tmp)))); | 
            ||
| 240 | $data = [];  | 
            ||
| 241 |         try { | 
            ||
| 242 | $data = Cache::remember(  | 
            ||
| 243 | $key,  | 
            ||
| 244 |                 function () use ($query) { | 
            ||
| 245 | return $this->fetchTreeData($query);  | 
            ||
| 246 | },  | 
            ||
| 247 | TreeCacheEventHandler::CACHE_CONFIG  | 
            ||
| 248 | );  | 
            ||
| 249 |         } catch (BEditaClientException $e) { | 
            ||
| 250 | // Something bad happened  | 
            ||
| 251 | $this->log($e->getMessage(), LogLevel::ERROR);  | 
            ||
| 252 | |||
| 253 | return [];  | 
            ||
| 254 | }  | 
            ||
| 255 | |||
| 256 | return $data;  | 
            ||
| 257 | }  | 
            ||
| 258 | |||
| 259 | /**  | 
            ||
| 260 | * Get node from ID.  | 
            ||
| 261 | * It uses cache to store data.  | 
            ||
| 262 | *  | 
            ||
| 263 | * @param string $id The ID.  | 
            ||
| 264 | * @return array|null  | 
            ||
| 265 | */  | 
            ||
| 266 | public function fetchNodeData(string $id): ?array  | 
            ||
| 289 | }  | 
            ||
| 290 | |||
| 291 | /**  | 
            ||
| 292 | * Get parent from ID.  | 
            ||
| 293 | * It uses cache to store data.  | 
            ||
| 294 | *  | 
            ||
| 295 | * @param string $id The ID.  | 
            ||
| 296 | * @return array|null  | 
            ||
| 297 | */  | 
            ||
| 298 | public function fetchParentData(string $id): ?array  | 
            ||
| 321 | }  | 
            ||
| 322 | |||
| 323 | /**  | 
            ||
| 324 | * Get parent from ID.  | 
            ||
| 325 | * It uses cache to store data.  | 
            ||
| 326 | *  | 
            ||
| 327 | * @param string $id The ID.  | 
            ||
| 328 | * @param string $type The type.  | 
            ||
| 329 | * @return array  | 
            ||
| 330 | */  | 
            ||
| 331 | public function fetchParentsData(string $id, string $type): array  | 
            ||
| 357 | }  | 
            ||
| 358 | |||
| 359 | /**  | 
            ||
| 360 | * Fetch compact tree data from API.  | 
            ||
| 361 | * Retrieve minimal data for folders: id, status, title, meta.  | 
            ||
| 362 | * Return tree and folders.  | 
            ||
| 363 | * Return an array with 'tree' and 'folders' keys.  | 
            ||
| 364 | *  | 
            ||
| 365 | * @return array  | 
            ||
| 366 | */  | 
            ||
| 367 | protected function fetchCompactTreeData(): array  | 
            ||
| 368 |     { | 
            ||
| 369 | $done = false;  | 
            ||
| 370 | $page = 1;  | 
            ||
| 371 | $pageSize = 100;  | 
            ||
| 372 | $folders = [];  | 
            ||
| 373 | $paths = [];  | 
            ||
| 374 |         while (!$done) { | 
            ||
| 375 |             $response = ApiClientProvider::getApiClient()->get('/folders', [ | 
            ||
| 376 | 'page_size' => $pageSize,  | 
            ||
| 377 | 'page' => $page,  | 
            ||
| 378 | ]);  | 
            ||
| 379 | $data = (array)Hash::get($response, 'data');  | 
            ||
| 380 |             foreach ($data as $item) { | 
            ||
| 381 | $folders[$item['id']] = $this->minimalDataWithMeta((array)$item);  | 
            ||
| 382 | $path = (string)Hash::get($item, 'meta.path');  | 
            ||
| 383 | $paths[$path] = $item['id'];  | 
            ||
| 384 | }  | 
            ||
| 385 | $page++;  | 
            ||
| 386 | $meta = (array)Hash::get($response, 'meta');  | 
            ||
| 387 |             if ($page > (int)Hash::get($meta, 'pagination.page_count')) { | 
            ||
| 388 | $done = true;  | 
            ||
| 389 | }  | 
            ||
| 390 | }  | 
            ||
| 391 | // organize the tree as roots and children  | 
            ||
| 392 | $tree = [];  | 
            ||
| 393 |         foreach ($paths as $path => $id) { | 
            ||
| 394 | $countSlash = substr_count($path, '/');  | 
            ||
| 395 |             if ($countSlash === 1) { | 
            ||
| 396 |                 $tree[$id] = compact('id'); | 
            ||
| 397 | continue;  | 
            ||
| 398 | }  | 
            ||
| 399 | |||
| 400 | $parentPath = substr($path, 0, strrpos($path, '/'));  | 
            ||
| 401 | $parentId = $paths[$parentPath];  | 
            ||
| 402 |             if (!empty($parentId)) { | 
            ||
| 403 | $this->pushIntoTree($tree, $parentId, $id, 'subfolders');  | 
            ||
| 404 | }  | 
            ||
| 405 | }  | 
            ||
| 406 | |||
| 407 |         return compact('tree', 'folders'); | 
            ||
| 408 | }  | 
            ||
| 409 | |||
| 410 | /**  | 
            ||
| 411 | * Push child into tree, searching parent inside the tree structure.  | 
            ||
| 412 | *  | 
            ||
| 413 | * @param array $tree The tree.  | 
            ||
| 414 | * @param string $searchParentId The parent ID.  | 
            ||
| 415 | * @param string $childId The child ID.  | 
            ||
| 416 | * @param string $subtreeKey The subtree key.  | 
            ||
| 417 | * @return bool  | 
            ||
| 418 | */  | 
            ||
| 419 | public function pushIntoTree(array &$tree, string $searchParentId, string $childId, string $subtreeKey): bool  | 
            ||
| 439 | }  | 
            ||
| 440 | |||
| 441 | /**  | 
            ||
| 442 | * Fetch tree data from API.  | 
            ||
| 443 | * Retrieve minimal data for folders: id, status, title.  | 
            ||
| 444 | * Return data and meta (no links, no included).  | 
            ||
| 445 | *  | 
            ||
| 446 | * @param array $query Query params.  | 
            ||
| 447 | * @return array  | 
            ||
| 448 | */  | 
            ||
| 449 | protected function fetchTreeData(array $query): array  | 
            ||
| 450 |     { | 
            ||
| 451 | $fields = 'id,status,title,perms,relation,slug_path';  | 
            ||
| 452 |         $response = ApiClientProvider::getApiClient()->get('/folders', compact('fields') + $query); | 
            ||
| 453 | $data = (array)Hash::get($response, 'data');  | 
            ||
| 454 | $meta = (array)Hash::get($response, 'meta');  | 
            ||
| 455 |         foreach ($data as &$item) { | 
            ||
| 456 | $item = $this->minimalData((array)$item);  | 
            ||
| 457 | }  | 
            ||
| 458 | |||
| 459 |         return compact('data', 'meta'); | 
            ||
| 460 | }  | 
            ||
| 461 | |||
| 462 | /**  | 
            ||
| 463 | * Get minimal data for object.  | 
            ||
| 464 | *  | 
            ||
| 465 | * @param array $fullData Full data.  | 
            ||
| 466 | * @return array  | 
            ||
| 467 | */  | 
            ||
| 468 | protected function minimalData(array $fullData): array  | 
            ||
| 469 |     { | 
            ||
| 470 |         if (empty($fullData)) { | 
            ||
| 471 | return [];  | 
            ||
| 472 | }  | 
            ||
| 473 | $meta = (array)Hash::get($fullData, 'meta');  | 
            ||
| 474 | $meta['slug_path_compact'] = $this->slugPathCompact((array)Hash::get($meta, 'slug_path'));  | 
            ||
| 475 | |||
| 476 | return [  | 
            ||
| 477 | 'id' => (string)Hash::get($fullData, 'id'),  | 
            ||
| 478 | 'type' => (string)Hash::get($fullData, 'type'),  | 
            ||
| 479 | 'attributes' => [  | 
            ||
| 480 | 'title' => (string)Hash::get($fullData, 'attributes.title'),  | 
            ||
| 481 | 'status' => (string)Hash::get($fullData, 'attributes.status'),  | 
            ||
| 482 | ],  | 
            ||
| 483 | 'meta' => $meta,  | 
            ||
| 484 | ];  | 
            ||
| 485 | }  | 
            ||
| 486 | |||
| 487 | /**  | 
            ||
| 488 | * Get minimal data for object with meta.  | 
            ||
| 489 | *  | 
            ||
| 490 | * @param array $fullData Full data.  | 
            ||
| 491 | * @return array|null  | 
            ||
| 492 | */  | 
            ||
| 493 | protected function minimalDataWithMeta(array $fullData): ?array  | 
            ||
| 494 |     { | 
            ||
| 495 |         if (empty($fullData)) { | 
            ||
| 496 | return null;  | 
            ||
| 497 | }  | 
            ||
| 498 | |||
| 499 | return [  | 
            ||
| 500 | 'id' => (string)Hash::get($fullData, 'id'),  | 
            ||
| 501 | 'type' => (string)Hash::get($fullData, 'type'),  | 
            ||
| 502 | 'attributes' => [  | 
            ||
| 503 | 'title' => (string)Hash::get($fullData, 'attributes.title'),  | 
            ||
| 504 | 'uname' => (string)Hash::get($fullData, 'attributes.uname'),  | 
            ||
| 505 | 'lang' => (string)Hash::get($fullData, 'attributes.lang'),  | 
            ||
| 506 | 'status' => (string)Hash::get($fullData, 'attributes.status'),  | 
            ||
| 507 | ],  | 
            ||
| 508 | 'meta' => [  | 
            ||
| 509 | 'modified' => (string)Hash::get($fullData, 'meta.modified'),  | 
            ||
| 510 | 'path' => (string)Hash::get($fullData, 'meta.path'),  | 
            ||
| 511 | 'slug_path' => (array)Hash::get($fullData, 'meta.slug_path'),  | 
            ||
| 512 | 'slug_path_compact' => $this->slugPathCompact((array)Hash::get($fullData, 'meta.slug_path')),  | 
            ||
| 513 | 'relation' => [  | 
            ||
| 514 | 'canonical' => (string)Hash::get($fullData, 'meta.relation.canonical'),  | 
            ||
| 515 | 'depth_level' => (string)Hash::get($fullData, 'meta.relation.depth_level'),  | 
            ||
| 516 | 'menu' => (string)Hash::get($fullData, 'meta.relation.menu'),  | 
            ||
| 517 | 'slug' => (string)Hash::get($fullData, 'meta.relation.slug'),  | 
            ||
| 518 | ],  | 
            ||
| 519 | ],  | 
            ||
| 520 | ];  | 
            ||
| 521 | }  | 
            ||
| 522 | |||
| 523 | /**  | 
            ||
| 524 | * Get compact slug path.  | 
            ||
| 525 | *  | 
            ||
| 526 | * @param array $slugPath Slug path.  | 
            ||
| 527 | * @return string  | 
            ||
| 528 | */  | 
            ||
| 529 | protected function slugPathCompact(array $slugPath): string  | 
            ||
| 537 | }  | 
            ||
| 538 | }  | 
            ||
| 539 |