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 Encoder 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 Encoder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class Encoder |
||
12 | { |
||
13 | /** |
||
14 | * Takes an xmlrpc value in object format and translates it into native PHP types. |
||
15 | * |
||
16 | * Works with xmlrpc requests objects as input, too. |
||
17 | * |
||
18 | * Given proper options parameter, can rebuild generic php object instances |
||
19 | * (provided those have been encoded to xmlrpc format using a corresponding |
||
20 | * option in php_xmlrpc_encode()) |
||
21 | * PLEASE NOTE that rebuilding php objects involves calling their constructor function. |
||
22 | * This means that the remote communication end can decide which php code will |
||
23 | * get executed on your server, leaving the door possibly open to 'php-injection' |
||
24 | * style of attacks (provided you have some classes defined on your server that |
||
25 | * might wreak havoc if instances are built outside an appropriate context). |
||
26 | * Make sure you trust the remote server/client before eanbling this! |
||
27 | * |
||
28 | * @author Dan Libby ([email protected]) |
||
29 | * |
||
30 | * @param Value|Request $xmlrpcVal |
||
31 | * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects (standard is |
||
32 | * |
||
33 | * @return mixed |
||
34 | */ |
||
35 | public function decode($xmlrpcVal, $options = array()) |
||
36 | { |
||
37 | switch ($xmlrpcVal->kindOf()) { |
||
38 | case 'scalar': |
||
39 | if (in_array('extension_api', $options)) { |
||
40 | $val = reset($xmlrpcVal->me); |
||
41 | $typ = key($xmlrpcVal->me); |
||
42 | switch ($typ) { |
||
43 | case 'dateTime.iso8601': |
||
44 | $xmlrpcVal->scalar = $val; |
||
45 | $xmlrpcVal->type = 'datetime'; |
||
46 | $xmlrpcVal->timestamp = \PhpXmlRpc\Helper\Date::iso8601Decode($val); |
||
47 | |||
48 | return $xmlrpcVal; |
||
49 | case 'base64': |
||
50 | $xmlrpcVal->scalar = $val; |
||
51 | $xmlrpcVal->type = $typ; |
||
52 | |||
53 | return $xmlrpcVal; |
||
54 | default: |
||
55 | return $xmlrpcVal->scalarval(); |
||
|
|||
56 | } |
||
57 | } |
||
58 | if (in_array('dates_as_objects', $options) && $xmlrpcVal->scalartyp() == 'dateTime.iso8601') { |
||
59 | // we return a Datetime object instead of a string |
||
60 | // since now the constructor of xmlrpc value accepts safely strings, ints and datetimes, |
||
61 | // we cater to all 3 cases here |
||
62 | $out = $xmlrpcVal->scalarval(); |
||
63 | if (is_string($out)) { |
||
64 | $out = strtotime($out); |
||
65 | } |
||
66 | if (is_int($out)) { |
||
67 | $result = new \DateTime(); |
||
68 | $result->setTimestamp($out); |
||
69 | |||
70 | return $result; |
||
71 | } elseif (is_a($out, 'DateTimeInterface')) { |
||
72 | return $out; |
||
73 | } |
||
74 | } |
||
75 | |||
76 | return $xmlrpcVal->scalarval(); |
||
77 | case 'array': |
||
78 | $arr = array(); |
||
79 | foreach($xmlrpcVal as $value) { |
||
80 | $arr[] = $this->decode($value, $options); |
||
81 | } |
||
82 | |||
83 | return $arr; |
||
84 | case 'struct': |
||
85 | // If user said so, try to rebuild php objects for specific struct vals. |
||
86 | /// @todo should we raise a warning for class not found? |
||
87 | // shall we check for proper subclass of xmlrpc value instead of |
||
88 | // presence of _php_class to detect what we can do? |
||
89 | if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != '' |
||
90 | && class_exists($xmlrpcVal->_php_class) |
||
91 | ) { |
||
92 | $obj = @new $xmlrpcVal->_php_class(); |
||
93 | foreach ($xmlrpcVal as $key => $value) { |
||
94 | $obj->$key = $this->decode($value, $options); |
||
95 | } |
||
96 | |||
97 | return $obj; |
||
98 | } else { |
||
99 | $arr = array(); |
||
100 | foreach ($xmlrpcVal as $key => $value) { |
||
101 | $arr[$key] = $this->decode($value, $options); |
||
102 | } |
||
103 | |||
104 | return $arr; |
||
105 | } |
||
106 | case 'msg': |
||
107 | $paramCount = $xmlrpcVal->getNumParams(); |
||
108 | $arr = array(); |
||
109 | for ($i = 0; $i < $paramCount; $i++) { |
||
110 | $arr[] = $this->decode($xmlrpcVal->getParam($i), $options); |
||
111 | } |
||
112 | |||
113 | return $arr; |
||
114 | } |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Takes native php types and encodes them into xmlrpc PHP object format. |
||
119 | * It will not re-encode xmlrpc value objects. |
||
120 | * |
||
121 | * Feature creep -- could support more types via optional type argument |
||
122 | * (string => datetime support has been added, ??? => base64 not yet) |
||
123 | * |
||
124 | * If given a proper options parameter, php object instances will be encoded |
||
125 | * into 'special' xmlrpc values, that can later be decoded into php objects |
||
126 | * by calling php_xmlrpc_decode() with a corresponding option |
||
127 | * |
||
128 | * @author Dan Libby ([email protected]) |
||
129 | * |
||
130 | * @param mixed $phpVal the value to be converted into an xmlrpc value object |
||
131 | * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api' |
||
132 | * |
||
133 | * @return \PhpXmlrpc\Value |
||
134 | */ |
||
135 | 99 | public function encode($phpVal, $options = array()) |
|
136 | { |
||
137 | 99 | $type = gettype($phpVal); |
|
138 | switch ($type) { |
||
139 | 99 | case 'string': |
|
140 | 98 | if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $phpVal)) { |
|
141 | 1 | $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime); |
|
142 | 1 | } else { |
|
143 | 98 | $xmlrpcVal = new Value($phpVal, Value::$xmlrpcString); |
|
144 | } |
||
145 | 98 | break; |
|
146 | 99 | case 'integer': |
|
147 | 97 | $xmlrpcVal = new Value($phpVal, Value::$xmlrpcInt); |
|
148 | 97 | break; |
|
149 | 23 | case 'double': |
|
150 | 1 | $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble); |
|
151 | 1 | break; |
|
152 | // <G_Giunta_2001-02-29> |
||
153 | // Add support for encoding/decoding of booleans, since they are supported in PHP |
||
154 | 23 | case 'boolean': |
|
155 | 1 | $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean); |
|
156 | 1 | break; |
|
157 | // </G_Giunta_2001-02-29> |
||
158 | 23 | case 'array': |
|
159 | // PHP arrays can be encoded to either xmlrpc structs or arrays, |
||
160 | // depending on whether they are hashes or plain 0..n integer indexed |
||
161 | // A shorter one-liner would be |
||
162 | // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1)); |
||
163 | // but execution time skyrockets! |
||
164 | 22 | $j = 0; |
|
165 | 22 | $arr = array(); |
|
166 | 22 | $ko = false; |
|
167 | 22 | foreach ($phpVal as $key => $val) { |
|
168 | 22 | $arr[$key] = $this->encode($val, $options); |
|
169 | 22 | if (!$ko && $key !== $j) { |
|
170 | 21 | $ko = true; |
|
171 | 21 | } |
|
172 | 22 | $j++; |
|
173 | 22 | } |
|
174 | 22 | if ($ko) { |
|
175 | 21 | $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct); |
|
176 | 21 | } else { |
|
177 | 2 | $xmlrpcVal = new Value($arr, Value::$xmlrpcArray); |
|
178 | } |
||
179 | 22 | break; |
|
180 | 1 | case 'object': |
|
181 | 1 | if (is_a($phpVal, 'PhpXmlRpc\Value')) { |
|
182 | 1 | $xmlrpcVal = $phpVal; |
|
183 | 1 | } elseif (is_a($phpVal, 'DateTimeInterface')) { |
|
184 | $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcStruct); |
||
185 | } else { |
||
186 | $arr = array(); |
||
187 | foreach($phpVal as $k => $v) { |
||
188 | $arr[$k] = $this->encode($v, $options); |
||
189 | } |
||
190 | $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct); |
||
191 | if (in_array('encode_php_objs', $options)) { |
||
192 | // let's save original class name into xmlrpc value: |
||
193 | // might be useful later on... |
||
194 | $xmlrpcVal->_php_class = get_class($phpVal); |
||
195 | } |
||
196 | } |
||
197 | 1 | break; |
|
198 | case 'NULL': |
||
199 | if (in_array('extension_api', $options)) { |
||
200 | $xmlrpcVal = new Value('', Value::$xmlrpcString); |
||
201 | } elseif (in_array('null_extension', $options)) { |
||
202 | $xmlrpcVal = new Value('', Value::$xmlrpcNull); |
||
203 | } else { |
||
204 | $xmlrpcVal = new Value(); |
||
205 | } |
||
206 | break; |
||
207 | case 'resource': |
||
208 | if (in_array('extension_api', $options)) { |
||
209 | $xmlrpcVal = new Value((int)$phpVal, Value::$xmlrpcInt); |
||
210 | } else { |
||
211 | $xmlrpcVal = new Value(); |
||
212 | } |
||
213 | break; |
||
214 | // catch "user function", "unknown type" |
||
215 | default: |
||
216 | // giancarlo pinerolo <[email protected]> |
||
217 | // it has to return |
||
218 | // an empty object in case, not a boolean. |
||
219 | $xmlrpcVal = new Value(); |
||
220 | break; |
||
221 | } |
||
222 | |||
223 | 99 | return $xmlrpcVal; |
|
224 | } |
||
225 | |||
226 | /** |
||
227 | * Convert the xml representation of a method response, method request or single |
||
228 | * xmlrpc value into the appropriate object (a.k.a. deserialize). |
||
229 | * |
||
230 | * @param string $xmlVal |
||
231 | * @param array $options |
||
232 | * |
||
233 | * @return mixed false on error, or an instance of either Value, Request or Response |
||
234 | */ |
||
235 | 3 | public function decodeXml($xmlVal, $options = array()) |
|
319 | |||
320 | } |
||
321 |
It seems like the method you are trying to call exists only in some of the possible types.
Let’s take a look at an example:
Available Fixes
Add an additional type-check:
Only allow a single type to be passed if the variable comes from a parameter: