dedalozzo /
surfer
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * @file Message.php |
||
| 5 | * @brief This file contains the Message class. |
||
| 6 | * @details |
||
| 7 | * @author Filippo F. Fadda |
||
| 8 | */ |
||
| 9 | |||
| 10 | //! Message, request and response. |
||
| 11 | namespace Surfer\Message; |
||
| 12 | |||
| 13 | |||
| 14 | use ToolBag\Helper\ArrayHelper; |
||
| 15 | |||
| 16 | |||
| 17 | /** |
||
| 18 | * @brief An HTTP Message can either be a Request or a Response. This class represents a generic message with common |
||
| 19 | * properties and methods. |
||
| 20 | * @nosubgrouping |
||
| 21 | */ |
||
| 22 | abstract class Message { |
||
| 23 | |||
| 24 | //! CR+LF (0x0D 0x0A). A Carriage Return followed by a Line Feed. We don't use PHP_EOL because HTTP wants CR+LF. |
||
| 25 | const CRLF = "\r\n"; |
||
| 26 | |||
| 27 | |||
| 28 | /** @name General Header Fields */ |
||
| 29 | //!@{ |
||
| 30 | |||
| 31 | /** |
||
| 32 | * @brief Used to specify directives that MUST be obeyed by all caching mechanisms along the request/response chain. |
||
| 33 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 |
||
| 34 | */ |
||
| 35 | const CACHE_CONTROL_HF = "Cache-Control"; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * @brief HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed |
||
| 39 | * after completion of the response. HTTP/1.1 applications that do not support persistent connections MUST include |
||
| 40 | * the "close" connection option in every message. |
||
| 41 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10 |
||
| 42 | */ |
||
| 43 | const CONNECTION_HF = "Connection"; |
||
| 44 | |||
| 45 | /** |
||
| 46 | * @brief Provides a date and time stamp telling when the message was created. |
||
| 47 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 |
||
| 48 | */ |
||
| 49 | const DATE_HF = "Date"; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @brief The Pragma general-header field is used to include implementation- specific directives that might apply to |
||
| 53 | * any recipient along the request/response chain. |
||
| 54 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32 |
||
| 55 | */ |
||
| 56 | const PRAGMA_HF = "Pragma"; |
||
| 57 | |||
| 58 | /** |
||
| 59 | * @brief Lists the set of headers that are in the trailer of a message encoded with the chunked transfer encoding. |
||
| 60 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.40 |
||
| 61 | */ |
||
| 62 | const TRAILER_HF = "Trailer"; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * @brief Tells the receiver what encoding was performed on the message in order for it to be transported safely. |
||
| 66 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41 |
||
| 67 | */ |
||
| 68 | const TRANSFER_ENCODING_HF = "Transfer-Encoding"; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * @brief Gives a new version or protocol that the sender would like to "upgrade" to using. |
||
| 72 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.42 |
||
| 73 | */ |
||
| 74 | const UPGRADE_HF = "Upgrade"; |
||
| 75 | |||
| 76 | /** |
||
| 77 | * @brief Shows what intermediaries (proxies, gateways) the message has gone through. |
||
| 78 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.45 |
||
| 79 | */ |
||
| 80 | const VIA_HF = "Via"; |
||
| 81 | |||
| 82 | /** |
||
| 83 | * @brief The Warning general-header field is used to carry additional information about the status or transformation |
||
| 84 | * of a message which might not be reflected in the message. This information is typically used to warn about a possible |
||
| 85 | * lack of semantic transparency from caching operations or transformations applied to the entity body of the message. |
||
| 86 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46 |
||
| 87 | */ |
||
| 88 | const WARNING_HF = "Warning"; |
||
| 89 | |||
| 90 | //!@} |
||
| 91 | |||
| 92 | /** @name Entity Header Fields */ |
||
| 93 | //!@{ |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @brief The purpose of this field is strictly to inform the recipient of valid methods associated with the resource. |
||
| 97 | * To be used for a 405 Method not allowed. |
||
| 98 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 |
||
| 99 | */ |
||
| 100 | const ALLOW_HF = "Allow"; |
||
| 101 | |||
| 102 | /** |
||
| 103 | * @brief The Content-Encoding entity-header field is used as a modifier to the media-type. When present, its value |
||
| 104 | * indicates what additional content codings have been applied to the entity-body, and thus what decoding mechanisms |
||
| 105 | * must be applied in order to obtain the media-type referenced by the Content-Type header field. Content-Encoding is |
||
| 106 | * primarily used to allow a document to be compressed without losing the identity of its underlying media type. |
||
| 107 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 |
||
| 108 | */ |
||
| 109 | const CONTENT_ENCODING_HF = "Content-Encoding"; |
||
| 110 | |||
| 111 | /** |
||
| 112 | * @brief The natural language that is best used to understand the body. |
||
| 113 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.12 |
||
| 114 | */ |
||
| 115 | const CONTENT_LANGUAGE_HF = "Content-Language"; |
||
| 116 | |||
| 117 | /** |
||
| 118 | * @brief The length or size of the body. |
||
| 119 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13 |
||
| 120 | */ |
||
| 121 | const CONTENT_LENGTH_HF = "Content-Length"; |
||
| 122 | |||
| 123 | /** |
||
| 124 | * @brief Where the resource actually is located. |
||
| 125 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.14 |
||
| 126 | */ |
||
| 127 | const CONTENT_LOCATION_HF = "Content-Location"; |
||
| 128 | |||
| 129 | /** |
||
| 130 | * @brief An MD5 checksum of the body. |
||
| 131 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.15 |
||
| 132 | */ |
||
| 133 | const CONTENT_MD5_HF = "Content-MD5"; |
||
| 134 | |||
| 135 | /** |
||
| 136 | * @brief The range of bytes that this entity represents from the entire resource. |
||
| 137 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16 |
||
| 138 | */ |
||
| 139 | const CONTENT_RANGE_HF = "Content-Range"; |
||
| 140 | |||
| 141 | /** |
||
| 142 | * @brief The type of object that this body is. |
||
| 143 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 |
||
| 144 | */ |
||
| 145 | const CONTENT_TYPE_HF = "Content-Type"; |
||
| 146 | |||
| 147 | /** |
||
| 148 | * @brief The Expires entity-header field gives the date/time after which the response is considered stale. A stale |
||
| 149 | * cache entry may not normally be returned by a cache (either a proxy cache or a user agent cache) unless it is first |
||
| 150 | * validated with the origin server (or with an intermediate cache that has a fresh copy of the entity). |
||
| 151 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 |
||
| 152 | */ |
||
| 153 | const EXPIRES_HF = "Expires"; |
||
| 154 | |||
| 155 | /** |
||
| 156 | * @brief The last modified date for the requested object. |
||
| 157 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29 |
||
| 158 | */ |
||
| 159 | const LAST_MODIFIED_HF = "Last-Modified"; |
||
| 160 | |||
| 161 | //!@} |
||
| 162 | |||
| 163 | // Stores the header fields supported by a Message. |
||
| 164 | protected static $supportedHeaderFields = array( // Cannot use [] syntax otherwise Doxygen generates a warning. |
||
| 165 | self::CACHE_CONTROL_HF => NULL, |
||
| 166 | self::CONNECTION_HF => NULL, |
||
| 167 | self::DATE_HF => NULL, |
||
| 168 | self::PRAGMA_HF => NULL, |
||
| 169 | self::TRAILER_HF => NULL, |
||
| 170 | self::TRANSFER_ENCODING_HF => NULL, |
||
| 171 | self::UPGRADE_HF => NULL, |
||
| 172 | self::VIA_HF => NULL, |
||
| 173 | self::WARNING_HF => NULL, |
||
| 174 | self::ALLOW_HF => NULL, |
||
| 175 | self::CONTENT_ENCODING_HF => NULL, |
||
| 176 | self::CONTENT_LANGUAGE_HF => NULL, |
||
| 177 | self::CONTENT_LENGTH_HF => NULL, |
||
| 178 | self::CONTENT_LOCATION_HF => NULL, |
||
| 179 | self::CONTENT_MD5_HF => NULL, |
||
| 180 | self::CONTENT_RANGE_HF => NULL, |
||
| 181 | self::CONTENT_TYPE_HF => NULL, |
||
| 182 | self::EXPIRES_HF => NULL, |
||
| 183 | self::LAST_MODIFIED_HF => NULL |
||
| 184 | ); |
||
| 185 | |||
| 186 | // Stores the message header fields. |
||
| 187 | protected $header = []; |
||
| 188 | |||
| 189 | // Stores the entity body. |
||
| 190 | protected $body = ""; |
||
| 191 | |||
| 192 | |||
| 193 | /** |
||
| 194 | * @brief Creates a Message object. |
||
| 195 | * @details I wanted declare it abstract, but since PHP sucks, I can't define a constructor, in a derived class, with |
||
| 196 | * a different number of parameters, not when the parent's class constructor is abstract. |
||
| 197 | */ |
||
| 198 | public function __construct() { |
||
| 199 | } |
||
| 200 | |||
| 201 | |||
| 202 | /** |
||
| 203 | * @brief Returns an array of well-formed header fields. Ex: <c>Content-Type: application/json</c>. |
||
| 204 | * @return array An array of strings. |
||
| 205 | */ |
||
| 206 | public function getHeaderAsArray() { |
||
| 207 | $wellformedHeader = []; |
||
| 208 | foreach ($this->header as $name => $value) |
||
| 209 | $wellformedHeader[] = $name.": ".$value; |
||
| 210 | |||
| 211 | return $wellformedHeader; |
||
| 212 | } |
||
| 213 | |||
| 214 | |||
| 215 | /** |
||
| 216 | * @brief Returns the header string. |
||
| 217 | * @return string |
||
| 218 | */ |
||
| 219 | public function getHeaderAsString() { |
||
| 220 | return implode(self::CRLF, $this->getHeaderAsArray()); |
||
| 221 | } |
||
| 222 | |||
| 223 | |||
| 224 | /** |
||
| 225 | * @brief Returns `TRUE` in case exist the specified header field or `FALSE` in case it doesn't exist. |
||
| 226 | * @param string $name Header field name. |
||
| 227 | * @return bool |
||
| 228 | */ |
||
| 229 | public function hasHeaderField($name) { |
||
| 230 | return (array_key_exists($name, $this->header)) ? TRUE : FALSE; |
||
| 231 | } |
||
| 232 | |||
| 233 | |||
| 234 | /** |
||
| 235 | * @brief Returns the value for the header identified by the specified name or `FALSE` in case it doesn't exist. |
||
| 236 | * @param string $name Header field name. |
||
| 237 | * @return string |
||
| 238 | */ |
||
| 239 | public function getHeaderFieldValue($name) { |
||
| 240 | if ($this->hasHeaderField($name)) |
||
| 241 | return $this->header[$name]; |
||
| 242 | else |
||
| 243 | return FALSE; |
||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 244 | } |
||
| 245 | |||
| 246 | |||
| 247 | /** |
||
| 248 | * @brief Adds to the headers associative array, the provided header's name and value. |
||
| 249 | * @param string $name The header field name. |
||
| 250 | * @param string $value The header field value. |
||
| 251 | */ |
||
| 252 | public function setHeaderField($name, $value) { |
||
| 253 | if (array_key_exists($name, static::$supportedHeaderFields)) |
||
| 254 | $this->header[$name] = $value; |
||
| 255 | else |
||
| 256 | throw new \Exception("$name header field is not supported."); |
||
| 257 | } |
||
| 258 | |||
| 259 | |||
| 260 | /** |
||
| 261 | * @brief Removes the header field from the headers associative array. |
||
| 262 | * @param string $name The header field name. |
||
| 263 | */ |
||
| 264 | public function removeHeaderField($name) { |
||
| 265 | if (array_key_exists($name, static::$supportedHeaderFields)) |
||
| 266 | unset($this->header[$name]); |
||
| 267 | } |
||
| 268 | |||
| 269 | |||
| 270 | /** |
||
| 271 | * @brief Used to set many header fields at once. |
||
| 272 | * @param array $headerFields An associative array of header fields. |
||
| 273 | */ |
||
| 274 | public function setMultipleHeaderFieldsAtOnce(array $headerFields) { |
||
| 275 | if (ArrayHelper::isAssociative($headerFields)) |
||
| 276 | foreach ($headerFields as $name => $value) |
||
| 277 | $this->setHeaderField($name, $value); |
||
| 278 | else |
||
| 279 | throw new \Exception("\$headerFields must be an associative array."); |
||
| 280 | } |
||
| 281 | |||
| 282 | |||
| 283 | /** |
||
| 284 | * @brief Adds a non standard HTTP header. |
||
| 285 | * @param string $name Header field name. |
||
| 286 | */ |
||
| 287 | public static function addCustomHeaderField($name) { |
||
| 288 | if (array_key_exists($name, static::$supportedHeaderFields)) |
||
| 289 | throw new \Exception("$name header field is supported but already exists."); |
||
| 290 | else |
||
| 291 | static::$supportedHeaderFields[] = $name; |
||
| 292 | } |
||
| 293 | |||
| 294 | |||
| 295 | /** |
||
| 296 | * @brief Returns a list of all supported header fields. |
||
| 297 | * @return array An associative array |
||
| 298 | */ |
||
| 299 | public function getSupportedHeaderFields() { |
||
| 300 | return static::$supportedHeaderFields; |
||
| 301 | } |
||
| 302 | |||
| 303 | |||
| 304 | /** |
||
| 305 | * @brief Returns the Message entity-body as raw string. The string can be in JSON, XML or a proprietary format, |
||
| 306 | * depends by the server implementation. |
||
| 307 | * @return string |
||
| 308 | */ |
||
| 309 | public function getBody() { |
||
| 310 | return $this->body; |
||
| 311 | } |
||
| 312 | |||
| 313 | |||
| 314 | /** |
||
| 315 | * @brief Returns the Message entity-body JSON as an array. |
||
| 316 | * @param bool $assoc When `true`, returned objects will be converted into associative arrays. |
||
| 317 | * @return array An associative array |
||
| 318 | */ |
||
| 319 | public function getBodyAsArray($assoc = TRUE) { |
||
| 320 | return ArrayHelper::fromJson($this->body, $assoc); |
||
| 321 | } |
||
| 322 | |||
| 323 | |||
| 324 | /** |
||
| 325 | * @brief Returns the Message entity-body JSON as an object. |
||
| 326 | * @return object |
||
| 327 | */ |
||
| 328 | public function getBodyAsObject() { |
||
| 329 | return ArrayHelper::toObject($this->getBodyAsArray(FALSE)); |
||
| 330 | } |
||
| 331 | |||
| 332 | |||
| 333 | /** |
||
| 334 | * @brief Sets the Message entity-body. |
||
| 335 | * @param string $body |
||
| 336 | */ |
||
| 337 | public function setBody($body) { |
||
| 338 | $this->body = (string)$body; |
||
| 339 | } |
||
| 340 | |||
| 341 | |||
| 342 | /** |
||
| 343 | * @brief Checks if the Message has a body. |
||
| 344 | * @return bool |
||
| 345 | */ |
||
| 346 | public function hasBody() { |
||
| 347 | return (empty($this->body)) ? FALSE : TRUE; |
||
| 348 | } |
||
| 349 | |||
| 350 | |||
| 351 | /** |
||
| 352 | * @brief Returns the body length. |
||
| 353 | * @return integer |
||
| 354 | */ |
||
| 355 | public function getBodyLength() { |
||
| 356 | return strlen($this->body); |
||
| 357 | } |
||
| 358 | |||
| 359 | } |