Total Complexity | 42 |
Total Lines | 309 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like CustomMethods 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 CustomMethods, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | trait CustomMethods |
||
13 | { |
||
14 | |||
15 | /** |
||
16 | * Custom method sources |
||
17 | * |
||
18 | * @var array |
||
19 | */ |
||
20 | protected static $extra_methods = []; |
||
21 | |||
22 | /** |
||
23 | * Name of methods to invoke by defineMethods for this instance |
||
24 | * |
||
25 | * @var array |
||
26 | */ |
||
27 | protected $extra_method_registers = array(); |
||
28 | |||
29 | /** |
||
30 | * Non-custom methods |
||
31 | * |
||
32 | * @var array |
||
33 | */ |
||
34 | protected static $built_in_methods = array(); |
||
35 | |||
36 | /** |
||
37 | * Attempts to locate and call a method dynamically added to a class at runtime if a default cannot be located |
||
38 | * |
||
39 | * You can add extra methods to a class using {@link Extensions}, {@link Object::createMethod()} or |
||
40 | * {@link Object::addWrapperMethod()} |
||
41 | * |
||
42 | * @param string $method |
||
43 | * @param array $arguments |
||
44 | * @return mixed |
||
45 | * @throws BadMethodCallException |
||
46 | */ |
||
47 | public function __call($method, $arguments) |
||
48 | { |
||
49 | // If the method cache was cleared by an an Object::add_extension() / Object::remove_extension() |
||
50 | // call, then we should rebuild it. |
||
51 | $class = static::class; |
||
52 | $config = $this->getExtraMethodConfig($method); |
||
53 | if (empty($config)) { |
||
54 | throw new BadMethodCallException( |
||
55 | "Object->__call(): the method '$method' does not exist on '$class'" |
||
56 | ); |
||
57 | } |
||
58 | |||
59 | switch (true) { |
||
60 | case isset($config['callback']): { |
||
|
|||
61 | return $config['callback']($this, $arguments); |
||
62 | } |
||
63 | case isset($config['property']) : { |
||
64 | $property = $config['property']; |
||
65 | $index = $config['index']; |
||
66 | $obj = $index !== null ? |
||
67 | $this->{$property}[$index] : |
||
68 | $this->{$property}; |
||
69 | |||
70 | if (!$obj) { |
||
71 | throw new BadMethodCallException( |
||
72 | "Object->__call(): {$class} cannot pass control to {$property}({$index})." |
||
73 | . ' Perhaps this object was mistakenly destroyed?' |
||
74 | ); |
||
75 | } |
||
76 | |||
77 | // Call without setOwner |
||
78 | if (empty($config['callSetOwnerFirst'])) { |
||
79 | return $obj->$method(...$arguments); |
||
80 | } |
||
81 | |||
82 | /** @var Extension $obj */ |
||
83 | try { |
||
84 | $obj->setOwner($this); |
||
85 | return $obj->$method(...$arguments); |
||
86 | } finally { |
||
87 | $obj->clearOwner(); |
||
88 | } |
||
89 | } |
||
90 | case isset($config['wrap']): { |
||
91 | array_unshift($arguments, $config['method']); |
||
92 | $wrapped = $config['wrap']; |
||
93 | return $this->$wrapped(...$arguments); |
||
94 | } |
||
95 | case isset($config['function']): { |
||
96 | return $config['function']($this, $arguments); |
||
97 | } |
||
98 | default: { |
||
99 | throw new BadMethodCallException( |
||
100 | "Object->__call(): extra method $method is invalid on $class:" |
||
101 | . var_export($config, true) |
||
102 | ); |
||
103 | } |
||
104 | } |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Adds any methods from {@link Extension} instances attached to this object. |
||
109 | * All these methods can then be called directly on the instance (transparently |
||
110 | * mapped through {@link __call()}), or called explicitly through {@link extend()}. |
||
111 | * |
||
112 | * @uses addMethodsFrom() |
||
113 | */ |
||
114 | protected function defineMethods() |
||
115 | { |
||
116 | // Define from all registered callbacks |
||
117 | foreach ($this->extra_method_registers as $callback) { |
||
118 | call_user_func($callback); |
||
119 | } |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Register an callback to invoke that defines extra methods |
||
124 | * |
||
125 | * @param string $name |
||
126 | * @param callable $callback |
||
127 | */ |
||
128 | protected function registerExtraMethodCallback($name, $callback) |
||
132 | } |
||
133 | } |
||
134 | |||
135 | // -------------------------------------------------------------------------------------------------------------- |
||
136 | |||
137 | /** |
||
138 | * Return TRUE if a method exists on this object |
||
139 | * |
||
140 | * This should be used rather than PHP's inbuild method_exists() as it takes into account methods added via |
||
141 | * extensions |
||
142 | * |
||
143 | * @param string $method |
||
144 | * @return bool |
||
145 | */ |
||
146 | public function hasMethod($method) |
||
147 | { |
||
148 | return method_exists($this, $method) || $this->getExtraMethodConfig($method); |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Get meta-data details on a named method |
||
153 | * |
||
154 | * @param string $method |
||
155 | * @return array List of custom method details, if defined for this method |
||
156 | */ |
||
157 | protected function getExtraMethodConfig($method) |
||
158 | { |
||
159 | // Lazy define methods |
||
160 | if (!isset(self::$extra_methods[static::class])) { |
||
161 | $this->defineMethods(); |
||
162 | } |
||
163 | |||
164 | if (isset(self::$extra_methods[static::class][strtolower($method)])) { |
||
165 | return self::$extra_methods[static::class][strtolower($method)]; |
||
166 | } |
||
167 | return null; |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Return the names of all the methods available on this object |
||
172 | * |
||
173 | * @param bool $custom include methods added dynamically at runtime |
||
174 | * @return array |
||
175 | */ |
||
176 | public function allMethodNames($custom = false) |
||
177 | { |
||
178 | $class = static::class; |
||
179 | if (!isset(self::$built_in_methods[$class])) { |
||
180 | self::$built_in_methods[$class] = array_map('strtolower', get_class_methods($this)); |
||
181 | } |
||
182 | |||
183 | if ($custom && isset(self::$extra_methods[$class])) { |
||
184 | return array_merge(self::$built_in_methods[$class], array_keys(self::$extra_methods[$class])); |
||
185 | } else { |
||
186 | return self::$built_in_methods[$class]; |
||
187 | } |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * @param object $extension |
||
192 | * @return array |
||
193 | */ |
||
194 | protected function findMethodsFromExtension($extension) |
||
195 | { |
||
196 | if (method_exists($extension, 'allMethodNames')) { |
||
197 | if ($extension instanceof Extension) { |
||
198 | try { |
||
199 | $extension->setOwner($this); |
||
200 | $methods = $extension->allMethodNames(true); |
||
201 | } finally { |
||
202 | $extension->clearOwner(); |
||
203 | } |
||
204 | } else { |
||
205 | $methods = $extension->allMethodNames(true); |
||
206 | } |
||
207 | } else { |
||
208 | $class = get_class($extension); |
||
209 | if (!isset(self::$built_in_methods[$class])) { |
||
210 | self::$built_in_methods[$class] = array_map('strtolower', get_class_methods($extension)); |
||
211 | } |
||
212 | $methods = self::$built_in_methods[$class]; |
||
213 | } |
||
214 | |||
215 | return $methods; |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * Add all the methods from an object property (which is an {@link Extension}) to this object. |
||
220 | * |
||
221 | * @param string $property the property name |
||
222 | * @param string|int $index an index to use if the property is an array |
||
223 | * @throws InvalidArgumentException |
||
224 | */ |
||
225 | protected function addMethodsFrom($property, $index = null) |
||
226 | { |
||
227 | $class = static::class; |
||
228 | $extension = ($index !== null) ? $this->{$property}[$index] : $this->$property; |
||
229 | |||
230 | if (!$extension) { |
||
231 | throw new InvalidArgumentException( |
||
232 | "Object->addMethodsFrom(): could not add methods from {$class}->{$property}[$index]" |
||
233 | ); |
||
234 | } |
||
235 | |||
236 | $methods = $this->findMethodsFromExtension($extension); |
||
237 | if ($methods) { |
||
238 | if ($extension instanceof Extension) { |
||
239 | Deprecation::notice( |
||
240 | '5.0', |
||
241 | 'Register custom methods from extensions with addCallbackMethod.' |
||
242 | . ' callSetOwnerFirst will be removed in 5.0' |
||
243 | ); |
||
244 | } |
||
245 | $methodInfo = array( |
||
246 | 'property' => $property, |
||
247 | 'index' => $index, |
||
248 | 'callSetOwnerFirst' => $extension instanceof Extension, |
||
249 | ); |
||
250 | |||
251 | $newMethods = array_fill_keys($methods, $methodInfo); |
||
252 | |||
253 | if (isset(self::$extra_methods[$class])) { |
||
254 | self::$extra_methods[$class] = |
||
255 | array_merge(self::$extra_methods[$class], $newMethods); |
||
256 | } else { |
||
257 | self::$extra_methods[$class] = $newMethods; |
||
258 | } |
||
259 | } |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Add all the methods from an object property (which is an {@link Extension}) to this object. |
||
264 | * |
||
265 | * @param string $property the property name |
||
266 | * @param string|int $index an index to use if the property is an array |
||
267 | */ |
||
268 | protected function removeMethodsFrom($property, $index = null) |
||
269 | { |
||
270 | $extension = ($index !== null) ? $this->{$property}[$index] : $this->$property; |
||
271 | $class = static::class; |
||
272 | |||
273 | if (!$extension) { |
||
274 | throw new InvalidArgumentException( |
||
275 | "Object->removeMethodsFrom(): could not remove methods from {$class}->{$property}[$index]" |
||
276 | ); |
||
277 | } |
||
278 | |||
279 | $methods = $this->findMethodsFromExtension($extension); |
||
280 | if ($methods) { |
||
281 | foreach ($methods as $method) { |
||
282 | $methodInfo = self::$extra_methods[$class][$method]; |
||
283 | |||
284 | if ($methodInfo['property'] === $property && $methodInfo['index'] === $index) { |
||
285 | unset(self::$extra_methods[$class][$method]); |
||
286 | } |
||
287 | } |
||
288 | |||
289 | if (empty(self::$extra_methods[$class])) { |
||
290 | unset(self::$extra_methods[$class]); |
||
291 | } |
||
292 | } |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Add a wrapper method - a method which points to another method with a different name. For example, Thumbnail(x) |
||
297 | * can be wrapped to generateThumbnail(x) |
||
298 | * |
||
299 | * @param string $method the method name to wrap |
||
300 | * @param string $wrap the method name to wrap to |
||
301 | */ |
||
302 | protected function addWrapperMethod($method, $wrap) |
||
303 | { |
||
304 | self::$extra_methods[static::class][strtolower($method)] = array( |
||
305 | 'wrap' => $wrap, |
||
306 | 'method' => $method |
||
307 | ); |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Add callback as a method. |
||
312 | * |
||
313 | * @param string $method Name of method |
||
314 | * @param callable $callback Callback to invoke. |
||
315 | * Note: $this is passed as first parameter to this callback and then $args as array |
||
316 | */ |
||
317 | protected function addCallbackMethod($method, $callback) |
||
321 | ]; |
||
322 | } |
||
323 | } |
||
324 |
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.