Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like PluginPackage 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 PluginPackage, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
35 | class PluginPackage extends BasePackage |
||
36 | { |
||
37 | |||
38 | /** |
||
39 | * Plugin information. |
||
40 | * |
||
41 | * @var array |
||
42 | */ |
||
43 | protected $_info = []; |
||
44 | |||
45 | /** |
||
46 | * Permissions tree for this plugin. |
||
47 | * |
||
48 | * @var null|array |
||
49 | */ |
||
50 | protected $_permissions = null; |
||
51 | |||
52 | /** |
||
53 | * {@inheritDoc} |
||
54 | * |
||
55 | * @return string CamelizedName plugin name |
||
56 | */ |
||
57 | public function name() |
||
61 | |||
62 | /** |
||
63 | * Gets plugin's permissions tree. |
||
64 | * |
||
65 | * ### Output example: |
||
66 | * |
||
67 | * ```php |
||
68 | * [ |
||
69 | * 'administrator' => [ |
||
70 | * 'Plugin/Controller/action', |
||
71 | * 'Plugin/Controller/action2', |
||
72 | * ... |
||
73 | * ], |
||
74 | * 'role-machine-name' => [ |
||
75 | * 'Plugin/Controller/anotherAction', |
||
76 | * 'Plugin/Controller/anotherAction2', |
||
77 | * ], |
||
78 | * ... |
||
79 | * ] |
||
80 | * ``` |
||
81 | * |
||
82 | * @return array Permissions index by role's machine-name |
||
83 | */ |
||
84 | public function permissions() |
||
85 | { |
||
86 | if (is_array($this->_permissions)) { |
||
87 | return $this->_permissions; |
||
88 | } |
||
89 | |||
90 | $out = []; |
||
91 | $acosTable = TableRegistry::get('User.Acos'); |
||
92 | $permissions = $acosTable |
||
93 | ->Permissions |
||
94 | ->find() |
||
95 | ->where(['Acos.plugin' => $this->name]) |
||
96 | ->contain(['Acos', 'Roles']) |
||
97 | ->all(); |
||
98 | |||
99 | View Code Duplication | foreach ($permissions as $permission) { |
|
100 | if (!isset($out[$permission->role->slug])) { |
||
101 | $out[$permission->role->slug] = []; |
||
102 | } |
||
103 | $out[$permission->role->slug][] = implode( |
||
104 | '/', |
||
105 | $acosTable |
||
106 | ->find('path', ['for' => $permission->aco->id]) |
||
107 | ->extract('alias') |
||
108 | ->toArray() |
||
109 | ); |
||
110 | } |
||
111 | |||
112 | $this->_permissions = $out; |
||
113 | |||
114 | return $out; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Magic getter to access properties that exists on info(). |
||
119 | * |
||
120 | * @param string $property Name of the property to access |
||
121 | * @return mixed |
||
122 | */ |
||
123 | public function &__get($property) |
||
127 | |||
128 | /** |
||
129 | * Gets information for this plugin. |
||
130 | * |
||
131 | * When `$full` is set to true some additional keys will be repent in the |
||
132 | * resulting array: |
||
133 | * |
||
134 | * - `settings`: Plugin's settings info fetched from DB. |
||
135 | * - `composer`: Composer JSON information, converted to an array. |
||
136 | * - `permissions`: Permissions tree for this plugin, see `PluginPackage::permissions()` |
||
137 | * |
||
138 | * ### Example: |
||
139 | * |
||
140 | * Reading full information: |
||
141 | * |
||
142 | * ```php |
||
143 | * $plugin->info(); |
||
144 | * |
||
145 | * // returns an array as follow: |
||
146 | * [ |
||
147 | * 'name' => 'User, |
||
148 | * 'isTheme' => false, |
||
149 | * 'hasHelp' => true, |
||
150 | * 'hasSettings' => false, |
||
151 | * 'eventListeners' => [ ... ], |
||
152 | * 'status' => 1, |
||
153 | * 'path' => '/path/to/plugin', |
||
154 | * 'settings' => [ ... ], // only when $full = true |
||
155 | * 'composer' => [ ... ], // only when $full = true |
||
156 | * 'permissions' => [ ... ], // only when $full = true |
||
157 | * ] |
||
158 | * ``` |
||
159 | * |
||
160 | * Additionally the first argument, $key, can be used to get an specific value |
||
161 | * using a dot syntax path: |
||
162 | * |
||
163 | * ```php |
||
164 | * $plugin->info('isTheme'); |
||
165 | * $plugin->info('settings.some_key'); |
||
166 | * ``` |
||
167 | * |
||
168 | * If the given path is not found NULL will be returned |
||
169 | * |
||
170 | * @param string $key Optional path to read from the resulting array |
||
171 | * @return mixed Plugin information as an array if no key is given, or the |
||
172 | * requested value if a valid $key was provided, or NULL if $key path is not |
||
173 | * found |
||
174 | */ |
||
175 | public function &info($key = null) |
||
205 | |||
206 | /** |
||
207 | * Gets info value for the given key. |
||
208 | * |
||
209 | * @param string|array $key The path to read. String using a dot-syntax, or an |
||
210 | * array result of exploding by `.` symbol |
||
211 | * @return mixed |
||
212 | */ |
||
213 | protected function &_getKey($key) |
||
214 | { |
||
215 | $default = null; |
||
216 | $parts = is_string($key) ? explode('.', $key) : $key; |
||
217 | |||
218 | switch (count($parts)) { |
||
219 | case 1: |
||
220 | if (isset($this->_info[$parts[0]])) { |
||
221 | return $this->_info[$parts[0]]; |
||
222 | } |
||
223 | |||
224 | return $default; |
||
225 | case 2: |
||
226 | if (isset($this->_info[$parts[0]][$parts[1]])) { |
||
227 | return $this->_info[$parts[0]][$parts[1]]; |
||
228 | } |
||
229 | |||
230 | return $default; |
||
231 | case 3: |
||
232 | if (isset($this->_info[$parts[0]][$parts[1]][$parts[2]])) { |
||
233 | return $this->_info[$parts[0]][$parts[1]][$parts[2]]; |
||
234 | } |
||
235 | |||
236 | return $default; |
||
237 | case 4: |
||
238 | if (isset($this->_info[$parts[0]][$parts[1]][$parts[2]][$parts[3]])) { |
||
239 | return $this->_info[$parts[0]][$parts[1]][$parts[2]][$parts[3]]; |
||
240 | } |
||
241 | |||
242 | return $default; |
||
243 | default: |
||
244 | $data = $this->_info; |
||
245 | foreach ($parts as $key) { |
||
246 | if (is_array($data) && isset($data[$key])) { |
||
247 | $data = $data[$key]; |
||
248 | } else { |
||
249 | return $default; |
||
250 | } |
||
251 | } |
||
252 | } |
||
253 | |||
254 | return $data; |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * {@inheritDoc} |
||
259 | */ |
||
260 | public function composer($full = false) |
||
261 | { |
||
262 | $composer = parent::composer($full); |
||
263 | View Code Duplication | if ($this->isTheme && !isset($composer['extra']['admin'])) { |
|
264 | $composer['extra']['admin'] = false; |
||
265 | } |
||
266 | View Code Duplication | if ($this->isTheme && !isset($composer['extra']['regions'])) { |
|
267 | $composer['extra']['regions'] = []; |
||
268 | } |
||
269 | |||
270 | return $composer; |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * Gets settings from DB for this plugin. Or reads a single settings key value. |
||
275 | * |
||
276 | * @param string $key Which setting to read, the entire settings will be |
||
277 | * returned if no key is provided |
||
278 | * @return mixed Array of settings if $key was not provided, or the requested |
||
279 | * value for the given $key (null of key does not exists) |
||
280 | */ |
||
281 | public function settings($key = null) |
||
282 | { |
||
283 | $plugin = $this->name(); |
||
284 | if ($cache = $this->config('settings')) { |
||
285 | if ($key !== null) { |
||
286 | $cache = isset($cache[$key]) ? $cache[$key] : null; |
||
287 | } |
||
288 | |||
289 | return $cache; |
||
290 | } |
||
291 | |||
292 | $pluginsTable = TableRegistry::get('System.Plugins'); |
||
293 | $settings = []; |
||
294 | $dbInfo = $pluginsTable->find() |
||
295 | ->cache("{$plugin}_settings", 'plugins') |
||
296 | ->select(['name', 'settings']) |
||
297 | ->where(['name' => $plugin]) |
||
298 | ->limit(1) |
||
299 | ->first(); |
||
300 | |||
301 | if ($dbInfo) { |
||
302 | $settings = (array)$dbInfo->settings; |
||
303 | } |
||
304 | |||
305 | if (empty($settings)) { |
||
306 | $settings = (array)$pluginsTable->trigger("Plugin.{$plugin}.settingsDefaults")->result; |
||
307 | } |
||
308 | |||
309 | $this->config('settings', $settings); |
||
310 | if ($key !== null) { |
||
311 | $settings = isset($settings[$key]) ? $settings[$key] : null; |
||
312 | } |
||
313 | |||
314 | return $settings; |
||
315 | } |
||
316 | |||
317 | /** |
||
318 | * Gets a collection list of plugin that depends on this plugin. |
||
319 | * |
||
320 | * @return \Cake\Collection\Collection List of plugins |
||
321 | */ |
||
322 | public function requiredBy() |
||
323 | { |
||
324 | if ($cache = $this->config('required_by')) { |
||
325 | return collection($cache); |
||
326 | } |
||
327 | |||
328 | Plugin::dropCache(); |
||
329 | $out = []; |
||
330 | $plugins = Plugin::get()->filter(function ($v, $k) { |
||
331 | return $v->name() !== $this->name(); |
||
332 | }); |
||
333 | |||
334 | foreach ($plugins as $plugin) { |
||
335 | if ($dependencies = $plugin->dependencies($plugin->name)) { |
||
336 | $packages = array_map(function ($item) { |
||
337 | list(, $package) = packageSplit($item, true); |
||
338 | |||
339 | return strtolower($package); |
||
340 | }, array_keys($dependencies)); |
||
341 | |||
342 | if (in_array(strtolower($this->name()), $packages)) { |
||
343 | $out[] = $plugin; |
||
344 | } |
||
345 | } |
||
346 | } |
||
347 | |||
348 | $this->config('required_by', $out); |
||
349 | |||
350 | return collection($out); |
||
351 | } |
||
352 | |||
353 | /** |
||
354 | * {@inheritDoc} |
||
355 | * |
||
356 | * It will look for plugin's version in the following places: |
||
357 | * |
||
358 | * - Plugin's "composer.json" file. |
||
359 | * - Plugin's "VERSION.txt" file (or any file matching "/version?(\.\w+)/i"). |
||
360 | * - Composer's "installed.json" file. |
||
361 | * |
||
362 | * If not found `dev-master` is returned by default. If plugin is not registered |
||
363 | * on QuickAppsCMS (not installed) an empty string will be returned instead. |
||
364 | * |
||
365 | * @return string Plugin's version, for instance `1.2.x-dev` |
||
366 | */ |
||
367 | public function version() |
||
368 | { |
||
369 | if (parent::version() !== null) { |
||
370 | return parent::version(); |
||
371 | } |
||
372 | |||
373 | if (!Plugin::exists($this->name())) { |
||
374 | $this->_version = ''; |
||
375 | |||
376 | return $this->_version; |
||
377 | } |
||
378 | |||
379 | // from composer.json |
||
380 | if (!empty($this->composer['version'])) { |
||
381 | $this->_version = $this->composer['version']; |
||
382 | |||
383 | return $this->_version; |
||
384 | } |
||
385 | |||
386 | // from version.txt |
||
387 | $files = glob($this->path . '/*', GLOB_NOSORT); |
||
388 | foreach ($files as $file) { |
||
389 | $fileName = basename(strtolower($file)); |
||
390 | if (preg_match('/version?(\.\w+)/i', $fileName)) { |
||
391 | $versionFile = file($file); |
||
392 | $version = trim(array_pop($versionFile)); |
||
393 | $this->_version = $version; |
||
394 | |||
395 | return $this->_version; |
||
396 | } |
||
397 | } |
||
398 | |||
399 | // from installed.json |
||
400 | $installedJson = normalizePath(VENDOR_INCLUDE_PATH . "composer/installed.json"); |
||
401 | if (is_readable($installedJson)) { |
||
402 | $json = (array)json_decode(file_get_contents($installedJson), true); |
||
403 | foreach ($json as $pkg) { |
||
404 | if (isset($pkg['version']) && |
||
405 | strtolower($pkg['name']) === strtolower($this->_packageName) |
||
406 | ) { |
||
407 | $this->_version = $pkg['version']; |
||
408 | |||
409 | return $this->_version; |
||
410 | } |
||
411 | } |
||
412 | } |
||
413 | |||
414 | $this->_version = 'dev-master'; |
||
415 | |||
416 | return $this->_version; |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * Returns an array that can be used to describe the internal state of this |
||
421 | * object. |
||
422 | * |
||
423 | * @return array |
||
424 | */ |
||
425 | public function __debugInfo() |
||
429 | } |
||
430 |