source: sandbox/2.4.1-3/prototype/library/tonic/lib/tonic.php @ 6523

Revision 6523, 28.1 KB checked in by acoutinho, 12 years ago (diff)

Ticket #2766 - Melhorias e correcoes na api rest, criacao de novo cliente

  • Property svn:executable set to *
Line 
1<?php
2
3use prototype\api\Config as Config;
4
5/*
6 * This file is part of the Tonic.
7 * (c) Paul James <paul@peej.co.uk>
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13/* Turn on namespace in PHP5.3 if you have namespace collisions from the Tonic classnames
14namespace Tonic;
15use \ReflectionClass as ReflectionClass;
16use \ReflectionMethod as ReflectionMethod;
17use \Exception as Exception;
18//*/
19
20/**
21 * Model the data of the incoming HTTP request
22 * @namespace Tonic\Lib
23 */
24class Request {
25   
26    /**
27     * The requested URI
28     * @var str
29     */
30    public $uri;
31   
32    /**
33     * The URI where the front controller is positioned in the server URI-space
34     * @var str
35     */
36    public $baseUri = '';
37   
38    /**
39     * Array of possible URIs based upon accept and accept-language request headers in order of preference
40     * @var str[]
41     */
42    public $negotiatedUris = array();
43   
44    /**
45     * Array of possible URIs based upon accept request headers in order of preference
46     * @var str[]
47     */
48    public $formatNegotiatedUris = array();
49   
50    /**
51     * Array of possible URIs based upon accept-language request headers in order of preference
52     * @var str[]
53     */
54    public $languageNegotiatedUris = array();
55   
56    /**
57     * Array of accept headers in order of preference
58     * @var str[][]
59     */
60    public $accept = array();
61   
62    /**
63     * Array of accept-language headers in order of preference
64     * @var str[][]
65     */
66    public $acceptLang = array();
67   
68    /**
69     * Array of accept-encoding headers in order of preference
70     * @var str[]
71     */
72    public $acceptEncoding = array();
73   
74    /**
75     * Map of file/URI extensions to mimetypes
76     * @var str[]
77     */
78    public $mimetypes = array(
79        'html' => 'text/html',
80        'txt' => 'text/plain',
81        'php' => 'application/php',
82        'css' => 'text/css',
83        'js' => 'application/javascript',
84        'json' => 'application/json',
85        'xml' => 'application/xml',
86        'rss' => 'application/rss+xml',
87        'atom' => 'application/atom+xml',
88        'gz' => 'application/x-gzip',
89        'tar' => 'application/x-tar',
90        'zip' => 'application/zip',
91        'gif' => 'image/gif',
92        'png' => 'image/png',
93        'jpg' => 'image/jpeg',
94        'ico' => 'image/x-icon',
95        'swf' => 'application/x-shockwave-flash',
96        'flv' => 'video/x-flv',
97        'avi' => 'video/mpeg',
98        'mpeg' => 'video/mpeg',
99        'mpg' => 'video/mpeg',
100        'mov' => 'video/quicktime',
101        'mp3' => 'audio/mpeg'
102    );
103   
104    /**
105     * Supported HTTP methods
106     * @var str[]
107     */
108    public $HTTPMethods = array(
109        'GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'
110    );
111   
112    /**
113     * Allowed resource methods
114     * @var str[]
115     */
116    public $allowedMethods = array();
117   
118    /**
119     * HTTP request method of incoming request
120     * @var str
121     */
122    public $method = 'GET';
123   
124    /**
125     * Body data of incoming request
126     * @var str
127     */
128    public $data;
129   
130    /**
131     * Body data of incoming request
132     * @var array
133     */
134    public $dataDecoded;
135   
136    /**
137     * Array of if-match etags
138     * @var str[]
139     */
140    public $ifMatch = array();
141   
142    /**
143     * Array of if-none-match etags
144     * @var str[]
145     */
146    public $ifNoneMatch = array();
147   
148    /**
149     * The resource classes loaded and how they are wired to URIs
150     * @var str[]
151     */
152    public $resources = array();
153   
154    /**
155     * A list of URL to namespace/package mappings for routing requests to a
156     * group of resources that are wired into a different URL-space
157     * @var str[]
158     */
159    public $mounts = array();
160   
161    /**
162     * Set a default configuration option
163     */
164    protected function getConfig($config, $configVar, $serverVar = NULL, $default = NULL) {
165        if (isset($config[$configVar])) {
166            return $config[$configVar];
167        } elseif (isset($_SERVER[$serverVar]) && $_SERVER[$serverVar] != '') {
168            return $_SERVER[$serverVar];
169        } else {
170            return $default;
171        }
172    }
173   
174    /**
175     * Create a request object using the given configuration options.
176     *
177     * The configuration options array can contain the following:
178     *
179     * <dl>
180     * <dt>uri</dt> <dd>The URI of the request</dd>
181     * <dt>method</dt> <dd>The HTTP method of the request</dd>
182     * <dt>data</dt> <dd>The body data of the request</dd>
183     * <dt>accept</dt> <dd>An accept header</dd>
184     * <dt>acceptLang</dt> <dd>An accept-language header</dd>
185     * <dt>acceptEncoding</dt> <dd>An accept-encoding header</dd>
186     * <dt>ifMatch</dt> <dd>An if-match header</dd>
187     * <dt>ifNoneMatch</dt> <dd>An if-none-match header</dd>
188     * <dt>mimetypes</dt> <dd>A map of file/URI extenstions to mimetypes, these
189     * will be added to the default map of mimetypes</dd>
190     * <dt>baseUri</dt> <dd>The base relative URI to use when dispatcher isn't
191     * at the root of the domain. Do not put a trailing slash</dd>
192     * <dt>mounts</dt> <dd>an array of namespace to baseUri prefix mappings</dd>
193     * <dt>HTTPMethods</dt> <dd>an array of HTTP methods to support</dd>
194     * </dl>
195     *
196     * @param mixed[] config Configuration options
197     */
198    function __construct($config = array()) {
199       
200        // set defaults
201        $config['uri'] = parse_url($this->getConfig($config, 'uri', 'REQUEST_URI'), PHP_URL_PATH);
202        $config['baseUri'] = $this->getConfig($config, 'baseUri', '');
203        $config['accept'] = $this->getConfig($config, 'accept', 'HTTP_ACCEPT');
204        $config['acceptLang'] = $this->getConfig($config, 'acceptLang', 'HTTP_ACCEPT_LANGUAGE');
205        $config['acceptEncoding'] = $this->getConfig($config, 'acceptEncoding', 'HTTP_ACCEPT_ENCODING');
206        $config['ifMatch'] = $this->getConfig($config, 'ifMatch', 'HTTP_IF_MATCH');
207        $config['ifNoneMatch'] = $this->getConfig($config, 'ifNoneMatch', 'HTTP_IF_NONE_MATCH');
208       
209        if (isset($config['mimetypes']) && is_array($config['mimetypes'])) {
210            foreach ($config['mimetypes'] as $ext => $mimetype) {
211                $this->mimetypes[$ext] = $mimetype;
212            }
213        }
214       
215        // set baseUri
216        $this->baseUri = $config['baseUri'];
217       
218        // get request URI
219        $parts = explode('/', $config['uri']);
220        $lastPart = array_pop($parts);
221        $this->uri = join('/', $parts);
222       
223        $parts = explode('.', $lastPart);
224        $this->uri .= '/'.$parts[0];
225       
226        if ($this->uri != '/' && substr($this->uri, -1, 1) == '/') { // remove trailing slash problem
227            $this->uri = substr($this->uri, 0, -1);
228        }
229       
230        array_shift($parts);
231        foreach ($parts as $part) {
232            $this->accept[10][] = $part;
233            $this->acceptLang[10][] = $part;
234        }
235       
236        // sort accept headers
237        $accept = explode(',', strtolower($config['accept']));
238        foreach ($accept as $mimetype) {
239            $parts = explode(';q=', $mimetype);
240            if (isset($parts) && isset($parts[1]) && $parts[1]) {
241                $num = $parts[1] * 10;
242            } else {
243                $num = 10;
244            }
245            $key = array_search($parts[0], $this->mimetypes);
246            if ($key) {
247                $this->accept[$num][] = $key;
248            }
249        }
250        krsort($this->accept);
251       
252        // sort lang accept headers
253        $accept = explode(',', strtolower($config['acceptLang']));
254        foreach ($accept as $mimetype) {
255            $parts = explode(';q=', $mimetype);
256            if (isset($parts) && isset($parts[1]) && $parts[1]) {
257                $num = $parts[1] * 10;
258            } else {
259                $num = 10;
260            }
261            $this->acceptLang[$num][] = $parts[0];
262        }
263        krsort($this->acceptLang);
264       
265        // get encoding accept headers
266        if ($config['acceptEncoding']) {
267            foreach (explode(',', $config['acceptEncoding']) as $key => $accept) {
268                $this->acceptEncoding[$key] = trim($accept);
269            }
270        }
271       
272        // create negotiated URI lists from accept headers and request URI
273        foreach ($this->accept as $typeOrder) {
274            foreach ($typeOrder as $type) {
275                if ($type) {
276                    foreach ($this->acceptLang as $langOrder) {
277                        foreach ($langOrder as $lang) {
278                            if ($lang && $lang != $type) {
279                                $this->negotiatedUris[] = $this->uri.'.'.$type.'.'.$lang;
280                            }
281                        }
282                    }
283                    $this->negotiatedUris[] = $this->uri.'.'.$type;
284                    $this->formatNegotiatedUris[] = $this->uri.'.'.$type;
285                }
286            }
287        }
288        foreach ($this->acceptLang as $langOrder) {
289            foreach ($langOrder as $lang) {
290                if ($lang) {
291                    $this->negotiatedUris[] = $this->uri.'.'.$lang;
292                    $this->languageNegotiatedUris[] = $this->uri.'.'.$lang;
293                }
294            }
295        }
296        $this->negotiatedUris[] = $this->uri;
297        $this->formatNegotiatedUris[] = $this->uri;
298        $this->languageNegotiatedUris[] = $this->uri;
299       
300        $this->negotiatedUris = array_values(array_unique($this->negotiatedUris));
301        $this->formatNegotiatedUris = array_values(array_unique($this->formatNegotiatedUris));
302        $this->languageNegotiatedUris = array_values(array_unique($this->languageNegotiatedUris));
303       
304        $this->HTTPMethods = $this->getConfig($config, 'HTTPMethods', NULL, $this->HTTPMethods);
305       
306        // get HTTP method
307        $this->method = strtoupper($this->getConfig($config, 'method', 'REQUEST_METHOD', $this->method));
308       
309        // get HTTP request data
310        $this->data = $this->getConfig($config, 'data', NULL, file_get_contents("php://input"));
311
312        // get HTTP request dataDecode into array
313        $this->dataDecoded  = $this->__decodeData( $this->data );
314       
315        // conditional requests
316        if ($config['ifMatch']) {
317            $ifMatch = explode(',', $config['ifMatch']);
318            foreach ($ifMatch as $etag) {
319                $this->ifMatch[] = trim($etag, '" ');
320            }
321        }
322        if ($config['ifNoneMatch']) {
323            $ifNoneMatch = explode(',', $config['ifNoneMatch']);
324            foreach ($ifNoneMatch as $etag) {
325                $this->ifNoneMatch[] = trim($etag, '" ');
326            }
327        }
328       
329        // mounts
330        if (isset($config['mount']) && is_array($config['mount'])) {
331            $this->mounts = $config['mount'];
332        }
333       
334        // prime named resources for autoloading
335        if (isset($config['autoload']) && is_array($config['autoload'])) {
336            foreach ($config['autoload'] as $uri => $className) {
337                $this->resources[$uri] = array(
338                    'class' => $className,
339                    'loaded' => FALSE
340                );
341            }
342        }
343       
344        // load definitions of already loaded resource classes
345        $resourceClassName = class_exists('Tonic\\Resource', false) ? 'Tonic\\Resource' : 'Resource';
346        foreach (get_declared_classes() as $className) {
347            if (is_subclass_of($className, $resourceClassName)) {
348               
349                $resourceDetails = $this->getResourceClassDetails($className);
350               
351                preg_match_all('/@uri\s+([^\s]+)(?:\s([0-9]+))?/', $resourceDetails['comment'], $annotations);
352                if (isset($annotations[1]) && $annotations[1]) {
353                    $uris = $annotations[1];
354                } else {
355                    $uris = array();
356                }
357               
358                foreach ($uris as $index => $uri) {
359                    if ($uri != '/' && substr($uri, -1, 1) == '/') { // remove trailing slash problem
360                        $uri = substr($uri, 0, -1);
361                    }
362                    if (isset($annotations[2][$index]) && is_numeric($annotations[2][$index])) {
363                        $priority = intval($annotations[2][$index]);
364                    } else {
365                        $priority = 0;
366                    }
367                    if (
368                        !isset($this->resources[$resourceDetails['mountPoint'].$uri]) ||
369                        $this->resources[$resourceDetails['mountPoint'].$uri]['priority'] < $priority
370                    ) {
371                        $this->resources[$resourceDetails['mountPoint'].$uri] = array(
372                            'namespace' => $resourceDetails['namespaceName'],
373                            'class' => $resourceDetails['className'],
374                            'filename' => $resourceDetails['filename'],
375                            'line' => $resourceDetails['line'],
376                            'priority' => $priority,
377                            'loaded' => TRUE
378                        );
379                    }
380                }
381            }
382        }
383       
384    }
385   
386    /**
387     * Get the details of a Resource class by reflection
388     * @param str className
389     * @return str[]
390     */
391    protected function getResourceClassDetails($className) {
392       
393        $resourceReflector = new ReflectionClass($className);
394        $comment = $resourceReflector->getDocComment();
395       
396        $className = $resourceReflector->getName();
397        if (method_exists($resourceReflector, 'getNamespaceName')) {
398            $namespaceName = $resourceReflector->getNamespaceName();
399        } else {
400            // @codeCoverageIgnoreStart
401            $namespaceName = FALSE;
402            // @codeCoverageIgnoreEnd
403        }
404       
405        if (!$namespaceName) {
406            preg_match('/@(?:package|namespace)\s+([^\s]+)/', $comment, $package);
407            if (isset($package[1])) {
408                $namespaceName = $package[1];
409            }
410        }
411       
412        // adjust URI for mountpoint
413        if (isset($this->mounts[$namespaceName])) {
414            $mountPoint = $this->mounts[$namespaceName];
415        } else {
416            $mountPoint = '';
417        }
418       
419        return array(
420            'comment' => $comment,
421            'className' => $className,
422            'namespaceName' => $namespaceName,
423            'filename' => $resourceReflector->getFileName(),
424            'line' => $resourceReflector->getStartLine(),
425            'mountPoint' => $mountPoint
426        );
427   
428    }
429
430     /**
431     * Convert the string data into a array data
432     * @return array
433     * @codeCoverageIgnore
434     */
435    function __decodeData($data) {
436        $arrayData = array();
437        if($data){
438            $arrayIndex = explode('&', urldecode($data));
439            foreach($arrayIndex as $key => $value){
440                list($i,$val) = explode ('=', $value);
441                $arrayData[$i] = $val;
442            }
443        }
444        return $arrayData;
445    }
446
447    /**
448     * Convert the object into a string suitable for printing
449     * @return str
450     * @codeCoverageIgnore
451     */
452    function __toString() {
453        $str = 'URI: '.$this->uri."\n";
454        $str .= 'Method: '.$this->method."\n";
455        if ($this->data) {
456            $str .= 'Data: '.$this->data."\n";
457        }
458        $str .= 'Acceptable Formats:';
459        foreach ($this->accept as $accept) {
460            foreach ($accept as $a) {
461                $str .= ' .'.$a;
462                if (isset($this->mimetypes[$a])) $str .= ' ('.$this->mimetypes[$a].')';
463            }
464        }
465        $str .= "\n";
466        $str .= 'Acceptable Languages:';
467        foreach ($this->acceptLang as $accept) {
468            foreach ($accept as $a) {
469                $str .= ' '.$a;
470            }
471        }
472        $str .= "\n";
473        $str .= 'Negotated URIs:'."\n";
474        foreach ($this->negotiatedUris as $uri) {
475            $str .= "\t".$uri."\n";
476        }
477        $str .= 'Format Negotated URIs:'."\n";
478        foreach ($this->formatNegotiatedUris as $uri) {
479            $str .= "\t".$uri."\n";
480        }
481        $str .= 'Language Negotated URIs:'."\n";
482        foreach ($this->languageNegotiatedUris as $uri) {
483            $str .= "\t".$uri."\n";
484        }
485        if ($this->ifMatch) {
486            $str .= 'If Match:';
487            foreach ($this->ifMatch as $etag) {
488                $str .= ' '.$etag;
489            }
490            $str .= "\n";
491        }
492        if ($this->ifNoneMatch) {
493            $str .= 'If None Match:';
494            foreach ($this->ifNoneMatch as $etag) {
495                $str .= ' '.$etag;
496            }
497            $str .= "\n";
498        }
499        $str .= 'Loaded Resources:'."\n";
500        foreach ($this->resources as $uri => $resource) {
501            $str .= "\t".$uri."\n";
502            if (isset($resource['namespace']) && $resource['namespace']) $str .= "\t\tNamespace: ".$resource['namespace']."\n";
503            $str .= "\t\tClass: ".$resource['class']."\n";
504            $str .= "\t\tFile: ".$resource['filename'];
505            if (isset($resource['line']) && $resource['line']) $str .= '#'.$resource['line'];
506            $str .= "\n";
507        }
508        return $str;
509    }
510   
511    /**
512     * Instantiate the resource class that matches the request URI the best
513     * @return Resource
514     * @throws ResponseException If the resource does not exist, a 404 exception is thrown
515     */
516    function loadResource() {
517       
518        $uriMatches = array();
519        foreach ($this->resources as $uri => $resource) {
520           
521            preg_match_all('#((?<!\?):[^/]+|{[^0-9][^}]*}|\(.+?\))#', $uri, $params, PREG_PATTERN_ORDER);
522           
523            $uri = $this->baseUri.$uri;
524            if ($uri != '/' && substr($uri, -1, 1) == '/') { // remove trailing slash problem
525                $uri = substr($uri, 0, -1);
526            }
527            $uriRegex = preg_replace('#((?<!\?):[^(/]+|{[^0-9][^}]*})#', '(.+)', $uri);
528           
529            if (preg_match('#^'.$uriRegex.'$#', $this->uri, $matches)) {
530                array_shift($matches);
531               
532                if (isset($params[1])) {
533                    foreach ($params[1] as $index => $param) {
534                        if (isset($matches[$index])) {
535                            if (substr($param, 0, 1) == ':') {
536                                $matches[substr($param, 1)] = $matches[$index];
537                                unset($matches[$index]);
538                            } elseif (substr($param, 0, 1) == '{' && substr($param, -1, 1) == '}') {
539                                $matches[substr($param, 1, -1)] = $matches[$index];
540                                unset($matches[$index]);
541                            }
542                        }
543                    }
544                }
545               
546                $uriMatches[isset($resource['priority']) ? $resource['priority'] : 0] = array(
547                    $uri,
548                    $resource,
549                    $matches
550                );
551               
552            }
553        }
554        krsort($uriMatches);
555       
556        if ($uriMatches) {
557            list($uri, $resource, $parameters) = array_shift($uriMatches);
558            if (!$resource['loaded']) { // autoload
559                if (!class_exists($resource['class'])) {
560                    throw new Exception('Unable to load resource' . $resource['class']);
561                }
562                $resourceDetails = $this->getResourceClassDetails($resource['class']);
563                $resource = $this->resources[$uri] = array(
564                    'namespace' => $resourceDetails['namespaceName'],
565                    'class' => $resourceDetails['className'],
566                    'filename' => $resourceDetails['filename'],
567                    'line' => $resourceDetails['line'],
568                    'priority' => 0,
569                    'loaded' => TRUE
570                );
571            }
572           
573            $this->allowedMethods = array_intersect(array_map('strtoupper', get_class_methods($resource['class'])), $this->HTTPMethods);
574           
575            return new $resource['class']($parameters);
576        }
577       
578        // no resource found, throw response exception
579        throw new ResponseException('A resource matching URI "'.$this->uri.'" was not found', Response::NOTFOUND);
580       
581    }
582   
583    /**
584     * Check if an etag matches the requests if-match header
585     * @param str etag Etag to match
586     * @return bool
587     */
588    function ifMatch($etag) {
589        if (isset($this->ifMatch[0]) && $this->ifMatch[0] == '*') {
590            return TRUE;
591        }
592        return in_array($etag, $this->ifMatch);
593    }
594   
595    /**
596     * Check if an etag matches the requests if-none-match header
597     * @param str etag Etag to match
598     * @return bool
599     */
600    function ifNoneMatch($etag) {
601        if (isset($this->ifMatch[0]) && $this->ifMatch[0] == '*') {
602            return FALSE;
603        }
604        return in_array($etag, $this->ifNoneMatch);
605    }
606   
607    /**
608     * Return the most acceptable of the given formats based on the accept array
609     * @param str[] formats
610     * @param str default The default format if the requested format does not match $formats
611     * @return str
612     */
613    function mostAcceptable($formats, $default = NULL) {
614        foreach (call_user_func_array('array_merge', $this->accept) as $format) {
615            if (in_array($format, $formats)) {
616                return $format;
617            }
618        }
619        return $default;
620    }
621   
622}
623
624/**
625 * Base resource class
626 * @namespace Tonic\Lib
627 */
628class Resource {
629   
630    private $parameters;
631   
632    /**
633     * Resource constructor
634     * @param str[] parameters Parameters passed in from the URL as matched from the URI regex
635     */
636    function  __construct($parameters) {
637        $this->parameters = $parameters;
638    }
639   
640    /**
641     * Convert the object into a string suitable for printing
642     * @return str
643     * @codeCoverageIgnore
644     */
645    function __toString() {
646        $str = get_class($this);
647        foreach ($this->parameters as $name => $value) {
648            $str .= "\n".$name.': '.$value;
649        }
650        return $str;
651    }
652   
653    function secured()
654    { 
655        try
656        {
657            $oauth = new OAuth2(new OAuth2StorageUserCredential());
658            $token = $oauth->getBearerToken();
659            $oauth->verifyAccessToken($token);
660
661            if(!Config::me('uidNumber'))
662                throw new ResponseException('Session experired.', Response::UNAUTHORIZED);
663        }
664        catch (OAuth2ServerException $oauthError)
665        {
666                $oauthError->sendHttpResponse();
667        }
668       
669    }
670   
671    /**
672     * Execute a request on this resource.
673     * @param Request request The request to execute the resource in the context of
674     * @return Response
675     * @throws ResponseException If the HTTP method is not allowed on the resource, a 405 exception is thrown
676     */
677    function exec($request) {
678       
679        if (
680            in_array(strtoupper($request->method), $request->HTTPMethods) &&
681            method_exists($this, $request->method)
682        ) {
683           
684            $method = new ReflectionMethod($this, $request->method);
685            $parameters = array();
686            foreach ($method->getParameters() as $param) {
687                if ($param->name == 'request') {
688                    $parameters[] = $request;
689                } elseif (isset($this->parameters[$param->name])) {
690                    $parameters[] = $this->parameters[$param->name];
691                    unset($this->parameters[$param->name]);
692                } else {
693                    $parameters[] = reset($this->parameters);
694                    array_shift($this->parameters);
695                }
696            }
697           
698            $response = call_user_func_array(
699                array($this, $request->method),
700                $parameters
701            );
702           
703            $responseClassName = class_exists('Tonic\\Response', false) ? 'Tonic\\Response' : 'Response';
704            if (!$response || !($response instanceof $responseClassName)) {
705                throw new Exception('Method '.$request->method.' of '.get_class($this).' did not return a Response object');
706            }
707           
708        } else {
709           
710            // send 405 method not allowed
711            throw new ResponseException(
712                'The HTTP method "'.$request->method.'" is not allowed for the resource "'.$request->uri.'".',
713                Response::METHODNOTALLOWED
714            );
715           
716        }
717       
718        # good for debugging, remove this at some point
719        $response->addHeader('X-Resource', get_class($this));
720       
721        return $response;
722       
723    }
724   
725}
726
727/**
728 * Model the data of the outgoing HTTP response
729 * @namespace Tonic\Lib
730 */
731class Response {
732   
733    /**
734     * HTTP response code constant
735     */
736    const OK = 200,
737          CREATED = 201,
738          NOCONTENT = 204,
739          MOVEDPERMANENTLY = 301,
740          FOUND = 302,
741          SEEOTHER = 303,
742          NOTMODIFIED = 304,
743          TEMPORARYREDIRECT = 307,
744          BADREQUEST = 400,
745          UNAUTHORIZED = 401,
746          FORBIDDEN = 403,
747          NOTFOUND = 404,
748          METHODNOTALLOWED = 405,
749          NOTACCEPTABLE = 406,
750          GONE = 410,
751          LENGTHREQUIRED = 411,
752          PRECONDITIONFAILED = 412,
753          UNSUPPORTEDMEDIATYPE = 415,
754          INTERNALSERVERERROR = 500;
755   
756    /**
757     * The request object generating this response
758     * @var Request
759     */
760    public $request;
761   
762    /**
763     * The HTTP response code to send
764     * @var int
765     */
766    public $code = Response::OK;
767   
768    /**
769     * The HTTP headers to send
770     * @var str[]
771     */
772    public $headers = array();
773   
774    /**
775     * The HTTP response body to send
776     * @var str
777     */
778    public $body;
779   
780    /**
781     * Create a response object.
782     * @param Request request The request object generating this response
783     * @param str uri The URL of the actual resource being used to build the response
784     */
785    function __construct($request, $uri = NULL) {
786       
787        $this->request = $request;
788       
789        if ($uri && $uri != $request->uri) { // add content location header
790            $this->addHeader('Content-Location', $uri);
791            $this->addVary('Accept');
792            $this->addVary('Accept-Language');
793        }
794        $this->addHeader('Allow', implode(', ', $request->allowedMethods));
795       
796    }
797   
798    /**
799     * Convert the object into a string suitable for printing
800     * @return str
801     * @codeCoverageIgnore
802     */
803    function __toString() {
804        $str = 'HTTP/1.1 '.$this->code;
805        foreach ($this->headers as $name => $value) {
806            $str .= "\n".$name.': '.$value;
807        }
808        return $str;
809    }
810   
811    /**
812     * Add a header to the response
813     * @param str header
814     * @param str value
815     */
816    function addHeader($header, $value) {
817        $this->headers[$header] = $value;
818    }
819   
820    /**
821     * Send a cache control header with the response
822     * @param int time Cache length in seconds
823     */
824    function addCacheHeader($time = 86400) {
825        if ($time) {
826            $this->addHeader('Cache-Control', 'max-age='.$time.', must-revalidate');
827        } else {
828            $this->addHeader('Cache-Control', 'no-cache');
829        }
830    }
831   
832    /**
833     * Send an etag with the response
834     * @param str etag Etag value
835     */
836    function addEtag($etag) {
837        $this->addHeader('Etag', '"'.$etag.'"');
838    }
839   
840    function addVary($header) {
841        if (isset($this->headers['Vary'])) {
842            $this->headers['Vary'] .= ', '.$header;
843        } else {
844            $this->addHeader('Vary', $header);
845        }
846    }
847   
848    /**
849     * Output the response
850     * @codeCoverageIgnore
851     */
852    function output() {
853       
854        if (php_sapi_name() != 'cli' && !headers_sent()) {
855           
856            header('HTTP/1.1 '.$this->code);
857            foreach ($this->headers as $header => $value) {
858                header($header.': '.$value);
859            }
860        }
861       
862        if (strtoupper($this->request->method) !== 'HEAD') {
863            echo $this->body;
864        }
865       
866    }
867   
868}
869
870/**
871 * Exception class for HTTP response errors
872 * @namespace Tonic\Lib
873 */
874class ResponseException extends Exception {
875   
876    /**
877     * Generate a default response for this exception
878     * @param Request request
879     * @return Response
880     */
881    function response($request) {
882        $response = new Response($request);
883        $response->code = $this->code;
884        $response->body = $this->message;
885        return $response;
886    }
887   
888}
889
Note: See TracBrowser for help on using the repository browser.