Complex classes like Response 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 Response, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 30 | class Response |
||
| 31 | { |
||
| 32 | use Options; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * @var string HTTP protocol version |
||
| 36 | */ |
||
| 37 | protected $protocol = '1.1'; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * @var integer response code equal to HTTP status codes |
||
| 41 | */ |
||
| 42 | protected $code = 200; |
||
| 43 | |||
| 44 | /** |
||
| 45 | * @var string|null HTTP Phrase |
||
| 46 | */ |
||
| 47 | protected $phrase; |
||
| 48 | |||
| 49 | /** |
||
| 50 | * @var array list of headers |
||
| 51 | */ |
||
| 52 | protected $headers = array(); |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @var array list of cookies |
||
| 56 | */ |
||
| 57 | protected $cookies = array(); |
||
| 58 | |||
| 59 | /** |
||
| 60 | * @var mixed result can be View|object|function |
||
| 61 | */ |
||
| 62 | protected $body; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * send |
||
| 66 | * |
||
| 67 | * @return void |
||
| 68 | */ |
||
| 69 | public function send() |
||
| 70 | { |
||
| 71 | // switch statement for $this->status |
||
|
|
|||
| 72 | switch ($this->getStatusCode()) { |
||
| 73 | case 204: |
||
| 74 | $response = new EmptyResponse($this->getStatusCode(), $this->getHeaders()); |
||
| 75 | break; |
||
| 76 | case 301: |
||
| 77 | case 302: |
||
| 78 | $response = new RedirectResponse( |
||
| 79 | $this->getHeader('Location'), |
||
| 80 | $this->getStatusCode(), |
||
| 81 | $this->getHeaders() |
||
| 82 | ); |
||
| 83 | break; |
||
| 84 | default: |
||
| 85 | $body = $this->getBody(); |
||
| 86 | |||
| 87 | // run callable structure, but don't run view |
||
| 88 | if (is_callable($body) && !($body instanceof View)) { |
||
| 89 | $body = $body(); |
||
| 90 | } |
||
| 91 | |||
| 92 | if (is_null($body)) { |
||
| 93 | // empty response |
||
| 94 | $response = new EmptyResponse( |
||
| 95 | $this->getStatusCode(), |
||
| 96 | $this->getHeaders() |
||
| 97 | ); |
||
| 98 | } elseif (PHP_SAPI === 'cli') { |
||
| 99 | // CLI response |
||
| 100 | // extract data from view |
||
| 101 | if ($body instanceof View) { |
||
| 102 | // just print to console as key-value pair |
||
| 103 | $data = $body->toArray(); |
||
| 104 | $output = array(); |
||
| 105 | array_walk_recursive($data, function ($value, $key) use (&$output) { |
||
| 106 | $output[] = $key .': '. $value; |
||
| 107 | }); |
||
| 108 | $body = join("\n", $output); |
||
| 109 | } |
||
| 110 | |||
| 111 | // @TODO: create CLIResponse |
||
| 112 | $response = new HtmlResponse( |
||
| 113 | (string) $body, |
||
| 114 | $this->getStatusCode(), |
||
| 115 | $this->getHeaders() |
||
| 116 | ); |
||
| 117 | } elseif ($this->getHeader('Content-Type') == 'application/json') { |
||
| 118 | // JSON response |
||
| 119 | |||
| 120 | // setup messages |
||
| 121 | if (Messages::count()) { |
||
| 122 | $this->setHeader('Bluz-Notify', json_encode(Messages::popAll())); |
||
| 123 | } |
||
| 124 | |||
| 125 | // extract data from view |
||
| 126 | if ($body instanceof View) { |
||
| 127 | $body = $body->toArray(); |
||
| 128 | } |
||
| 129 | |||
| 130 | // encode body data to JSON |
||
| 131 | $response = new JsonResponse( |
||
| 132 | $body, |
||
| 133 | $this->getStatusCode(), |
||
| 134 | $this->getHeaders() |
||
| 135 | ); |
||
| 136 | } else { |
||
| 137 | // HTML response |
||
| 138 | $response = new HtmlResponse( |
||
| 139 | (string) $body, |
||
| 140 | $this->getStatusCode(), |
||
| 141 | $this->getHeaders() |
||
| 142 | ); |
||
| 143 | } |
||
| 144 | break; |
||
| 145 | } |
||
| 146 | |||
| 147 | $emitter = new SapiEmitter(); |
||
| 148 | $emitter->emit($response); |
||
| 149 | } |
||
| 150 | |||
| 151 | /** |
||
| 152 | * Gets the HTTP protocol version as a string |
||
| 153 | * |
||
| 154 | * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). |
||
| 155 | * |
||
| 156 | * @return string HTTP protocol version. |
||
| 157 | */ |
||
| 158 | 1 | public function getProtocolVersion() |
|
| 162 | |||
| 163 | /** |
||
| 164 | * Gets the response Status-Code |
||
| 165 | * |
||
| 166 | * The Status-Code is a 3-digit integer result code of the server's attempt |
||
| 167 | * to understand and satisfy the request. |
||
| 168 | * |
||
| 169 | * @return integer status code. |
||
| 170 | */ |
||
| 171 | 1 | public function getStatusCode() |
|
| 175 | |||
| 176 | /** |
||
| 177 | * Sets the status code of this response |
||
| 178 | * |
||
| 179 | * @param integer $code the 3-digit integer result code to set. |
||
| 180 | * @return void |
||
| 181 | */ |
||
| 182 | 11 | public function setStatusCode($code) |
|
| 186 | |||
| 187 | /** |
||
| 188 | * Gets the response Reason-Phrase, a short textual description of the Status-Code |
||
| 189 | * |
||
| 190 | * Because a Reason-Phrase is not a required element in response |
||
| 191 | * Status-Line, the Reason-Phrase value MAY be null. Implementations MAY |
||
| 192 | * choose to return the default RFC 2616 recommended reason phrase for the |
||
| 193 | * response's Status-Code. |
||
| 194 | * |
||
| 195 | * @return string|null reason phrase, or null if unknown. |
||
| 196 | */ |
||
| 197 | 2 | public function getReasonPhrase() |
|
| 201 | |||
| 202 | /** |
||
| 203 | * Sets the Reason-Phrase of the response |
||
| 204 | * |
||
| 205 | * If no Reason-Phrase is specified, implementations MAY choose to default |
||
| 206 | * to the RFC 2616 recommended reason phrase for the response's Status-Code. |
||
| 207 | * |
||
| 208 | * @param string $phrase the Reason-Phrase to set. |
||
| 209 | */ |
||
| 210 | 1 | public function setReasonPhrase($phrase) |
|
| 214 | |||
| 215 | /** |
||
| 216 | * Retrieve a header by the given case-insensitive name as a string |
||
| 217 | * |
||
| 218 | * This method returns all of the header values of the given |
||
| 219 | * case-insensitive header name as a string concatenated together using |
||
| 220 | * a comma. |
||
| 221 | * |
||
| 222 | * @param string $header case-insensitive header name. |
||
| 223 | * @return string |
||
| 224 | */ |
||
| 225 | 4 | public function getHeader($header) |
|
| 233 | |||
| 234 | /** |
||
| 235 | * Retrieves a header by the given case-insensitive name as an array of strings |
||
| 236 | * |
||
| 237 | * @param string $header Case-insensitive header name. |
||
| 238 | * @return string[] |
||
| 239 | */ |
||
| 240 | 1 | public function getHeaderAsArray($header) |
|
| 248 | |||
| 249 | /** |
||
| 250 | * Checks if a header exists by the given case-insensitive name |
||
| 251 | * |
||
| 252 | * @param string $header case-insensitive header name. |
||
| 253 | * @return bool returns true if any header names match the given header |
||
| 254 | * name using a case-insensitive string comparison. Returns false if |
||
| 255 | * no matching header name is found in the message. |
||
| 256 | */ |
||
| 257 | 3 | public function hasHeader($header) |
|
| 261 | |||
| 262 | /** |
||
| 263 | * Sets a header, replacing any existing values of any headers with the |
||
| 264 | * same case-insensitive name |
||
| 265 | * |
||
| 266 | * The header name is case-insensitive. The header values MUST be a string |
||
| 267 | * or an array of strings. |
||
| 268 | * |
||
| 269 | * @param string $header header name |
||
| 270 | * @param string|string[] $value header value(s) |
||
| 271 | * @return void |
||
| 272 | */ |
||
| 273 | 8 | public function setHeader($header, $value) |
|
| 277 | |||
| 278 | /** |
||
| 279 | * Appends a header value for the specified header |
||
| 280 | * |
||
| 281 | * Existing values for the specified header will be maintained. The new |
||
| 282 | * value will be appended to the existing list. |
||
| 283 | * |
||
| 284 | * @param string $header header name to add |
||
| 285 | * @param string $value value of the header |
||
| 286 | * @return void |
||
| 287 | */ |
||
| 288 | 1 | public function addHeader($header, $value) |
|
| 289 | { |
||
| 290 | 1 | if ($this->hasHeader($header)) { |
|
| 291 | 1 | $this->headers[$header][] = $value; |
|
| 292 | } else { |
||
| 293 | 1 | $this->setHeader($header, $value); |
|
| 294 | } |
||
| 295 | 1 | } |
|
| 296 | |||
| 297 | /** |
||
| 298 | * Remove a specific header by case-insensitive name. |
||
| 299 | * |
||
| 300 | * @param string $header HTTP header to remove |
||
| 301 | * @return void |
||
| 302 | */ |
||
| 303 | 1 | public function removeHeader($header) |
|
| 307 | |||
| 308 | /** |
||
| 309 | * Gets all message headers |
||
| 310 | * |
||
| 311 | * The keys represent the header name as it will be sent over the wire, and |
||
| 312 | * each value is an array of strings associated with the header. |
||
| 313 | * |
||
| 314 | * // Represent the headers as a string |
||
| 315 | * foreach ($message->getHeaders() as $name => $values) { |
||
| 316 | * echo $name . ": " . implode(", ", $values); |
||
| 317 | * } |
||
| 318 | * |
||
| 319 | * @return array returns an associative array of the message's headers. |
||
| 320 | */ |
||
| 321 | 1 | public function getHeaders() |
|
| 325 | |||
| 326 | /** |
||
| 327 | * Sets headers, replacing any headers that have already been set on the message |
||
| 328 | * |
||
| 329 | * The array keys MUST be a string. The array values must be either a |
||
| 330 | * string or an array of strings. |
||
| 331 | * |
||
| 332 | * @param array $headers Headers to set. |
||
| 333 | * @return void |
||
| 334 | */ |
||
| 335 | 1 | public function setHeaders(array $headers) |
|
| 339 | |||
| 340 | /** |
||
| 341 | * Merges in an associative array of headers. |
||
| 342 | * |
||
| 343 | * Each array key MUST be a string representing the case-insensitive name |
||
| 344 | * of a header. Each value MUST be either a string or an array of strings. |
||
| 345 | * For each value, the value is appended to any existing header of the same |
||
| 346 | * name, or, if a header does not already exist by the given name, then the |
||
| 347 | * header is added. |
||
| 348 | * |
||
| 349 | * @param array $headers Associative array of headers to add to the message |
||
| 350 | * @return void |
||
| 351 | */ |
||
| 352 | 1 | public function addHeaders(array $headers) |
|
| 356 | |||
| 357 | /** |
||
| 358 | * Remove all headers |
||
| 359 | * |
||
| 360 | * @return void |
||
| 361 | */ |
||
| 362 | 2 | public function removeHeaders() |
|
| 366 | |||
| 367 | /** |
||
| 368 | * Set response body |
||
| 369 | * |
||
| 370 | * @param mixed $body |
||
| 371 | * @return void |
||
| 372 | */ |
||
| 373 | 2 | public function setBody($body) |
|
| 377 | |||
| 378 | /** |
||
| 379 | * Get response body |
||
| 380 | * |
||
| 381 | * @return View |
||
| 382 | */ |
||
| 383 | public function getBody() |
||
| 387 | |||
| 388 | /** |
||
| 389 | * Clear response body |
||
| 390 | * |
||
| 391 | * @return void |
||
| 392 | */ |
||
| 393 | 1 | public function clearBody() |
|
| 397 | |||
| 398 | /** |
||
| 399 | * Set Cookie |
||
| 400 | * |
||
| 401 | * @param string $name |
||
| 402 | * @param string $value |
||
| 403 | * @param int|string|\DateTime $expire |
||
| 404 | * @param string $path |
||
| 405 | * @param string $domain |
||
| 406 | * @param bool $secure |
||
| 407 | * @param bool $httpOnly |
||
| 408 | * @return void |
||
| 409 | */ |
||
| 410 | 5 | public function setCookie( |
|
| 411 | $name, |
||
| 412 | $value = null, |
||
| 413 | $expire = 0, |
||
| 414 | $path = '/', |
||
| 415 | $domain = null, |
||
| 416 | $secure = false, |
||
| 417 | $httpOnly = true |
||
| 418 | ) { |
||
| 419 | // from PHP source code |
||
| 420 | 5 | if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { |
|
| 421 | 1 | throw new \InvalidArgumentException('The cookie name contains invalid characters.'); |
|
| 422 | } |
||
| 423 | |||
| 424 | 4 | if (empty($name)) { |
|
| 425 | 1 | throw new \InvalidArgumentException('The cookie name cannot be empty.'); |
|
| 426 | } |
||
| 427 | |||
| 428 | // convert expiration time to a Unix timestamp |
||
| 429 | 3 | if ($expire instanceof \DateTime) { |
|
| 430 | 1 | $expire = $expire->format('U'); |
|
| 431 | 2 | } elseif (!is_numeric($expire)) { |
|
| 432 | 1 | $expire = strtotime($expire); |
|
| 433 | 1 | if (false === $expire || -1 === $expire) { |
|
| 434 | 1 | throw new \InvalidArgumentException('The cookie expiration time is not valid.'); |
|
| 435 | } |
||
| 436 | } |
||
| 437 | |||
| 438 | 2 | $this->cookies[$name] = [ |
|
| 439 | 2 | 'name' => $name, |
|
| 440 | 2 | 'value' => $value, |
|
| 441 | 2 | 'expire' => $expire, |
|
| 442 | 2 | 'path' => empty($path) ? '/' : $path, |
|
| 443 | 2 | 'domain' => $domain, |
|
| 444 | 2 | 'secure' => (bool) $secure, |
|
| 445 | 2 | 'httpOnly' => (bool) $httpOnly |
|
| 446 | ]; |
||
| 447 | 2 | } |
|
| 448 | |||
| 449 | /** |
||
| 450 | * Get Cookie by name |
||
| 451 | * |
||
| 452 | * @param string $name |
||
| 453 | * @return array|null |
||
| 454 | */ |
||
| 455 | 2 | public function getCookie($name) |
|
| 459 | } |
||
| 460 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.