| Total Complexity | 82 |
| Total Lines | 565 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like Parser 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 Parser, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 38 | class Parser |
||
| 39 | { |
||
| 40 | /** |
||
| 41 | * Plugin triggers. |
||
| 42 | * |
||
| 43 | * These are constants indicating trigger points for plugins |
||
| 44 | * |
||
| 45 | * BEGIN: Before normal parsing |
||
| 46 | * SUCCESS: After successful parsing |
||
| 47 | * RECURSION: After parsing cancelled by recursion |
||
| 48 | * DEPTH_LIMIT: After parsing cancelled by depth limit |
||
| 49 | * COMPLETE: SUCCESS | RECURSION | DEPTH_LIMIT |
||
| 50 | * |
||
| 51 | * While a plugin's getTriggers may return any of these |
||
| 52 | */ |
||
| 53 | const TRIGGER_NONE = 0; |
||
| 54 | const TRIGGER_BEGIN = 1; |
||
| 55 | const TRIGGER_SUCCESS = 2; |
||
| 56 | const TRIGGER_RECURSION = 4; |
||
| 57 | const TRIGGER_DEPTH_LIMIT = 8; |
||
| 58 | const TRIGGER_COMPLETE = 14; |
||
| 59 | |||
| 60 | protected $caller_class; |
||
| 61 | protected $depth_limit = false; |
||
| 62 | protected $marker; |
||
| 63 | protected $object_hashes = array(); |
||
| 64 | protected $parse_break = false; |
||
| 65 | protected $plugins = array(); |
||
| 66 | |||
| 67 | /** |
||
| 68 | * @param false|int $depth_limit Maximum depth to parse data |
||
| 69 | * @param null|string $caller Caller class name |
||
| 70 | */ |
||
| 71 | public function __construct($depth_limit = false, $caller = null) |
||
| 79 | } |
||
| 80 | } |
||
| 81 | |||
| 82 | /** |
||
| 83 | * Set the caller class. |
||
| 84 | * |
||
| 85 | * @param null|string $caller Caller class name |
||
| 86 | */ |
||
| 87 | public function setCallerClass($caller = null) |
||
| 88 | { |
||
| 89 | $this->noRecurseCall(); |
||
| 90 | |||
| 91 | $this->caller_class = $caller; |
||
| 92 | } |
||
| 93 | |||
| 94 | public function getCallerClass() |
||
| 95 | { |
||
| 96 | return $this->caller_class; |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Set the depth limit. |
||
| 101 | * |
||
| 102 | * @param false|int $depth_limit Maximum depth to parse data |
||
| 103 | */ |
||
| 104 | public function setDepthLimit($depth_limit = false) |
||
| 105 | { |
||
| 106 | $this->noRecurseCall(); |
||
| 107 | |||
| 108 | $this->depth_limit = $depth_limit; |
||
| 109 | } |
||
| 110 | |||
| 111 | public function getDepthLimit() |
||
| 112 | { |
||
| 113 | return $this->depth_limit; |
||
| 114 | } |
||
| 115 | |||
| 116 | /** |
||
| 117 | * Disables the depth limit and parses a variable. |
||
| 118 | * |
||
| 119 | * This should not be used unless you know what you're doing! |
||
| 120 | * |
||
| 121 | * @param mixed $var The input variable |
||
| 122 | * @param BasicObject $o The base object |
||
| 123 | * |
||
| 124 | * @return BasicObject |
||
| 125 | */ |
||
| 126 | public function parseDeep(&$var, BasicObject $o) |
||
| 127 | { |
||
| 128 | $depth_limit = $this->depth_limit; |
||
| 129 | $this->depth_limit = false; |
||
| 130 | |||
| 131 | $out = $this->parse($var, $o); |
||
| 132 | |||
| 133 | $this->depth_limit = $depth_limit; |
||
| 134 | |||
| 135 | return $out; |
||
| 136 | } |
||
| 137 | |||
| 138 | /** |
||
| 139 | * Parses a variable into a Kint object structure. |
||
| 140 | * |
||
| 141 | * @param mixed $var The input variable |
||
| 142 | * @param BasicObject $o The base object |
||
| 143 | * |
||
| 144 | * @return BasicObject |
||
| 145 | */ |
||
| 146 | public function parse(&$var, BasicObject $o) |
||
| 147 | { |
||
| 148 | $o->type = \strtolower(\gettype($var)); |
||
| 149 | |||
| 150 | if (!$this->applyPlugins($var, $o, self::TRIGGER_BEGIN)) { |
||
| 151 | return $o; |
||
| 152 | } |
||
| 153 | |||
| 154 | switch ($o->type) { |
||
| 155 | case 'array': |
||
| 156 | return $this->parseArray($var, $o); |
||
| 157 | case 'boolean': |
||
| 158 | case 'double': |
||
| 159 | case 'integer': |
||
| 160 | case 'null': |
||
| 161 | return $this->parseGeneric($var, $o); |
||
| 162 | case 'object': |
||
| 163 | return $this->parseObject($var, $o); |
||
| 164 | case 'resource': |
||
| 165 | return $this->parseResource($var, $o); |
||
| 166 | case 'string': |
||
| 167 | return $this->parseString($var, $o); |
||
| 168 | default: |
||
| 169 | return $this->parseUnknown($var, $o); |
||
| 170 | } |
||
| 171 | } |
||
| 172 | |||
| 173 | public function addPlugin(Plugin $p) |
||
| 174 | { |
||
| 175 | if (!$types = $p->getTypes()) { |
||
| 176 | return false; |
||
| 177 | } |
||
| 178 | |||
| 179 | if (!$triggers = $p->getTriggers()) { |
||
| 180 | return false; |
||
| 181 | } |
||
| 182 | |||
| 183 | $p->setParser($this); |
||
| 184 | |||
| 185 | foreach ($types as $type) { |
||
| 186 | if (!isset($this->plugins[$type])) { |
||
| 187 | $this->plugins[$type] = array( |
||
| 188 | self::TRIGGER_BEGIN => array(), |
||
| 189 | self::TRIGGER_SUCCESS => array(), |
||
| 190 | self::TRIGGER_RECURSION => array(), |
||
| 191 | self::TRIGGER_DEPTH_LIMIT => array(), |
||
| 192 | ); |
||
| 193 | } |
||
| 194 | |||
| 195 | foreach ($this->plugins[$type] as $trigger => &$pool) { |
||
| 196 | if ($triggers & $trigger) { |
||
| 197 | $pool[] = $p; |
||
| 198 | } |
||
| 199 | } |
||
| 200 | } |
||
| 201 | |||
| 202 | return true; |
||
| 203 | } |
||
| 204 | |||
| 205 | public function clearPlugins() |
||
| 206 | { |
||
| 207 | $this->plugins = array(); |
||
| 208 | } |
||
| 209 | |||
| 210 | public function haltParse() |
||
| 211 | { |
||
| 212 | $this->parse_break = true; |
||
| 213 | } |
||
| 214 | |||
| 215 | public function childHasPath(InstanceObject $parent, BasicObject $child) |
||
| 216 | { |
||
| 217 | if ('object' === $parent->type && (null !== $parent->access_path || $child->static || $child->const)) { |
||
| 218 | if (BasicObject::ACCESS_PUBLIC === $child->access) { |
||
| 219 | return true; |
||
| 220 | } |
||
| 221 | |||
| 222 | if (BasicObject::ACCESS_PRIVATE === $child->access && $this->caller_class) { |
||
| 223 | if ($this->caller_class === $child->owner_class) { |
||
| 224 | return true; |
||
| 225 | } |
||
| 226 | } elseif (BasicObject::ACCESS_PROTECTED === $child->access && $this->caller_class) { |
||
| 227 | if ($this->caller_class === $child->owner_class) { |
||
| 228 | return true; |
||
| 229 | } |
||
| 230 | |||
| 231 | if (\is_subclass_of($this->caller_class, $child->owner_class)) { |
||
| 232 | return true; |
||
| 233 | } |
||
| 234 | |||
| 235 | if (\is_subclass_of($child->owner_class, $this->caller_class)) { |
||
| 236 | return true; |
||
| 237 | } |
||
| 238 | } |
||
| 239 | } |
||
| 240 | |||
| 241 | return false; |
||
| 242 | } |
||
| 243 | |||
| 244 | /** |
||
| 245 | * Returns an array without the recursion marker in it. |
||
| 246 | * |
||
| 247 | * DO NOT pass an array that has had it's marker removed back |
||
| 248 | * into the parser, it will result in an extra recursion |
||
| 249 | * |
||
| 250 | * @param array $array Array potentially containing a recursion marker |
||
| 251 | * |
||
| 252 | * @return array Array with recursion marker removed |
||
| 253 | */ |
||
| 254 | public function getCleanArray(array $array) |
||
| 255 | { |
||
| 256 | unset($array[$this->marker]); |
||
| 257 | |||
| 258 | return $array; |
||
| 259 | } |
||
| 260 | |||
| 261 | protected function noRecurseCall() |
||
| 262 | { |
||
| 263 | $bt = \debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS); |
||
| 264 | |||
| 265 | $caller_frame = array( |
||
| 266 | 'function' => __FUNCTION__, |
||
| 267 | ); |
||
| 268 | |||
| 269 | while (isset($bt[0]['object']) && $bt[0]['object'] === $this) { |
||
| 270 | $caller_frame = \array_shift($bt); |
||
| 271 | } |
||
| 272 | |||
| 273 | foreach ($bt as $frame) { |
||
| 274 | if (isset($frame['object']) && $frame['object'] === $this) { |
||
| 275 | throw new DomainException(__CLASS__.'::'.$caller_frame['function'].' cannot be called from inside a parse'); |
||
| 276 | } |
||
| 277 | } |
||
| 278 | } |
||
| 279 | |||
| 280 | private function parseGeneric(&$var, BasicObject $o) |
||
| 281 | { |
||
| 282 | $rep = new Representation('Contents'); |
||
| 283 | $rep->contents = $var; |
||
| 284 | $rep->implicit_label = true; |
||
| 285 | $o->addRepresentation($rep); |
||
| 286 | $o->value = $rep; |
||
| 287 | |||
| 288 | $this->applyPlugins($var, $o, self::TRIGGER_SUCCESS); |
||
| 289 | |||
| 290 | return $o; |
||
| 291 | } |
||
| 292 | |||
| 293 | /** |
||
| 294 | * Parses a string into a Kint BlobObject structure. |
||
| 295 | * |
||
| 296 | * @param string $var The input variable |
||
| 297 | * @param BasicObject $o The base object |
||
| 298 | * |
||
| 299 | * @return BasicObject |
||
| 300 | */ |
||
| 301 | private function parseString(&$var, BasicObject $o) |
||
| 302 | { |
||
| 303 | $string = new BlobObject(); |
||
| 304 | $string->transplant($o); |
||
| 305 | $string->encoding = BlobObject::detectEncoding($var); |
||
| 306 | $string->size = BlobObject::strlen($var, $string->encoding); |
||
| 307 | |||
| 308 | $rep = new Representation('Contents'); |
||
| 309 | $rep->contents = $var; |
||
| 310 | $rep->implicit_label = true; |
||
| 311 | |||
| 312 | $string->addRepresentation($rep); |
||
| 313 | $string->value = $rep; |
||
| 314 | |||
| 315 | $this->applyPlugins($var, $string, self::TRIGGER_SUCCESS); |
||
| 316 | |||
| 317 | return $string; |
||
| 318 | } |
||
| 319 | |||
| 320 | /** |
||
| 321 | * Parses an array into a Kint object structure. |
||
| 322 | * |
||
| 323 | * @param array $var The input variable |
||
| 324 | * @param BasicObject $o The base object |
||
| 325 | * |
||
| 326 | * @return BasicObject |
||
| 327 | */ |
||
| 328 | private function parseArray(array &$var, BasicObject $o) |
||
| 411 | } |
||
| 412 | |||
| 413 | /** |
||
| 414 | * Parses an object into a Kint InstanceObject structure. |
||
| 415 | * |
||
| 416 | * @param object $var The input variable |
||
| 417 | * @param BasicObject $o The base object |
||
| 418 | * |
||
| 419 | * @return BasicObject |
||
| 420 | */ |
||
| 421 | private function parseObject(&$var, BasicObject $o) |
||
| 422 | { |
||
| 423 | $hash = \spl_object_hash($var); |
||
| 424 | $values = (array) $var; |
||
| 425 | |||
| 426 | $object = new InstanceObject(); |
||
| 427 | $object->transplant($o); |
||
| 428 | $object->classname = \get_class($var); |
||
| 429 | $object->hash = $hash; |
||
| 430 | $object->size = \count($values); |
||
| 431 | |||
| 432 | if (isset($this->object_hashes[$hash])) { |
||
| 433 | $object->hints[] = 'recursion'; |
||
| 434 | |||
| 435 | $this->applyPlugins($var, $object, self::TRIGGER_RECURSION); |
||
| 436 | |||
| 437 | return $object; |
||
| 438 | } |
||
| 439 | |||
| 440 | $this->object_hashes[$hash] = $object; |
||
| 441 | |||
| 442 | if ($this->depth_limit && $o->depth >= $this->depth_limit) { |
||
| 443 | $object->hints[] = 'depth_limit'; |
||
| 444 | |||
| 445 | $this->applyPlugins($var, $object, self::TRIGGER_DEPTH_LIMIT); |
||
| 446 | unset($this->object_hashes[$hash]); |
||
| 447 | |||
| 448 | return $object; |
||
| 449 | } |
||
| 450 | |||
| 451 | $reflector = new ReflectionObject($var); |
||
| 452 | |||
| 453 | if ($reflector->isUserDefined()) { |
||
| 454 | $object->filename = $reflector->getFileName(); |
||
| 455 | $object->startline = $reflector->getStartLine(); |
||
| 456 | } |
||
| 457 | |||
| 458 | $rep = new Representation('Properties'); |
||
| 459 | |||
| 460 | $copy = \array_values($values); |
||
| 461 | $refmarker = new stdClass(); |
||
| 462 | $i = 0; |
||
| 463 | |||
| 464 | // Reflection will not show parent classes private properties, and if a |
||
| 465 | // property was unset it will happly trigger a notice looking for it. |
||
| 466 | foreach ($values as $key => &$val) { |
||
| 467 | // Casting object to array: |
||
| 468 | // private properties show in the form "\0$owner_class_name\0$property_name"; |
||
| 469 | // protected properties show in the form "\0*\0$property_name"; |
||
| 470 | // public properties show in the form "$property_name"; |
||
| 471 | // http://www.php.net/manual/en/language.types.array.php#language.types.array.casting |
||
| 472 | |||
| 473 | $child = new BasicObject(); |
||
| 474 | $child->depth = $object->depth + 1; |
||
| 475 | $child->owner_class = $object->classname; |
||
| 476 | $child->operator = BasicObject::OPERATOR_OBJECT; |
||
| 477 | $child->access = BasicObject::ACCESS_PUBLIC; |
||
| 478 | |||
| 479 | $split_key = \explode("\0", $key, 3); |
||
| 480 | |||
| 481 | if (3 === \count($split_key) && '' === $split_key[0]) { |
||
| 482 | $child->name = $split_key[2]; |
||
| 483 | if ('*' === $split_key[1]) { |
||
| 484 | $child->access = BasicObject::ACCESS_PROTECTED; |
||
| 485 | } else { |
||
| 486 | $child->access = BasicObject::ACCESS_PRIVATE; |
||
| 487 | $child->owner_class = $split_key[1]; |
||
| 488 | } |
||
| 489 | } elseif (KINT_PHP72) { |
||
| 490 | $child->name = (string) $key; |
||
| 491 | } else { |
||
| 492 | $child->name = $key; // @codeCoverageIgnore |
||
| 493 | } |
||
| 494 | |||
| 495 | if ($this->childHasPath($object, $child)) { |
||
| 496 | $child->access_path = $object->access_path; |
||
| 497 | |||
| 498 | if (!KINT_PHP72 && \is_int($child->name)) { |
||
| 499 | $child->access_path = 'array_values((array) '.$child->access_path.')['.$i.']'; // @codeCoverageIgnore |
||
| 500 | } elseif (\preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $child->name)) { |
||
| 501 | $child->access_path .= '->'.$child->name; |
||
| 502 | } else { |
||
| 503 | $child->access_path .= '->{'.\var_export((string) $child->name, true).'}'; |
||
| 504 | } |
||
| 505 | } |
||
| 506 | |||
| 507 | $stash = $val; |
||
| 508 | $copy[$i] = $refmarker; |
||
| 509 | if ($val === $refmarker) { |
||
| 510 | $child->reference = true; |
||
| 511 | $val = $stash; |
||
| 512 | } |
||
| 513 | |||
| 514 | $rep->contents[] = $this->parse($val, $child); |
||
| 515 | ++$i; |
||
| 516 | } |
||
| 517 | |||
| 518 | $object->addRepresentation($rep); |
||
| 519 | $object->value = $rep; |
||
| 520 | $this->applyPlugins($var, $object, self::TRIGGER_SUCCESS); |
||
| 521 | unset($this->object_hashes[$hash]); |
||
| 522 | |||
| 523 | return $object; |
||
| 524 | } |
||
| 525 | |||
| 526 | /** |
||
| 527 | * Parses a resource into a Kint ResourceObject structure. |
||
| 528 | * |
||
| 529 | * @param resource $var The input variable |
||
| 530 | * @param BasicObject $o The base object |
||
| 531 | * |
||
| 532 | * @return BasicObject |
||
| 533 | */ |
||
| 534 | private function parseResource(&$var, BasicObject $o) |
||
| 535 | { |
||
| 536 | $resource = new ResourceObject(); |
||
| 537 | $resource->transplant($o); |
||
| 538 | $resource->resource_type = \get_resource_type($var); |
||
| 539 | |||
| 540 | $this->applyPlugins($var, $resource, self::TRIGGER_SUCCESS); |
||
| 541 | |||
| 542 | return $resource; |
||
| 543 | } |
||
| 544 | |||
| 545 | /** |
||
| 546 | * Parses an unknown into a Kint object structure. |
||
| 547 | * |
||
| 548 | * @param mixed $var The input variable |
||
| 549 | * @param BasicObject $o The base object |
||
| 550 | * |
||
| 551 | * @return BasicObject |
||
| 552 | */ |
||
| 553 | private function parseUnknown(&$var, BasicObject $o) |
||
| 559 | } |
||
| 560 | |||
| 561 | /** |
||
| 562 | * Applies plugins for an object type. |
||
| 563 | * |
||
| 564 | * @param mixed $var variable |
||
| 565 | * @param BasicObject $o Kint object parsed so far |
||
| 566 | * @param int $trigger The trigger to check for the plugins |
||
| 567 | * |
||
| 568 | * @return bool Continue parsing |
||
| 569 | */ |
||
| 570 | private function applyPlugins(&$var, BasicObject &$o, $trigger) |
||
| 603 | } |
||
| 604 | } |
||
| 605 |
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: