ICanBoogie/HTTP v2.4.0
  • Namespace
  • Class

Namespaces

  • ICanBoogie
    • Exception
    • HTTP
      • Dispatcher
      • Headers
      • Request

Classes

  • ICanBoogie\Exception\RescueEvent
  • ICanBoogie\HTTP\CallableDispatcher
  • ICanBoogie\HTTP\Dispatcher
  • ICanBoogie\HTTP\Dispatcher\BeforeDispatchEvent
  • ICanBoogie\HTTP\Dispatcher\DispatchEvent
  • ICanBoogie\HTTP\File
  • ICanBoogie\HTTP\FileInfo
  • ICanBoogie\HTTP\FileList
  • ICanBoogie\HTTP\Headers
  • ICanBoogie\HTTP\Headers\CacheControl
  • ICanBoogie\HTTP\Headers\ContentDisposition
  • ICanBoogie\HTTP\Headers\ContentType
  • ICanBoogie\HTTP\Headers\Date
  • ICanBoogie\HTTP\Headers\Header
  • ICanBoogie\HTTP\Headers\HeaderParameter
  • ICanBoogie\HTTP\Helpers
  • ICanBoogie\HTTP\RedirectResponse
  • ICanBoogie\HTTP\Request
  • ICanBoogie\HTTP\Request\Context
  • ICanBoogie\HTTP\Response
  • ICanBoogie\HTTP\Status
  • ICanBoogie\HTTP\WeightedDispatcher

Interfaces

  • ICanBoogie\HTTP\DispatcherInterface
  • ICanBoogie\HTTP\Exception

Exceptions

  • ICanBoogie\HTTP\DispatcherNotDefined
  • ICanBoogie\HTTP\ForceRedirect
  • ICanBoogie\HTTP\MethodNotSupported
  • ICanBoogie\HTTP\NotFound
  • ICanBoogie\HTTP\ServiceUnavailable
  • ICanBoogie\HTTP\StatusCodeNotValid
  1 <?php
  2 
  3 /*
  4  * This file is part of the ICanBoogie package.
  5  *
  6  * (c) Olivier Laviale <olivier.laviale@gmail.com>
  7  *
  8  * For the full copyright and license information, please view the LICENSE
  9  * file that was distributed with this source code.
 10  */
 11 
 12 namespace ICanBoogie\HTTP;
 13 
 14 /**
 15  * HTTP Header field definitions.
 16  *
 17  * Instances of this class are used to collect and manipulate HTTP header field definitions.
 18  * Header field instances are used to handle the definition of complex header fields such as
 19  * `Content-Type` and `Cache-Control`. For instance a {@link Headers\CacheControl} instance
 20  * is used to handle the directives of the `Cache-Control` header field.
 21  *
 22  * @see http://tools.ietf.org/html/rfc2616#section-14
 23  */
 24 class Headers implements \ArrayAccess, \IteratorAggregate
 25 {
 26     static private $mapping = [
 27 
 28         'Cache-Control'       => 'ICanBoogie\HTTP\Headers\CacheControl',
 29         'Content-Disposition' => 'ICanBoogie\HTTP\Headers\ContentDisposition',
 30         'Content-Type'        => 'ICanBoogie\HTTP\Headers\ContentType',
 31         'Date'                => 'ICanBoogie\HTTP\Headers\Date',
 32         'Expires'             => 'ICanBoogie\HTTP\Headers\Date',
 33         'If-Modified-Since'   => 'ICanBoogie\HTTP\Headers\Date',
 34         'If-Unmodified-Since' => 'ICanBoogie\HTTP\Headers\Date',
 35         'Last-Modified'       => 'ICanBoogie\HTTP\Headers\Date'
 36 
 37     ];
 38 
 39     /**
 40      * Normalizes field name.
 41      *
 42      * @param string $name
 43      *
 44      * @return string
 45      */
 46     static private function normalize_field_name($name)
 47     {
 48         return mb_convert_case(strtr(substr($name, 5), '_', '-'), MB_CASE_TITLE);
 49     }
 50 
 51     /**
 52      * Header fields.
 53      *
 54      * @var array
 55      */
 56     protected $fields = [];
 57 
 58     /**
 59      * If the `REQUEST_URI` key is found in the header fields they are considered coming from the
 60      * super global `$_SERVER` array in which case they are filtered to keep only keys
 61      * starting with the `HTTP_` prefix. Also, header field names are normalized. For instance,
 62      * `HTTP_CONTENT_TYPE` becomes `Content-Type`.
 63      *
 64      * @param array $fields The initial headers.
 65      */
 66     public function __construct(array $fields = [])
 67     {
 68         if (isset($fields['REQUEST_URI']))
 69         {
 70             foreach ($fields as $field => $value)
 71             {
 72                 if (strpos($field, 'HTTP_') !== 0)
 73                 {
 74                     continue;
 75                 }
 76 
 77                 $field = self::normalize_field_name($field);
 78 
 79                 $this[$field] = $value;
 80             }
 81         }
 82         else
 83         {
 84             foreach ($fields as $field => $value)
 85             {
 86                 if (strpos($field, 'HTTP_') === 0)
 87                 {
 88                     $field = self::normalize_field_name($field);
 89                 }
 90 
 91                 $this[$field] = $value;
 92             }
 93         }
 94     }
 95 
 96     /**
 97      * Clone instantiated fields.
 98      */
 99     public function __clone()
100     {
101         foreach ($this->fields as &$field)
102         {
103             if (!is_object($field))
104             {
105                 continue;
106             }
107 
108             $field = clone $field;
109         }
110     }
111 
112     /**
113      * Returns the header as a string.
114      *
115      * Header fields with empty string values are discarded.
116      *
117      * @return string
118      */
119     public function __toString()
120     {
121         $header = '';
122 
123         foreach ($this->fields as $field => $value)
124         {
125             $value = (string) $value;
126 
127             if ($value === '')
128             {
129                 continue;
130             }
131 
132             $header .= "$field: $value\r\n";
133         }
134 
135         return $header;
136     }
137 
138     /**
139      * Sends header fields using the {@link header()} function.
140      *
141      * Header fields with empty string values are discarded.
142      */
143     public function __invoke()
144     {
145         foreach ($this->fields as $field => $value)
146         {
147             $value = (string) $value;
148 
149             if ($value === '')
150             {
151                 continue;
152             }
153 
154             $this->send_header($field, $value);
155         }
156     }
157 
158     /**
159      * Send header field.
160      *
161      * Note: The only reason for this method is testing.
162      *
163      * @param string $field
164      * @param string $value
165      */
166     // @codeCoverageIgnoreStart
167     protected function send_header($field, $value)
168     {
169         header("$field: $value");
170     }
171     // @codeCoverageIgnoreEnd
172 
173     /**
174      * Checks if a header field exists.
175      *
176      * @param mixed $field
177      *
178      * @return boolean
179      */
180     public function offsetExists($field)
181     {
182         return isset($this->fields[(string) $field]);
183     }
184 
185     /**
186      * Returns a header.
187      *
188      * @param mixed $field
189      *
190      * @return string|null The header field value or null if it is not defined.
191      */
192     public function offsetGet($field)
193     {
194         if (isset(self::$mapping[$field]))
195         {
196             if (empty($this->fields[$field]))
197             {
198                 $class = self::$mapping[$field];
199                 $this->fields[$field] = call_user_func($class . '::from', null);
200             }
201 
202             return $this->fields[$field];
203         }
204 
205         return $this->offsetExists($field) ? $this->fields[$field] : null;
206     }
207 
208     /**
209      * Sets a header field.
210      *
211      * Note: Setting a header field to `null` removes it, just like unset() would.
212      *
213      * ## Date, Expires, Last-Modified
214      *
215      * The `Date`, `Expires` and `Last-Modified` header fields can be provided as a Unix
216      * timestamp, a string or a {@link \DateTime} object.
217      *
218      * ## Cache-Control, Content-Disposition and Content-Type
219      *
220      * Instances of the {@link Headers\CacheControl}, {@link Headers\ContentDisposition} and
221      * {@link Headers\ContentType} are used to handle the values of the `Cache-Control`,
222      * `Content-Disposition` and `Content-Type` header fields.
223      *
224      * @param string $field The header field to set.
225      * @param mixed $value The value of the header field.
226      */
227     public function offsetSet($field, $value)
228     {
229         if ($value === null)
230         {
231             unset($this[$field]);
232 
233             return;
234         }
235 
236         switch ($field)
237         {
238             # http://tools.ietf.org/html/rfc2616#section-14.25
239             case 'If-Modified-Since':
240             {
241                 #
242                 # Removes the ";length=xxx" string added by Internet Explorer.
243                 # http://stackoverflow.com/questions/12626699/if-modified-since-http-header-passed-by-ie9-includes-length
244                 #
245 
246                 if (is_string($value))
247                 {
248                     $pos = strpos($value, ';');
249 
250                     if ($pos)
251                     {
252                         $value = substr($value, 0, $pos);
253                     }
254                 }
255             }
256             break;
257 
258             # http://tools.ietf.org/html/rfc2616#section-14.37
259             case 'Retry-After':
260             {
261                 $value = is_numeric($value) ? $value : Headers\Date::from($value);
262             }
263             break;
264         }
265 
266         if (isset(self::$mapping[$field]))
267         {
268             $value = call_user_func(self::$mapping[$field] . '::from', $value);
269         }
270 
271         $this->fields[$field] = $value;
272     }
273 
274     /**
275      * Removes a header field.
276      *
277      * @param mixed $field
278      */
279     public function offsetUnset($field)
280     {
281         unset($this->fields[$field]);
282     }
283 
284     /**
285      * Returns an iterator for the header fields.
286      */
287     public function getIterator()
288     {
289         return new \ArrayIterator($this->fields);
290     }
291 }
292 
ICanBoogie/HTTP v2.4.0 API documentation generated by ApiGen