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