Complex classes like AcoManager 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 AcoManager, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 35 | class AcoManager  | 
            ||
| 36 | { | 
            ||
| 37 | |||
| 38 | use ModelAwareTrait;  | 
            ||
| 39 | use StaticCacheTrait;  | 
            ||
| 40 | |||
| 41 | /**  | 
            ||
| 42 | * Name of the plugin being managed.  | 
            ||
| 43 | *  | 
            ||
| 44 | * @var string  | 
            ||
| 45 | */  | 
            ||
| 46 | protected $_pluginName;  | 
            ||
| 47 | |||
| 48 | /**  | 
            ||
| 49 | * Constructor.  | 
            ||
| 50 | *  | 
            ||
| 51 | * @param string $pluginName The plugin being managed  | 
            ||
| 52 | * @throws \Cake\Error\FatalErrorException When no plugin name is given.  | 
            ||
| 53 | */  | 
            ||
| 54 | public function __construct($pluginName = null)  | 
            ||
| 67 | |||
| 68 | /**  | 
            ||
| 69 | * Grants permissions to all users within $roles over the given $aco.  | 
            ||
| 70 | *  | 
            ||
| 71 | * ### ACO path format:  | 
            ||
| 72 | *  | 
            ||
| 73 | * - `ControllerName/`: Maps to \<PluginName>\Controller\ControllerName::index()  | 
            ||
| 74 | * - `ControllerName`: Same.  | 
            ||
| 75 | * - `ControllerName/action_name`: Maps to \<PluginName>\Controller\ControllerName::action_name()  | 
            ||
| 76 | * - `Prefix/ControllerName/action_name`: Maps to \<PluginName>\Controller\Prefix\ControllerName::action_name()  | 
            ||
| 77 | *  | 
            ||
| 78 | * @param string $path ACO path as described above  | 
            ||
| 79 | * @param array $roles List of user roles to grant access to. If not given,  | 
            ||
| 80 | * $path cannot be used by anyone but "administrators"  | 
            ||
| 81 | * @return bool True on success  | 
            ||
| 82 | */  | 
            ||
| 83 | public function add($path, $roles = [])  | 
            ||
| 84 |     { | 
            ||
| 85 | $path = $this->_parseAco($path);  | 
            ||
| 86 |         if (!$path) { | 
            ||
| 87 | return false;  | 
            ||
| 88 | }  | 
            ||
| 89 | |||
| 90 | // path already exists  | 
            ||
| 91 | $contents = $this->Acos->node($path);  | 
            ||
| 92 |         if (is_object($contents)) { | 
            ||
| 93 |             $contents = $contents->extract('alias')->toArray(); | 
            ||
| 94 | }  | 
            ||
| 95 |         if (!empty($contents) && implode('/', $contents) === $path) { | 
            ||
| 96 | return true;  | 
            ||
| 97 | }  | 
            ||
| 98 | |||
| 99 | $parent = null;  | 
            ||
| 100 | $current = null;  | 
            ||
| 101 |         $parts = explode('/', $path); | 
            ||
| 102 |         $this->Acos->connection()->transactional(function () use ($parts, $current, &$parent, $path) { | 
            ||
| 103 |             foreach ($parts as $alias) { | 
            ||
| 104 | $current[] = $alias;  | 
            ||
| 105 |                 $content = $this->Acos->node(implode('/', $current)); | 
            ||
| 106 | |||
| 107 |                 if ($content) { | 
            ||
| 108 | $parent = $content->first();  | 
            ||
| 109 |                 } else { | 
            ||
| 110 | $acoEntity = $this->Acos->newEntity([  | 
            ||
| 111 | 'parent_id' => (isset($parent->id) ? $parent->id : null),  | 
            ||
| 112 | 'plugin' => $this->_pluginName,  | 
            ||
| 113 | 'alias' => $alias,  | 
            ||
| 114 | 'alias_hash' => md5($alias),  | 
            ||
| 115 | ]);  | 
            ||
| 116 | $parent = $this->Acos->save($acoEntity);  | 
            ||
| 117 | }  | 
            ||
| 118 | }  | 
            ||
| 119 | });  | 
            ||
| 120 | |||
| 121 |         if ($parent) { | 
            ||
| 122 | // register roles  | 
            ||
| 123 |             if (!empty($roles)) { | 
            ||
| 124 |                 $this->loadModel('User.Permissions'); | 
            ||
| 125 | $roles = $this->Acos->Roles  | 
            ||
| 126 | ->find()  | 
            ||
| 127 | ->select(['id'])  | 
            ||
| 128 | ->where(['Roles.slug IN' => $roles])  | 
            ||
| 129 | ->all();  | 
            ||
| 130 | |||
| 131 |                 foreach ($roles as $role) { | 
            ||
| 132 | $permissionEntity = $this->Permissions->newEntity([  | 
            ||
| 133 | 'aco_id' => $parent->id, // action  | 
            ||
| 134 | 'role_id' => $role->id,  | 
            ||
| 135 | ]);  | 
            ||
| 136 | $this->Permissions->save($permissionEntity);  | 
            ||
| 137 | }  | 
            ||
| 138 | }  | 
            ||
| 139 | |||
| 140 | return true;  | 
            ||
| 141 | }  | 
            ||
| 142 | |||
| 143 | return false;  | 
            ||
| 144 | }  | 
            ||
| 145 | |||
| 146 | /**  | 
            ||
| 147 | * Removes the given ACO and its permissions.  | 
            ||
| 148 | *  | 
            ||
| 149 | * @param string $path ACO path e.g. `ControllerName/action_name`  | 
            ||
| 150 | * @return bool True on success, false if path was not found  | 
            ||
| 151 | */  | 
            ||
| 152 | public function remove($path)  | 
            ||
| 153 |     { | 
            ||
| 154 | $contents = $this->Acos->node($path);  | 
            ||
| 155 |         if (!$contents) { | 
            ||
| 156 | return false;  | 
            ||
| 157 | }  | 
            ||
| 158 | |||
| 159 | $content = $contents->first();  | 
            ||
| 160 | $this->Acos->removeFromTree($content);  | 
            ||
| 161 | $this->Acos->delete($content);  | 
            ||
| 162 | |||
| 163 | return true;  | 
            ||
| 164 | }  | 
            ||
| 165 | |||
| 166 | /**  | 
            ||
| 167 | * This method should never be used unless you know what are you doing.  | 
            ||
| 168 | *  | 
            ||
| 169 | * Populates the "acos" DB with information of every installed plugin, or  | 
            ||
| 170 | * for the given plugin. It will automatically extracts plugin's controllers  | 
            ||
| 171 | * and actions for creating a tree structure as follow:  | 
            ||
| 172 | *  | 
            ||
| 173 | * - PluginName  | 
            ||
| 174 | * - Admin  | 
            ||
| 175 | * - PrivateController  | 
            ||
| 176 | * - index  | 
            ||
| 177 | * - some_action  | 
            ||
| 178 | * - ControllerName  | 
            ||
| 179 | * - index  | 
            ||
| 180 | * - another_action  | 
            ||
| 181 | *  | 
            ||
| 182 | * After tree is created you should be able to change permissions using  | 
            ||
| 183 | * User's permissions section in backend.  | 
            ||
| 184 | *  | 
            ||
| 185 | * @param string $for Optional, build ACOs for the given plugin, or all plugins  | 
            ||
| 186 | * if not given  | 
            ||
| 187 | * @param bool $sync Whether to sync the tree or not. When syncing all invalid  | 
            ||
| 188 | * ACO entries will be removed from the tree, also new ones will be added. When  | 
            ||
| 189 | * syn is set to false only new ACO entries will be added, any invalid entry  | 
            ||
| 190 | * will remain in the tree. Defaults to false  | 
            ||
| 191 | * @return bool True on success, false otherwise  | 
            ||
| 192 | */  | 
            ||
| 193 | public static function buildAcos($for = null, $sync = false)  | 
            ||
| 194 |     { | 
            ||
| 195 |         if (function_exists('ini_set')) { | 
            ||
| 196 |             ini_set('max_execution_time', 300); | 
            ||
| 197 |         } elseif (function_exists('set_time_limit')) { | 
            ||
| 198 | set_time_limit(300);  | 
            ||
| 199 | }  | 
            ||
| 200 | |||
| 201 |         if ($for === null) { | 
            ||
| 202 | $plugins = plugin()->toArray();  | 
            ||
| 203 |         } else { | 
            ||
| 204 |             try { | 
            ||
| 205 | $plugins = [plugin($for)];  | 
            ||
| 206 |             } catch (\Exception $e) { | 
            ||
| 207 | return false;  | 
            ||
| 208 | }  | 
            ||
| 209 | }  | 
            ||
| 210 | |||
| 211 | $added = [];  | 
            ||
| 212 |         foreach ($plugins as $plugin) { | 
            ||
| 213 |             if (!Plugin::exists($plugin->name)) { | 
            ||
| 214 | continue;  | 
            ||
| 215 | }  | 
            ||
| 216 | |||
| 217 | $aco = new AcoManager($plugin->name);  | 
            ||
| 218 |             $controllerDir = normalizePath("{$plugin->path}/src/Controller/"); | 
            ||
| 219 | $folder = new Folder($controllerDir);  | 
            ||
| 220 |             $controllers = $folder->findRecursive('.*Controller\.php'); | 
            ||
| 221 | |||
| 222 |             foreach ($controllers as $controller) { | 
            ||
| 223 | $controller = str_replace([$controllerDir, '.php'], '', $controller);  | 
            ||
| 224 | $className = $plugin->name . '\\' . 'Controller\\' . str_replace(DS, '\\', $controller);  | 
            ||
| 225 | $methods = static::_controllerMethods($className);  | 
            ||
| 226 | |||
| 227 |                 if (!empty($methods)) { | 
            ||
| 228 |                     $path = explode('Controller\\', $className)[1]; | 
            ||
| 229 |                     $path = str_replace_last('Controller', '', $path); | 
            ||
| 230 |                     $path = str_replace('\\', '/', $path); | 
            ||
| 231 | |||
| 232 |                     foreach ($methods as $method) { | 
            ||
| 233 |                         if ($aco->add("{$path}/{$method}")) { | 
            ||
| 234 |                             $added[] = "{$plugin->name}/{$path}/{$method}"; | 
            ||
| 235 | }  | 
            ||
| 236 | }  | 
            ||
| 237 | }  | 
            ||
| 238 | }  | 
            ||
| 239 | }  | 
            ||
| 240 | |||
| 241 |         if ($sync && isset($aco)) { | 
            ||
| 242 | $aco->Acos->recover();  | 
            ||
| 243 | $existingPaths = static::paths($for);  | 
            ||
| 244 |             foreach ($existingPaths as $exists) { | 
            ||
| 245 |                 if (!in_array($exists, $added)) { | 
            ||
| 246 | $aco->remove($exists);  | 
            ||
| 247 | }  | 
            ||
| 248 | }  | 
            ||
| 249 | $validLeafs = $aco->Acos  | 
            ||
| 250 | ->find()  | 
            ||
| 251 | ->select(['id'])  | 
            ||
| 252 | ->where([  | 
            ||
| 253 | 'id NOT IN' => $aco->Acos->find()  | 
            ||
| 254 | ->select(['parent_id'])  | 
            ||
| 255 | ->where(['parent_id IS NOT' => null])  | 
            ||
| 256 | ]);  | 
            ||
| 257 | |||
| 258 | $aco->Acos->Permissions->deleteAll([  | 
            ||
| 259 | 'aco_id NOT IN' => $validLeafs  | 
            ||
| 260 | ]);  | 
            ||
| 261 | }  | 
            ||
| 262 | |||
| 263 | return true;  | 
            ||
| 264 | }  | 
            ||
| 265 | |||
| 266 | /**  | 
            ||
| 267 | * Gets a list of existing ACO paths for the given plugin, or the entire list  | 
            ||
| 268 | * if no plugin is given.  | 
            ||
| 269 | *  | 
            ||
| 270 | * @param string $for Optional plugin name. e.g. `Taxonomy`  | 
            ||
| 271 | * @return array All registered ACO paths  | 
            ||
| 272 | */  | 
            ||
| 273 | public static function paths($for = null)  | 
            ||
| 274 |     { | 
            ||
| 275 |         if ($for !== null) { | 
            ||
| 276 |             try { | 
            ||
| 277 | $for = plugin($for)->name;  | 
            ||
| 278 |             } catch (\Exception $e) { | 
            ||
| 279 | return [];  | 
            ||
| 280 | }  | 
            ||
| 281 | }  | 
            ||
| 282 | |||
| 283 |         $cacheKey = "paths({$for})"; | 
            ||
| 284 | $paths = static::cache($cacheKey);  | 
            ||
| 285 | |||
| 286 |         if ($paths === null) { | 
            ||
| 287 | $paths = [];  | 
            ||
| 288 |             $aco = new AcoManager('__dummy__'); | 
            ||
| 289 |             $aco->loadModel('User.Acos'); | 
            ||
| 290 | $leafs = $aco->Acos  | 
            ||
| 291 |                 ->find('all') | 
            ||
| 292 | ->select(['id'])  | 
            ||
| 293 | ->where([  | 
            ||
| 294 | 'Acos.id NOT IN' => $aco->Acos  | 
            ||
| 295 | ->find()  | 
            ||
| 296 | ->select(['parent_id'])  | 
            ||
| 297 | ->where(['parent_id IS NOT' => null])  | 
            ||
| 298 | ]);  | 
            ||
| 299 | |||
| 300 |             foreach ($leafs as $leaf) { | 
            ||
| 301 | $path = $aco->Acos  | 
            ||
| 302 |                     ->find('path', ['for' => $leaf->id]) | 
            ||
| 303 |                     ->extract('alias') | 
            ||
| 304 | ->toArray();  | 
            ||
| 305 |                 $path = implode('/', $path); | 
            ||
| 306 | |||
| 307 | if ($for === null ||  | 
            ||
| 308 |                     ($for !== null && str_starts_with($path, "{$for}/")) | 
            ||
| 309 |                 ) { | 
            ||
| 310 | $paths[] = $path;  | 
            ||
| 311 | }  | 
            ||
| 312 | }  | 
            ||
| 313 | static::cache($cacheKey, $paths);  | 
            ||
| 314 | }  | 
            ||
| 315 | |||
| 316 | return $paths;  | 
            ||
| 317 | }  | 
            ||
| 318 | |||
| 319 | /**  | 
            ||
| 320 | * Extracts method names of the given controller class.  | 
            ||
| 321 | *  | 
            ||
| 322 | * @param string $className Fully qualified name  | 
            ||
| 323 | * @return array List of method names  | 
            ||
| 324 | */  | 
            ||
| 325 | protected static function _controllerMethods($className)  | 
            ||
| 346 | |||
| 347 | /**  | 
            ||
| 348 | * Sanitizes the given ACO path.  | 
            ||
| 349 | *  | 
            ||
| 350 | * This methods can return an array with the following keys if `$string` option  | 
            ||
| 351 | * is set to false:  | 
            ||
| 352 | *  | 
            ||
| 353 | * - `plugin`: The name of the plugin being managed by this class.  | 
            ||
| 354 | * - `prefix`: ACO prefix, for example `Admin` for controller within /Controller/Admin/  | 
            ||
| 355 | * it may be empty, if not prefix is found.  | 
            ||
| 356 | * - `controller`: Controller name. e.g.: `MySuperController`  | 
            ||
| 357 | * - `action`: Controller's action. e.g.: `mini_action`, `index` by default  | 
            ||
| 358 | *  | 
            ||
| 359 | * For example:  | 
            ||
| 360 | *  | 
            ||
| 361 | * `Admin/Users/`  | 
            ||
| 362 | *  | 
            ||
| 363 | * Returns:  | 
            ||
| 364 | *  | 
            ||
| 365 | * - plugin: YourPlugin  | 
            ||
| 366 | * - prefix: Admin  | 
            ||
| 367 | * - controller: Users  | 
            ||
| 368 | * - action: index  | 
            ||
| 369 | *  | 
            ||
| 370 | * Where "YourPlugin" is the plugin name passed to this class's constructor.  | 
            ||
| 371 | *  | 
            ||
| 372 | * @param string $aco An ACO path to parse  | 
            ||
| 373 | * @param bool $string Indicates if it should return a string format path (/Controller/action)  | 
            ||
| 374 | * @return bool|array|string An array as described above or false if an invalid $aco was given  | 
            ||
| 375 | */  | 
            ||
| 376 | protected function _parseAco($aco, $string = true)  | 
            ||
| 420 | |||
| 421 | /**  | 
            ||
| 422 | * Gets a CamelizedList of all existing router prefixes.  | 
            ||
| 423 | *  | 
            ||
| 424 | * @return array  | 
            ||
| 425 | */  | 
            ||
| 426 | protected function _routerPrefixes()  | 
            ||
| 447 | }  | 
            ||
| 448 |