| 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 |