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 AMQPAbstractCollection 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 AMQPAbstractCollection, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 11 | abstract class AMQPAbstractCollection implements \Iterator |
||
| 12 | { |
||
| 13 | |||
| 14 | //protocol defines available field types and their corresponding symbols |
||
| 15 | const PROTOCOL_080 = AbstractChannel::PROTOCOL_080; |
||
| 16 | const PROTOCOL_091 = AbstractChannel::PROTOCOL_091; |
||
| 17 | const PROTOCOL_RBT = 'rabbit'; //pseudo proto |
||
| 18 | |||
| 19 | //Abstract data types |
||
| 20 | const T_INT_SHORTSHORT = 1; |
||
| 21 | const T_INT_SHORTSHORT_U = 2; |
||
| 22 | const T_INT_SHORT = 3; |
||
| 23 | const T_INT_SHORT_U = 4; |
||
| 24 | const T_INT_LONG = 5; |
||
| 25 | const T_INT_LONG_U = 6; |
||
| 26 | const T_INT_LONGLONG = 7; |
||
| 27 | const T_INT_LONGLONG_U = 8; |
||
| 28 | |||
| 29 | const T_DECIMAL = 9; |
||
| 30 | const T_TIMESTAMP = 10; |
||
| 31 | const T_VOID = 11; |
||
| 32 | |||
| 33 | const T_BOOL = 12; |
||
| 34 | |||
| 35 | const T_STRING_SHORT = 13; |
||
| 36 | const T_STRING_LONG = 14; |
||
| 37 | |||
| 38 | const T_ARRAY = 15; |
||
| 39 | const T_TABLE = 16; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * @var string |
||
| 43 | */ |
||
| 44 | private static $_protocol = null; |
||
| 45 | |||
| 46 | /* |
||
| 47 | * Field types messy mess http://www.rabbitmq.com/amqp-0-9-1-errata.html#section_3 |
||
| 48 | * Default behaviour is to use rabbitMQ compatible field-set |
||
| 49 | * Define AMQP_STRICT_FLD_TYPES=true to use strict AMQP instead |
||
| 50 | */ |
||
| 51 | private static $_types_080 = array( |
||
| 52 | self::T_INT_LONG => 'I', |
||
| 53 | self::T_DECIMAL => 'D', |
||
| 54 | self::T_TIMESTAMP => 'T', |
||
| 55 | self::T_STRING_LONG => 'S', |
||
| 56 | self::T_TABLE => 'F' |
||
| 57 | ); |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @var array |
||
| 61 | */ |
||
| 62 | private static $_types_091 = array( |
||
| 63 | self::T_INT_SHORTSHORT => 'b', |
||
| 64 | self::T_INT_SHORTSHORT_U => 'B', |
||
| 65 | self::T_INT_SHORT => 'U', |
||
| 66 | self::T_INT_SHORT_U => 'u', |
||
| 67 | self::T_INT_LONG => 'I', |
||
| 68 | self::T_INT_LONG_U => 'i', |
||
| 69 | self::T_INT_LONGLONG => 'L', |
||
| 70 | self::T_INT_LONGLONG_U => 'l', |
||
| 71 | self::T_DECIMAL => 'D', |
||
| 72 | self::T_TIMESTAMP => 'T', |
||
| 73 | self::T_VOID => 'V', |
||
| 74 | self::T_BOOL => 't', |
||
| 75 | self::T_STRING_SHORT => 's', |
||
| 76 | self::T_STRING_LONG => 'S', |
||
| 77 | self::T_ARRAY => 'A', |
||
| 78 | self::T_TABLE => 'F' |
||
| 79 | ); |
||
| 80 | |||
| 81 | /** |
||
| 82 | * @var array |
||
| 83 | */ |
||
| 84 | private static $_types_rabbit = array( |
||
| 85 | self::T_INT_SHORTSHORT => 'b', |
||
| 86 | self::T_INT_SHORT => 's', |
||
| 87 | self::T_INT_LONG => 'I', |
||
| 88 | self::T_INT_LONGLONG => 'l', |
||
| 89 | self::T_DECIMAL => 'D', |
||
| 90 | self::T_TIMESTAMP => 'T', |
||
| 91 | self::T_VOID => 'V', |
||
| 92 | self::T_BOOL => 't', |
||
| 93 | self::T_STRING_LONG => 'S', |
||
| 94 | self::T_ARRAY => 'A', |
||
| 95 | self::T_TABLE => 'F' |
||
| 96 | ); |
||
| 97 | |||
| 98 | /** |
||
| 99 | * @var array |
||
| 100 | */ |
||
| 101 | protected $data = array(); |
||
| 102 | |||
| 103 | 190 | public function __construct(array $data = null) |
|
| 104 | { |
||
| 105 | 190 | if (!empty($data)) { |
|
| 106 | 110 | $this->data = $this->encodeCollection($data); |
|
| 107 | 84 | } |
|
| 108 | 185 | } |
|
| 109 | |||
| 110 | /** |
||
| 111 | * @return int |
||
| 112 | */ |
||
| 113 | abstract public function getType(); |
||
| 114 | |||
| 115 | /** |
||
| 116 | * @param mixed $val |
||
| 117 | * @param int $type |
||
| 118 | * @param string $key |
||
| 119 | */ |
||
| 120 | 105 | final protected function setValue($val, $type = null, $key = null) |
|
| 121 | { |
||
| 122 | 105 | if ($val instanceof self) { |
|
| 123 | 35 | if ($type && ($type != $val->getType())) { |
|
|
|
|||
| 124 | 5 | throw new Exception\AMQPInvalidArgumentException( |
|
| 125 | 5 | 'Attempted to add instance of ' . get_class($val) . ' representing type [' . $val->getType() . '] as mismatching type [' . $type . ']' |
|
| 126 | 4 | ); |
|
| 127 | } |
||
| 128 | 30 | $type = $val->getType(); |
|
| 129 | 99 | } elseif ($type) { //ensuring data integrity and that all members are properly validated |
|
| 130 | switch ($type) { |
||
| 131 | 90 | case self::T_ARRAY: |
|
| 132 | 5 | throw new Exception\AMQPInvalidArgumentException('Arrays must be passed as AMQPArray instance'); |
|
| 133 | break; |
||
| 134 | 85 | case self::T_TABLE: |
|
| 135 | 5 | throw new Exception\AMQPInvalidArgumentException('Tables must be passed as AMQPTable instance'); |
|
| 136 | break; |
||
| 137 | 80 | case self::T_DECIMAL: |
|
| 138 | 5 | if (!($val instanceof AMQPDecimal)) { |
|
| 139 | 5 | throw new Exception\AMQPInvalidArgumentException('Decimal values must be instance of AMQPDecimal'); |
|
| 140 | } |
||
| 141 | break; |
||
| 142 | } |
||
| 143 | 60 | } |
|
| 144 | |||
| 145 | 85 | if ($type) { |
|
| 146 | 80 | self::checkDataTypeIsSupported($type, false); |
|
| 147 | 65 | $val = array($type, $val); |
|
| 148 | 52 | } else { |
|
| 149 | 5 | $val = $this->encodeValue($val); |
|
| 150 | } |
||
| 151 | |||
| 152 | 70 | if ($key === null) { |
|
| 153 | 25 | $this->data[] = $val; |
|
| 154 | 20 | } else { |
|
| 155 | 60 | $this->data[$key] = $val; |
|
| 156 | } |
||
| 157 | 70 | } |
|
| 158 | |||
| 159 | /** |
||
| 160 | * @return array |
||
| 161 | */ |
||
| 162 | 80 | final public function getNativeData() |
|
| 166 | |||
| 167 | /** |
||
| 168 | * @param array $val |
||
| 169 | * @return array |
||
| 170 | */ |
||
| 171 | 110 | final protected function encodeCollection(array $val) |
|
| 172 | { |
||
| 173 | 110 | foreach ($val as $k=>$v) { |
|
| 174 | 110 | $val[$k] = $this->encodeValue($v); |
|
| 175 | 84 | } |
|
| 176 | |||
| 177 | 105 | return $val; |
|
| 178 | } |
||
| 179 | |||
| 180 | /** |
||
| 181 | * @param array $val |
||
| 182 | * @return array |
||
| 183 | */ |
||
| 184 | 80 | final protected function decodeCollection(array $val) |
|
| 185 | { |
||
| 186 | 80 | foreach ($val as $k=>$v) { |
|
| 187 | 80 | $val[$k] = $this->decodeValue($v[1], $v[0]); |
|
| 188 | 64 | } |
|
| 189 | |||
| 190 | 80 | return $val; |
|
| 191 | } |
||
| 192 | |||
| 193 | /** |
||
| 194 | * @param mixed $val |
||
| 195 | * @return mixed |
||
| 196 | * @throws Exception\AMQPOutOfBoundsException |
||
| 197 | */ |
||
| 198 | 115 | protected function encodeValue($val) |
|
| 199 | { |
||
| 200 | 115 | if (is_string($val)) { |
|
| 201 | 105 | $val = $this->encodeString($val); |
|
| 202 | 110 | } elseif (is_float($val)) { |
|
| 203 | 40 | $val = $this->encodeFloat($val); |
|
| 204 | 90 | } elseif (is_int($val)) { |
|
| 205 | 75 | $val = $this->encodeInt($val); |
|
| 206 | 90 | } elseif (is_bool($val)) { |
|
| 207 | 75 | $val = $this->encodeBool($val); |
|
| 208 | 88 | } elseif (is_null($val)) { |
|
| 209 | 35 | $val = $this->encodeVoid(); |
|
| 210 | 64 | } elseif ($val instanceof \DateTime) { |
|
| 211 | $val = array(self::T_TIMESTAMP, $val->getTimestamp()); |
||
| 212 | 61 | } elseif ($val instanceof AMQPDecimal) { |
|
| 213 | $val = array(self::T_DECIMAL, $val); |
||
| 214 | 75 | } elseif ($val instanceof self) { |
|
| 215 | //avoid silent type correction of strictly typed values |
||
| 216 | self::checkDataTypeIsSupported($val->getType(), false); |
||
| 217 | $val = array($val->getType(), $val); |
||
| 218 | 75 | } elseif (is_array($val)) { |
|
| 219 | //AMQP specs says "Field names MUST start with a letter, '$' or '#'" |
||
| 220 | //so beware, some servers may raise an exception with 503 code in cases when indexed array is encoded as table |
||
| 221 | 70 | if (self::isProtocol(self::PROTOCOL_080)) { |
|
| 222 | //080 doesn't support arrays, forcing table |
||
| 223 | 15 | $val = array(self::T_TABLE, new AMQPTable($val)); |
|
| 224 | 67 | } elseif (empty($val) || (array_keys($val) === range(0, count($val) - 1))) { |
|
| 225 | 50 | $val = array(self::T_ARRAY, new AMQPArray($val)); |
|
| 226 | 40 | } else { |
|
| 227 | 50 | $val = array(self::T_TABLE, new AMQPTable($val)); |
|
| 228 | } |
||
| 229 | 56 | } else { |
|
| 230 | 5 | throw new Exception\AMQPOutOfBoundsException(sprintf('Encountered value of unsupported type: %s', gettype($val))); |
|
| 231 | } |
||
| 232 | |||
| 233 | 110 | return $val; |
|
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * @param mixed $val |
||
| 238 | * @param integer $type |
||
| 239 | * @return array|bool|\DateTime|null |
||
| 240 | */ |
||
| 241 | 80 | protected function decodeValue($val, $type) |
|
| 242 | { |
||
| 243 | 80 | if ($val instanceof self) { |
|
| 244 | //covering arrays and tables |
||
| 245 | 50 | $val = $val->getNativeData(); |
|
| 246 | 40 | } else { |
|
| 247 | switch ($type) { |
||
| 248 | 75 | case self::T_BOOL: |
|
| 249 | 30 | $val = (bool) $val; |
|
| 250 | 30 | break; |
|
| 251 | 75 | case self::T_TIMESTAMP: |
|
| 252 | $val = \DateTime::createFromFormat('U', $val); |
||
| 253 | break; |
||
| 254 | 75 | case self::T_VOID: |
|
| 255 | 25 | $val = null; |
|
| 256 | 25 | break; |
|
| 257 | 70 | case self::T_ARRAY: |
|
| 258 | 70 | case self::T_TABLE: |
|
| 259 | throw new Exception\AMQPLogicException( |
||
| 260 | 'Encountered an array/table struct which is not an instance of AMQPCollection. ' . |
||
| 261 | 'This is considered a bug and should be fixed, please report' |
||
| 262 | ); |
||
| 263 | } |
||
| 264 | } |
||
| 265 | |||
| 266 | 80 | return $val; |
|
| 267 | } |
||
| 268 | |||
| 269 | /** |
||
| 270 | * @param string $val |
||
| 271 | * @return array |
||
| 272 | */ |
||
| 273 | 105 | protected function encodeString($val) |
|
| 274 | { |
||
| 275 | 105 | return array(self::T_STRING_LONG, $val); |
|
| 276 | } |
||
| 277 | |||
| 278 | /** |
||
| 279 | * @param int $val |
||
| 280 | * @return array |
||
| 281 | */ |
||
| 282 | 75 | protected function encodeInt($val) |
|
| 283 | { |
||
| 284 | 75 | if (($val >= -2147483648) && ($val <= 2147483647)) { |
|
| 285 | 75 | $ev = array(self::T_INT_LONG, $val); |
|
| 286 | 71 | } elseif (self::isProtocol(self::PROTOCOL_080)) { |
|
| 287 | //080 doesn't support longlong |
||
| 288 | 15 | $ev = $this->encodeString((string) $val); |
|
| 289 | 12 | } else { |
|
| 290 | 40 | $ev = array(self::T_INT_LONGLONG, $val); |
|
| 291 | } |
||
| 292 | |||
| 293 | 75 | return $ev; |
|
| 294 | } |
||
| 295 | |||
| 296 | /** |
||
| 297 | * @param float $val |
||
| 298 | * @return array |
||
| 299 | */ |
||
| 300 | 40 | protected function encodeFloat($val) |
|
| 301 | { |
||
| 302 | 40 | return static::encodeString((string) $val); |
|
| 303 | } |
||
| 304 | |||
| 305 | /** |
||
| 306 | * @param bool $val |
||
| 307 | * @return array |
||
| 308 | */ |
||
| 309 | 75 | protected function encodeBool($val) |
|
| 310 | { |
||
| 311 | 75 | $val = (bool) $val; |
|
| 312 | |||
| 313 | 75 | return self::isProtocol(self::PROTOCOL_080) ? array(self::T_INT_LONG, (int) $val) : array(self::T_BOOL, $val); |
|
| 314 | } |
||
| 315 | |||
| 316 | /** |
||
| 317 | * @return array |
||
| 318 | */ |
||
| 319 | 35 | protected function encodeVoid() |
|
| 320 | { |
||
| 321 | 35 | return self::isProtocol(self::PROTOCOL_080) ? $this->encodeString('') : array(self::T_VOID, null); |
|
| 322 | } |
||
| 323 | |||
| 324 | /** |
||
| 325 | * @return string |
||
| 326 | */ |
||
| 327 | 215 | final public static function getProtocol() |
|
| 328 | { |
||
| 329 | 215 | if (self::$_protocol === null) { |
|
| 330 | 5 | self::$_protocol = defined('AMQP_STRICT_FLD_TYPES') && AMQP_STRICT_FLD_TYPES ? |
|
| 331 | 4 | AbstractChannel::getProtocolVersion() : |
|
| 332 | 5 | self::PROTOCOL_RBT; |
|
| 333 | 4 | } |
|
| 334 | |||
| 335 | 215 | return self::$_protocol; |
|
| 336 | } |
||
| 337 | |||
| 338 | /** |
||
| 339 | * @param string $proto |
||
| 340 | * @return bool |
||
| 341 | */ |
||
| 342 | 85 | final public static function isProtocol($proto) |
|
| 343 | { |
||
| 344 | 85 | return self::getProtocol() == $proto; |
|
| 345 | } |
||
| 346 | |||
| 347 | /** |
||
| 348 | * @return array [dataTypeConstant => dataTypeSymbol] |
||
| 349 | */ |
||
| 350 | 185 | final public static function getSupportedDataTypes() |
|
| 351 | { |
||
| 352 | 185 | switch ($proto = self::getProtocol()) { |
|
| 353 | 185 | case self::PROTOCOL_080: |
|
| 354 | 10 | $types = self::$_types_080; |
|
| 355 | 10 | break; |
|
| 356 | 175 | case self::PROTOCOL_091: |
|
| 357 | 40 | $types = self::$_types_091; |
|
| 358 | 40 | break; |
|
| 359 | 140 | case self::PROTOCOL_RBT: |
|
| 360 | 140 | $types = self::$_types_rabbit; |
|
| 361 | 140 | break; |
|
| 362 | default: |
||
| 363 | throw new Exception\AMQPOutOfRangeException(sprintf('Unknown protocol: %s', $proto)); |
||
| 364 | 148 | } |
|
| 365 | |||
| 366 | 185 | return $types; |
|
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * @param string $type |
||
| 371 | * @param bool $return Whether to return or raise AMQPOutOfRangeException |
||
| 372 | * @return boolean |
||
| 373 | */ |
||
| 374 | 80 | final public static function checkDataTypeIsSupported($type, $return = true) |
|
| 375 | { |
||
| 376 | try { |
||
| 377 | 80 | $supported = self::getSupportedDataTypes(); |
|
| 378 | 80 | if (!isset($supported[$type])) { |
|
| 379 | 15 | throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t support data of type [%s]', self::getProtocol(), $type)); |
|
| 380 | } |
||
| 381 | 65 | return true; |
|
| 382 | |||
| 383 | 15 | } catch (Exception\AMQPOutOfRangeException $ex) { |
|
| 384 | 15 | if (!$return) { |
|
| 385 | 15 | throw $ex; |
|
| 386 | } |
||
| 387 | |||
| 388 | return false; |
||
| 389 | } |
||
| 390 | } |
||
| 391 | |||
| 392 | /** |
||
| 393 | * @param integer $type |
||
| 394 | * @return string |
||
| 395 | */ |
||
| 396 | 135 | View Code Duplication | final public static function getSymbolForDataType($type) |
| 397 | { |
||
| 398 | 135 | $types = self::getSupportedDataTypes(); |
|
| 399 | 135 | if (!isset($types[$type])) { |
|
| 400 | throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t support data of type [%s]', self::getProtocol(), $type)); |
||
| 401 | } |
||
| 402 | |||
| 403 | 135 | return $types[$type]; |
|
| 404 | } |
||
| 405 | |||
| 406 | /** |
||
| 407 | * @param string $symbol |
||
| 408 | * @return integer |
||
| 409 | */ |
||
| 410 | 155 | View Code Duplication | final public static function getDataTypeForSymbol($symbol) |
| 411 | { |
||
| 412 | 155 | $symbols = array_flip(self::getSupportedDataTypes()); |
|
| 413 | 155 | if (!isset($symbols[$symbol])) { |
|
| 414 | 5 | throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t define data of type [%s]', self::getProtocol(), $symbol)); |
|
| 415 | } |
||
| 416 | |||
| 417 | 150 | return $symbols[$symbol]; |
|
| 418 | } |
||
| 419 | |||
| 420 | 55 | public function current() |
|
| 421 | { |
||
| 422 | 55 | return current($this->data); |
|
| 423 | } |
||
| 424 | |||
| 425 | 25 | public function key() |
|
| 426 | { |
||
| 427 | 25 | return key($this->data); |
|
| 428 | } |
||
| 429 | |||
| 430 | 55 | public function next() |
|
| 431 | { |
||
| 432 | 55 | next($this->data); |
|
| 433 | 55 | } |
|
| 434 | |||
| 435 | 60 | public function rewind() |
|
| 436 | { |
||
| 437 | 60 | reset($this->data); |
|
| 438 | 60 | } |
|
| 439 | |||
| 440 | 60 | public function valid() |
|
| 444 | } |
||
| 445 |
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
integervalues, zero is a special case, in particular the following results might be unexpected: