Autodoc
  • Namespace
  • Class
  • Tree

Namespaces

  • BlueTihi
    • Context
  • Brickrouge
    • Element
      • Nodes
    • Renderer
    • Widget
  • ICanBoogie
    • ActiveRecord
    • AutoConfig
    • CLDR
    • Composer
    • Core
    • Event
    • Exception
    • HTTP
      • Dispatcher
      • Request
    • I18n
      • Translator
    • Mailer
    • Modules
      • Taxonomy
        • Support
      • Thumbnailer
        • Versions
    • Object
    • Operation
      • Dispatcher
    • Prototype
    • Routes
    • Routing
      • Dispatcher
    • Session
  • Icybee
    • ActiveRecord
      • Model
    • ConfigOperation
    • Document
    • EditBlock
    • Element
      • ActionbarContextual
      • ActionbarSearch
      • ActionbarToolbar
    • FormBlock
    • Installer
    • ManageBlock
    • Modules
      • Articles
      • Cache
        • Collection
        • ManageBlock
      • Comments
        • ManageBlock
      • Contents
        • ManageBlock
      • Dashboard
      • Editor
        • Collection
      • Files
        • File
        • ManageBlock
      • Forms
        • Form
        • ManageBlock
      • I18n
      • Images
        • ManageBlock
      • Members
      • Modules
        • ManageBlock
      • Nodes
        • ManageBlock
        • Module
      • Pages
        • BreadcrumbElement
        • LanguagesElement
        • ManageBlock
        • NavigationBranchElement
        • NavigationElement
        • Page
        • PageController
      • Registry
      • Search
      • Seo
      • Sites
        • ManageBlock
      • Taxonomy
        • Terms
          • ManageBlock
        • Vocabulary
          • ManageBlock
      • Users
        • ManageBlock
        • NonceLogin
        • Roles
      • Views
        • ActiveRecordProvider
        • Collection
        • View
    • Operation
      • ActiveRecord
      • Constructor
      • Module
      • Widget
    • Rendering
  • None
  • Patron
  • PHP

Classes

  • AlterEvent
  • BeforeDispatchEvent
  • DispatchEvent
  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  * Dispatches requests.
 16  *
 17  * Events:
 18  *
 19  * - `ICanBoogie\HTTP\Dispatcher::dispatch:before`: {@link Dispatcher\BeforeDispatchEvent}.
 20  * - `ICanBoogie\HTTP\Dispatcher::dispatch`: {@link Dispatcher\DispatchEvent}.
 21  * - `ICanBoogie\HTTP\Dispatcher::rescue`: {@link ICanBoogie\Exception\RescueEvent}.
 22  */
 23 class Dispatcher implements \ArrayAccess, \IteratorAggregate, IDispatcher
 24 {
 25     /**
 26      * The dispatchers called during the dispatching of the request.
 27      *
 28      * @var array[string]callable|string
 29      */
 30     protected $dispatchers = array();
 31 
 32     /**
 33      * The weights of the dispatchers.
 34      *
 35      * @var array[string]mixed
 36      */
 37     protected $dispatchers_weight = array();
 38 
 39     protected $dispatchers_order;
 40 
 41     /**
 42      * Initialiazes the {@link $dispatchers} property.
 43      *
 44      * Dispatchers can be defined as callable or class name. If a dispatcher definition is not a
 45      * callable it is used as class name to instantiate a dispatcher.
 46      */
 47     public function __construct(array $dispatchers=array())
 48     {
 49         foreach ($dispatchers as $dispatcher_id => $dispatcher)
 50         {
 51             $this[$dispatcher_id] = $dispatcher;
 52         }
 53     }
 54 
 55     /**
 56      * Dispatches the request to retrieve a {@link Response}.
 57      *
 58      * The request is dispatched by the {@link dispatch()} method. If an exception is thrown
 59      * during the dispatch the {@link rescue()} method is used to rescue the exception and
 60      * retrieve a {@link Response}.
 61      *
 62      * ## HEAD requests
 63      *
 64      * If a {@link NotFound} exception is caught during the dispatching of a request with a
 65      * {@link Request::METHOD_HEAD} method the following happens:
 66      *
 67      * 1. The request is cloned and the method of the cloned request is changed to
 68      * {@link Request::METHOD_GET}.
 69      * 2. The cloned method is dispatched.
 70      * 3. If the result is *not* a {@link Response} instance, the result is returned.
 71      * 4. Otherwise, a new {@link Response} instance is created with a `null` body, but the status
 72      * code and headers of the original response.
 73      * 5. The new response is returned.
 74      *
 75      * @param Request $request
 76      *
 77      * @return Response
 78      */
 79     public function __invoke(Request $request)
 80     {
 81         try
 82         {
 83             return $this->dispatch($request);
 84         }
 85         catch (\Exception $e)
 86         {
 87             if ($request->method === Request::METHOD_HEAD && $e instanceof NotFound)
 88             {
 89                 $get_request = clone $request;
 90                 $get_request->method = Request::METHOD_GET;
 91 
 92                 $response = $this($get_request);
 93 
 94                 if (!($response instanceof Response))
 95                 {
 96                     return $response;
 97                 }
 98 
 99                 return new Response(null, $response->status, $response->headers);
100             }
101 
102             return $this->rescue($e, $request);
103         }
104     }
105 
106     /**
107      * Checks if the dispatcher is defined.
108      *
109      * @param string $dispatcher_id The identifier of the dispatcher.
110      *
111      * @return `true` if the dispatcher is defined, `false` otherwise.
112      */
113     public function offsetExists($dispatcher_id)
114     {
115         return isset($this->dispatchers[$dispatcher_id]);
116     }
117 
118     /**
119      * Returns a dispatcher.
120      *
121      * @param string $dispatcher_id The identifier of the dispatcher.
122      */
123     public function offsetGet($dispatcher_id)
124     {
125         if (!$this->offsetExists($dispatcher_id))
126         {
127             throw new DispatcherNotDefined($dispatcher_id);
128         }
129 
130         return $this->dispatchers[$dispatcher_id];
131     }
132 
133     /**
134      * Defines a dispatcher.
135      *
136      * @param string $dispatcher_id The identifier of the dispatcher.
137      * @param mixed $dispatcher The dispatcher class or callback.
138      */
139     public function offsetSet($dispatcher_id, $dispatcher)
140     {
141         $weight = 0;
142 
143         if ($dispatcher instanceof WeightedDispatcher)
144         {
145             $weight = $dispatcher->weight;
146             $dispatcher = $dispatcher->dispatcher;
147         }
148 
149         $this->dispatchers[$dispatcher_id] = $dispatcher;
150         $this->dispatchers_weight[$dispatcher_id] = $weight;
151         $this->dispatchers_order = null;
152     }
153 
154     /**
155      * Removes a dispatcher.
156      *
157      * @param string $dispatcher_id The identifier of the dispatcher.
158      */
159     public function offsetUnset($dispatcher_id)
160     {
161         unset($this->dispatchers[$dispatcher_id]);
162     }
163 
164     public function getIterator()
165     {
166         if (!$this->dispatchers_order)
167         {
168             $weights = $this->dispatchers_weight;
169 
170             $this->dispatchers_order = \ICanBoogie\sort_by_weight($this->dispatchers, function($v, $k) use($weights) {
171 
172                 return $weights[$k];
173 
174             });
175         }
176 
177         return new \ArrayIterator($this->dispatchers_order);
178     }
179 
180     /**
181      * Dispatches a request using the defined dispatchers.
182      *
183      * The method iterates over the defined dispatchers until one of them returns a
184      * {@link Response} instance. If an exception is throw during the dispatcher execution and
185      * the dispatcher implements the {@link IDispatcher} interface then its
186      * {@link IDispatcher::rescue} method is invoked to rescue the exception, otherwise the
187      * exception is just rethrown.
188      *
189      * {@link Dispatcher\BeforeDispatchEvent} is fired before dispatchers are traversed. If a
190      * response is provided the dispatchers are skipped.
191      *
192      * {@link Dispatcher\DispatchEvent} is fired before the response is returned. The event is
193      * fired event if the dispatchers didn't return a response. It's the last chance to get one.
194      *
195      * @param Request $request
196      *
197      * @return Response
198      *
199      * @throws NotFound when neither the events nor the dispatchers were able to provide
200      * a {@link Response}.
201      */
202     protected function dispatch(Request $request)
203     {
204         $response = null;
205 
206         new Dispatcher\BeforeDispatchEvent($this, $request, $response);
207 
208         if (!$response)
209         {
210             foreach ($this as $id => &$dispatcher) // MOVE some to AGGREGATE
211             {
212                 #
213                 # If the dispatcher is not a callable then it is considered as a class name, which
214                 # is used to instantiate a dispatcher.
215                 #
216 
217                 if (!($dispatcher instanceof CallableDispatcher))
218                 {
219                     $dispatcher = is_callable($dispatcher) ? new CallableDispatcher($dispatcher) : new $dispatcher;
220                 }
221 
222                 try
223                 {
224                     $request->context->dispatcher = $dispatcher;
225 
226                     $response = call_user_func($dispatcher, $request);
227                 }
228                 catch (\Exception $e)
229                 {
230                     if (!($dispatcher instanceof IDispatcher))
231                     {
232                         throw $e;
233                     }
234 
235                     $response = $dispatcher->rescue($e, $request);
236                 }
237 
238                 if ($response) break;
239 
240                 $request->context->dispatcher = null;
241             }
242         }
243 
244         new Dispatcher\DispatchEvent($this, $request, $response);
245 
246         if (!$response)
247         {
248             throw new NotFound;
249         }
250 
251         return $response;
252     }
253 
254     /**
255      * Tries to get a {@link Response} object from an exception.
256      *
257      * {@link \ICanBoogie\Exception\RescueEvent} is fired with the exception as target.
258      * The response provided by one of the event hooks is returned. If there is no response the
259      * exception is thrown again.
260      *
261      * If a response is finaly obtained, the `X-ICanBoogie-Rescued-Exception` header is added to
262      * indicate where the exception was thrown from.
263      *
264      * @param \Exception $exception The exception to rescue.
265      * @param Request $request The current request.
266      *
267      * @return Response
268      *
269      * @throws \Exception The exception is rethrown if it could not be rescued.
270      */
271     public function rescue(\Exception $exception, Request $request)
272     {
273         $response = null;
274 
275         new \ICanBoogie\Exception\RescueEvent($exception, $request, $response);
276 
277         if (!$response)
278         {
279             if ($exception instanceof ForceRedirect)
280             {
281                 return new RedirectResponse($exception->location, $exception->getCode());
282             }
283 
284             throw $exception;
285         }
286 
287         $pathname = $exception->getFile();
288         $root = $_SERVER['DOCUMENT_ROOT'];
289 
290         if ($root && strpos($pathname, $root) === 0)
291         {
292             $pathname = substr($pathname, strlen($root));
293         }
294 
295         $response->headers['X-ICanBoogie-Rescued-Exception'] = $pathname . '@' . $exception->getLine();
296 
297         return $response;
298     }
299 }
300 
301 /**
302  * Dispatcher interface.
303  */
304 interface IDispatcher
305 {
306     /**
307      * Process the request.
308      *
309      * @param Request $request
310      *
311      * @return Response A response to the tequest.
312      */
313     public function __invoke(Request $request);
314 
315     /**
316      * Rescues the exception that was thrown during the request process.
317      *
318      * @param \Exception $exception
319      *
320      * @return Response A response to the request exception.
321      *
322      * @throws \Exception when the request exception cannot be rescued.
323      */
324     public function rescue(\Exception $exception, Request $request);
325 }
326 
327 /**
328  * Wrapper for callable dispatchers.
329  */
330 class CallableDispatcher implements IDispatcher
331 {
332     private $callable;
333 
334     public function __construct($callable)
335     {
336         $this->callable = $callable;
337     }
338 
339     public function __invoke(Request $request)
340     {
341         return call_user_func($this->callable, $request);
342     }
343 
344     public function rescue(\Exception $exception, Request $request)
345     {
346         throw $exception;
347     }
348 }
349 
350 /**
351  * Used to defined a dispatcher and its weight.
352  *
353  * <pre>
354  * <?php
355  *
356  * $dispatcher['my'] = new WeightedDispatcher('callback', 'before:that_other_dispatcher');
357  * </pre>
358  */
359 class WeightedDispatcher
360 {
361     public $dispatcher;
362 
363     public $weight;
364 
365     public function __construct($dispatcher, $weight)
366     {
367         $this->dispatcher = $dispatcher;
368         $this->weight = $weight;
369     }
370 }
371 
372 /*
373  * Events
374  */
375 
376 namespace ICanBoogie\HTTP\Dispatcher;
377 
378 use ICanBoogie\HTTP\Dispatcher;
379 use ICanBoogie\HTTP\Response;
380 use ICanBoogie\HTTP\Request;
381 
382 /**
383  * Event class for the `ICanBoogie\HTTP\Dispatcher::dispatch:before` event.
384  *
385  * Third parties may use this event to provide a response to the request before the dispatchers
386  * are invoked. The event is usually used by third parties to redirect requests or provide cached
387  * responses.
388  */
389 class BeforeDispatchEvent extends \ICanBoogie\Event
390 {
391     /**
392      * The HTTP request.
393      *
394      * @var Request
395      */
396     public $request;
397 
398     /**
399      * Reference to the HTTP response.
400      *
401      * @var Response
402      */
403     public $response;
404 
405     /**
406      * The event is constructed with the type `dispatch:before`.
407      *
408      * @param Dispatcher $target
409      * @param array $payload
410      */
411     public function __construct(Dispatcher $target, Request $request, &$response)
412     {
413         if ($response !== null && !($response instanceof Response))
414         {
415             throw new \InvalidArgumentException('$response must be an instance of ICanBoogie\HTTP\Response. Given: ' . get_class($response) . '.');
416         }
417 
418         $this->request = $request;
419         $this->response = &$response;
420 
421         parent::__construct($target, 'dispatch:before');
422     }
423 }
424 
425 /**
426  * Event class for the `ICanBoogie\HTTP\Dispatcher::dispatch` event.
427  *
428  * Third parties may use this event to alter the response before it is returned by the dispatcher.
429  */
430 class DispatchEvent extends \ICanBoogie\Event
431 {
432     /**
433      * The request.
434      *
435      * @var Request
436      */
437     public $request;
438 
439     /**
440      * Reference to the response.
441      *
442      * @var Response
443      */
444     public $response;
445 
446     /**
447      * The event is constructed with the type `dispatch`.
448      *
449      * @param Dispatcher $target
450      * @param array $payload
451      */
452     public function __construct(Dispatcher $target, Request $request, &$response)
453     {
454         $this->request = $request;
455         $this->response = &$response;
456 
457         parent::__construct($target, 'dispatch');
458     }
459 }
460 
461 namespace ICanBoogie\Exception;
462 
463 use ICanBoogie\HTTP\Request;
464 use ICanBoogie\HTTP\Response;
465 
466 /**
467  * Event class for the `Exception:rescue` event type.
468  *
469  * Third parties may use this event to provide a response for the exception.
470  */
471 class RescueEvent extends \ICanBoogie\Event
472 {
473     /**
474      * Reference to the response.
475      *
476      * @var Response
477      */
478     public $response;
479 
480     /**
481      * Reference tot the exception.
482      *
483      * @var \Exception
484      */
485     public $exception;
486 
487     /**
488      * The request.
489      *
490      * @var Request
491      */
492     public $request;
493 
494     /**
495      * The event is constructed with the type `rescue`.
496      *
497      * @param \Exception $target
498      * @param array $payload
499      */
500     public function __construct(\Exception &$target, Request $request, &$response)
501     {
502         if ($response !== null && !($response instanceof Response))
503         {
504             throw new \InvalidArgumentException('$response must be an instance of ICanBoogie\HTTP\Response. Given: ' . (is_object($response) ? get_class($response) : gettype($response)) . '.');
505         }
506 
507         $this->response = &$response;
508         $this->exception = &$target;
509         $this->request = $request;
510 
511         parent::__construct($target, 'rescue');
512     }
513 }
Autodoc API documentation generated by ApiGen 2.8.0