Issues (16)

src/Surfer/Message/Message.php (1 issue)

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
The expression return FALSE returns the type false which is incompatible with the documented return type string.
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
}