Overview

Namespaces

  • None
  • Wei
    • Validator

Classes

  • Wei\Validator\All
  • Wei\Validator\AllOf
  • Wei\Validator\Alnum
  • Wei\Validator\Alpha
  • Wei\Validator\BaseValidator
  • Wei\Validator\Between
  • Wei\Validator\Blank
  • Wei\Validator\Callback
  • Wei\Validator\CharLength
  • Wei\Validator\Chinese
  • Wei\Validator\Color
  • Wei\Validator\Contains
  • Wei\Validator\CreditCard
  • Wei\Validator\Date
  • Wei\Validator\DateTime
  • Wei\Validator\Decimal
  • Wei\Validator\Digit
  • Wei\Validator\Dir
  • Wei\Validator\DivisibleBy
  • Wei\Validator\DoubleByte
  • Wei\Validator\Email
  • Wei\Validator\EndsWith
  • Wei\Validator\EqualTo
  • Wei\Validator\Exists
  • Wei\Validator\FieldExists
  • Wei\Validator\File
  • Wei\Validator\GreaterThan
  • Wei\Validator\GreaterThanOrEqual
  • Wei\Validator\IdCardCn
  • Wei\Validator\IdCardHk
  • Wei\Validator\IdCardMo
  • Wei\Validator\IdCardTw
  • Wei\Validator\IdenticalTo
  • Wei\Validator\Image
  • Wei\Validator\In
  • Wei\Validator\Ip
  • Wei\Validator\Length
  • Wei\Validator\LessThan
  • Wei\Validator\LessThanOrEqual
  • Wei\Validator\Lowercase
  • Wei\Validator\Luhn
  • Wei\Validator\MaxLength
  • Wei\Validator\MinLength
  • Wei\Validator\MobileCn
  • Wei\Validator\NaturalNumber
  • Wei\Validator\NoneOf
  • Wei\Validator\Null
  • Wei\Validator\Number
  • Wei\Validator\OneOf
  • Wei\Validator\Password
  • Wei\Validator\Phone
  • Wei\Validator\PhoneCn
  • Wei\Validator\PlateNumberCn
  • Wei\Validator\PositiveInteger
  • Wei\Validator\PostcodeCn
  • Wei\Validator\Present
  • Wei\Validator\QQ
  • Wei\Validator\RecordExists
  • Wei\Validator\Regex
  • Wei\Validator\Required
  • Wei\Validator\SomeOf
  • Wei\Validator\StartsWith
  • Wei\Validator\Time
  • Wei\Validator\Tld
  • Wei\Validator\Type
  • Wei\Validator\Uppercase
  • Wei\Validator\Url
  • Wei\Validator\Uuid
  • Overview
  • Namespace
  • Function
  1: <?php
  2: /**
  3:  * Wei Framework
  4:  *
  5:  * @copyright   Copyright (c) 2008-2015 Twin Huang
  6:  * @license     http://opensource.org/licenses/mit-license.php MIT License
  7:  */
  8: 
  9: namespace Wei;
 10: 
 11: /**
 12:  * A service that handles the HTTP response data
 13:  *
 14:  * @author      Twin Huang <twinhuang@qq.com>
 15:  * @property    Logger $logger The logger service
 16:  * @property    Request $request The request service
 17:  */
 18: class Response extends Base
 19: {
 20:     /**
 21:      * Common use HTTP status code and text
 22:      *
 23:      * @var array
 24:      */
 25:     protected $statusTexts = array(
 26:         // Successful Requests
 27:         200 => 'OK',
 28:         201 => 'Created',
 29:         202 => 'Accepted',
 30:         203 => 'Non-Authoritative Information',
 31:         204 => 'No Content',
 32:         205 => 'Reset Content',
 33:         206 => 'Partial Content',
 34:         // Redirects
 35:         300 => 'Multiple Choices',
 36:         301 => 'Moved Permanently',
 37:         302 => 'Found',
 38:         303 => 'See Other',
 39:         304 => 'Not Modified',
 40:         305 => 'Use Proxy',
 41:         307 => 'Temporary Redirect',
 42:         // Client Errors
 43:         401 => 'Unauthorized',
 44:         403 => 'Forbidden',
 45:         404 => 'Not Found',
 46:         405 => 'Method Not Allowed',
 47:         406 => 'Not Acceptable',
 48:         407 => 'Proxy Authentication Required',
 49:         408 => 'Request Timeout',
 50:         409 => 'Conflict',
 51:         410 => 'Gone',
 52:         411 => 'Length Required',
 53:         412 => 'Precondition Failed',
 54:         413 => 'Request Entity Too Large',
 55:         414 => 'Request-URI Too Long',
 56:         415 => 'Unsupported Media Type',
 57:         416 => 'Requested Range Not Satisfiable',
 58:         417 => 'Expectation Failed',
 59:         // Server Errors
 60:         500 => 'Internal Server Error',
 61:         501 => 'Not Implemented',
 62:         502 => 'Bad Gateway',
 63:         503 => 'Service Unavailable',
 64:         504 => 'Gateway Timeout',
 65:         505 => 'HTTP Version Not Supported'
 66:     );
 67: 
 68:     /**
 69:      * The HTTP version, current is 1.0 or 1.1
 70:      *
 71:      * @var string
 72:      */
 73:     protected $version = '1.1';
 74: 
 75:     /**
 76:      * The status code
 77:      *
 78:      * @var int
 79:      */
 80:     protected $statusCode = 200;
 81: 
 82:     /**
 83:      * The status text for status code
 84:      *
 85:      * @var string
 86:      */
 87:     protected $statusText = 'OK';
 88: 
 89:     /**
 90:      * The response content
 91:      *
 92:      * @var string
 93:      */
 94:     protected $content;
 95: 
 96:     /**
 97:      * The response headers
 98:      *
 99:      * @var array
100:      */
101:     protected $headers = array();
102: 
103:     /**
104:      * The sent response headers
105:      *
106:      * @var array
107:      */
108:     protected $sentHeaders = array();
109: 
110:     /**
111:      * The response cookies
112:      *
113:      * @var array
114:      */
115:     protected $cookies = array();
116: 
117:     /**
118:      * The cookie options
119:      *
120:      * Name     | Type   | Description
121:      * ---------|--------|-------------
122:      * expires  | int    | The lifetime of cookie (seconds)
123:      * path     | string | The path on the server in which the cookie will be available on
124:      * domain   | string | The domain that the cookie is available to
125:      * secure   | bool   | Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
126:      * httpOnly | bool   | When TRUE the cookie will be made accessible only through the HTTP protocol
127:      * raw      | bool   | Whether send a cookie without urlencoding the cookie value
128:      *
129:      * @var array
130:      * @link http://php.net/manual/en/function.setcookie.php
131:      */
132:     protected $cookieOption = array(
133:         'expires' => 864000,
134:         'path' => '/',
135:         'domain' => null,
136:         'secure' => false,
137:         'httpOnly' => false,
138:         'raw' => false,
139:     );
140: 
141:     /**
142:      * The download options
143:      *
144:      * Name        | Type   | Description
145:      * ------------|--------|-------------
146:      * type        | string | The HTTP content type
147:      * disposition | string | The type of disposition, could be "attachment" or "inline",
148:      * filename    | string | The file name to display in download dialog
149:      *
150:      * When disposition is "inline", the browser will try to open file within
151:      * the browser, while "attachment" will force it to download
152:      *
153:      * @var array
154:      * @link http://stackoverflow.com/questions/1395151/content-dispositionwhat-are-the-differences-between-inline-and-attachment
155:      */
156:     protected $downloadOption = array(
157:         'type' => 'application/x-download',
158:         'disposition' => 'attachment',
159:         'filename' => null,
160:     );
161: 
162:     /**
163:      * Whether in unit test mode
164:      *
165:      * @var bool
166:      */
167:     protected $unitTest = false;
168: 
169:     /**
170:      * The custom redirect view file
171:      *
172:      * @var string
173:      */
174:     protected $redirectView;
175: 
176:     /**
177:      * The seconds to wait before redirect
178:      *
179:      * @var int
180:      */
181:     protected $redirectWait = 0;
182: 
183:     /**
184:      * The callback executes *before* send response
185:      *
186:      * @var callable
187:      */
188:     protected $beforeSend;
189: 
190:     /**
191:      * The callback executes *after* sent response
192:      *
193:      * @var callable
194:      */
195:     protected $afterSend;
196: 
197:     /**
198:      * Send response header and content
199:      *
200:      * @param  string $content
201:      * @param  int $status
202:      * @return $this
203:      */
204:     public function __invoke($content = null, $status = null)
205:     {
206:         return $this->send($content, $status);
207:     }
208: 
209:     /**
210:      * Send response header and content
211:      *
212:      * @param  string $content
213:      * @param  int $status
214:      * @return $this
215:      */
216:     public function send($content = null, $status = null)
217:     {
218:         // Render json when content is array
219:         if (is_array($content)) {
220:             return $this->json($content)->send();
221:         } elseif (null !== $content) {
222:             $this->setContent($content);
223:         }
224: 
225:         if (null !== $status) {
226:             $this->setStatusCode($status);
227:         }
228: 
229:         // Trigger the after send callback
230:         $this->beforeSend && call_user_func($this->beforeSend, $this, $content);
231: 
232:         $this->sendHeader();
233:         $this->sendContent();
234: 
235:         // Trigger the after send callback
236:         $this->afterSend && call_user_func($this->afterSend, $this);
237: 
238:         return $this;
239:     }
240: 
241:     /**
242:      * Set response content
243:      *
244:      * @param  mixed $content
245:      * @return $this
246:      */
247:     public function setContent($content)
248:     {
249:         $this->content = $content;
250:         return $this;
251:     }
252: 
253:     /**
254:      * Get response content
255:      *
256:      * @return mixed
257:      */
258:     public function getContent()
259:     {
260:         return $this->content;
261:     }
262: 
263:     /**
264:      * Send response content
265:      *
266:      * @return $this
267:      */
268:     public function sendContent()
269:     {
270:         echo $this->content;
271:         return $this;
272:     }
273: 
274:     /**
275:      * Set the header status code
276:      *
277:      * @param  int $code The status code
278:      * @param  string|null $text The status text
279:      * @return $this
280:      */
281:     public function setStatusCode($code, $text = null)
282:     {
283:         $this->statusCode = (int)$code;
284: 
285:         if ($text) {
286:             $this->statusText = $text;
287:         } elseif (isset($this->statusTexts[$code])) {
288:             $this->statusText = $this->statusTexts[$code];
289:         }
290: 
291:         return $this;
292:     }
293: 
294:     /**
295:      * Get the status code
296:      *
297:      * @return int
298:      */
299:     public function getStatusCode()
300:     {
301:         return $this->statusCode;
302:     }
303: 
304:     /**
305:      * Set the HTTP version
306:      *
307:      * @param  string $version The HTTP version
308:      * @return $this
309:      */
310:     public function setVersion($version)
311:     {
312:         $this->version = $version;
313:         return $this;
314:     }
315: 
316:     /**
317:      * Get the HTTP version
318:      *
319:      * @return string
320:      */
321:     public function getVersion()
322:     {
323:         return $this->version;
324:     }
325: 
326:     /**
327:      * Set the header string
328:      *
329:      * @param  string $name The header name
330:      * @param  string|array $values The header values
331:      * @param  bool $replace Whether replace the exists values or not
332:      * @return $this
333:      */
334:     public function setHeader($name, $values = null, $replace = true)
335:     {
336:         if (is_array($name)) {
337:             foreach ($name as $key => $value) {
338:                 $this->setHeader($key, $value);
339:             }
340:             return $this;
341:         }
342: 
343:         $values = (array)$values;
344:         if (true === $replace || !isset($this->headers[$name])) {
345:             $this->headers[$name] = $values;
346:         } else {
347:             $this->headers[$name] = array_merge($this->headers[$name], $values);
348:         }
349: 
350:         return $this;
351:     }
352: 
353:     /**
354:      * Get the header string
355:      *
356:      * @param  string $name The header name
357:      * @param  mixed $default The default value
358:      * @param  bool $first Return the first element or the whole header values
359:      * @return mixed
360:      */
361:     public function getHeader($name, $default = null, $first = true)
362:     {
363:         if (!isset($this->headers[$name])) {
364:             return $default;
365:         }
366: 
367:         if (is_array($this->headers[$name]) && $first) {
368:             return current($this->headers[$name]);
369:         }
370: 
371:         return $this->headers[$name];
372:     }
373: 
374:     /**
375:      * Remove header by specified name
376:      *
377:      * @param string $name The header name
378:      * @return $this
379:      */
380:     public function removeHeader($name)
381:     {
382:         header_remove($name);
383:         unset($this->headers[$name]);
384:         return $this;
385:     }
386: 
387:     /**
388:      * Send HTTP headers, including HTTP status, raw headers and cookies
389:      *
390:      * @return bool If the header has been seen, return false, otherwise, return true
391:      */
392:     public function sendHeader()
393:     {
394:         $file = $line = null;
395:         if ($this->isHeaderSent($file, $line)) {
396:             if ($this->wei->has('logger')) {
397:                 $this->logger->debug(sprintf('Header has been at %s:%s', $file, $line));
398:             }
399:             return false;
400:         }
401: 
402:         // Send status
403:         $this->sendRawHeader(sprintf('HTTP/%s %d %s', $this->version, $this->statusCode, $this->statusText));
404: 
405:         // Send headers
406:         foreach ($this->headers as $name => $values) {
407:             foreach ($values as $value) {
408:                 $this->sendRawHeader($name . ': ' . $value);
409:             }
410:         }
411: 
412:         $this->sendCookie();
413: 
414:         return true;
415:     }
416: 
417:     /**
418:      * Send a raw HTTP header
419:      *
420:      * If in unit test mode, the response will store header string into
421:      * `sentHeaders` property without send it for testing purpose
422:      *
423:      * @param string $header
424:      */
425:     protected function sendRawHeader($header)
426:     {
427:         $this->unitTest ? ($this->sentHeaders[] = $header) : header($header, false);
428:     }
429: 
430:     /**
431:      * Checks if or where headers have been sent
432:      *
433:      * If NOT in unit test mode and the optional `file` and `line` parameters
434:      * are set, `isHeaderSent()` will put the PHP source file name and line
435:      * number where output started in the file and line variables
436:      *
437:      * @param string $file
438:      * @param int $line The line number where the output started
439:      * @return bool
440:      * @link http://php.net/manual/en/function.headers-sent.php
441:      */
442:     public function isHeaderSent(&$file = null, &$line = null)
443:     {
444:         return $this->unitTest ? (bool)$this->sentHeaders : headers_sent($file, $line);
445:     }
446: 
447:     /**
448:      * Get response cookie
449:      *
450:      * @param  string $key The name of cookie
451:      * @param  mixed $default The default value when cookie not exists
452:      * @return mixed
453:      */
454:     public function getCookie($key, $default = null)
455:     {
456:         return isset($this->cookies[$key]) ? $this->cookies[$key]['value'] : $default;
457:     }
458: 
459:     /**
460:      * Set response cookie
461:      *
462:      * @param  string $key The name of cookie
463:      * @param  mixed $value The value of cookie
464:      * @param  array $options The options of cookie
465:      * @return $this
466:      */
467:     public function setCookie($key, $value, array $options = array())
468:     {
469:         $this->cookies[$key] = array('value' => $value) + $options;
470:         return $this;
471:     }
472: 
473:     /**
474:      * Remove response cookie
475:      *
476:      * @param string $key The name of cookie
477:      * @return $this
478:      */
479:     public function removeCookie($key)
480:     {
481:         return $this->setCookie($key, '', array('expires' => -1));
482:     }
483: 
484:     /**
485:      * Send cookie
486:      *
487:      * @return $this
488:      */
489:     public function sendCookie()
490:     {
491:         $time = time();
492:         // Anonymous function for unit test
493:         $setCookie = function () {
494:         };
495:         foreach ($this->cookies as $name => $o) {
496:             $o += $this->cookieOption;
497:             $fn = $this->unitTest ? $setCookie : ($o['raw'] ? 'setrawcookie' : 'setcookie');
498:             $fn($name, $o['value'], $time + $o['expires'], $o['path'], $o['domain'], $o['secure'], $o['httpOnly']);
499:         }
500:         return $this;
501:     }
502: 
503:     /**
504:      * Returns response status, headers and content as string
505:      *
506:      * @return string
507:      */
508:     public function __toString()
509:     {
510:         return sprintf('HTTP/%s %d %s', $this->version, $this->statusCode, $this->statusText) . "\r\n"
511:         . $this->getHeaderString() . "\r\n"
512:         . $this->content;
513:     }
514: 
515:     /**
516:      * Returns response header as string
517:      *
518:      * @return string
519:      */
520:     public function getHeaderString()
521:     {
522:         $string = '';
523:         foreach ($this->headers as $name => $values) {
524:             foreach ($values as $value) {
525:                 $string .= $name . ': ' . $value . "\r\n";
526:             }
527:         }
528:         return $string;
529:     }
530: 
531:     /**
532:      * Set redirect view file
533:      *
534:      * @param string $redirectView The view file
535:      * @return $this
536:      * @throws \RuntimeException When view file not found
537:      */
538:     public function setRedirectView($redirectView)
539:     {
540:         if (!is_file($redirectView)) {
541:             throw new \RuntimeException(sprintf('Redirect view file "%s" not found', $redirectView));
542:         }
543:         $this->redirectView = $redirectView;
544:         return $this;
545:     }
546: 
547:     /**
548:      * Send a redirect response
549:      *
550:      * @param string $url The url redirect to
551:      * @param int $statusCode The response status code
552:      * @param array $options The redirect wei options
553:      * @return $this
554:      */
555:     public function redirect($url = null, $statusCode = 302, $options = array())
556:     {
557:         $this->setStatusCode($statusCode);
558:         $this->setOption($options);
559: 
560:         // The variables for custom redirect view
561:         $escapedUrl = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
562:         $wait = (int)$this->redirectWait;
563: 
564:         // Location header does not support delay
565:         if (0 === $wait) {
566:             $this->setHeader('Location', $url);
567:         }
568: 
569:         // Prepare response content
570:         if ($this->redirectView) {
571:             ob_start();
572:             require $this->redirectView;
573:             $content = ob_get_clean();
574:         } else {
575:             $content = sprintf('<!DOCTYPE html>
576: <html>
577:   <head>
578:     <meta charset="utf-8">
579:     <meta http-equiv="refresh" content="%d;url=%2$s">
580:     <title>Redirecting to %s</title>
581:   </head>
582:   <body>
583:     <h1>Redirecting to <a href="%2$s">%2$s</a></h1>
584:   </body>
585: </html>', $wait, $escapedUrl);
586:         }
587: 
588:         return $this->setContent($content);
589:     }
590: 
591:     /**
592:      * Response JSON or JSONP format string
593:      *
594:      * @param mixed $data The variable to be convert to JSON string
595:      * @param bool $jsonp Whether allow response json format on demand
596:      * @return $this
597:      */
598:     public function json($data, $jsonp = false)
599:     {
600:         $options = 0;
601:         defined('JSON_UNESCAPED_UNICODE') && $options = JSON_UNESCAPED_UNICODE;
602:         $content = json_encode($data, $options);
603: 
604:         if ($jsonp && preg_match('/^[$A-Z_][0-9A-Z_$.]*$/i', $this->request['callback']) === 1) {
605:             $this->setHeader('Content-Type', 'application/javascript');
606:             $content = $this->request['callback'] . '(' . $content . ')';
607:         } else {
608:             $this->setHeader('Content-Type', 'application/json');
609:         }
610: 
611:         return $this->setContent($content);
612:     }
613: 
614:     /**
615:      * Response JSONP format string
616:      *
617:      * @param mixed $data
618:      * @return $this
619:      */
620:     public function jsonp($data)
621:     {
622:         return $this->json($data, true);
623:     }
624: 
625:     /**
626:      * Flushes content to browser immediately
627:      *
628:      * @param string $content
629:      * @return $this
630:      */
631:     public function flush($content = null)
632:     {
633:         if (function_exists('apache_setenv')) {
634:             apache_setenv('no-gzip', '1');
635:         }
636: 
637:         /**
638:          * Disable zlib to compress output
639:          * @link http://www.php.net/manual/en/zlib.configuration.php
640:          */
641:         if (!headers_sent() && extension_loaded('zlib')) {
642:             ini_set('zlib.output_compression', '0');
643:         }
644: 
645:         /**
646:          * Turn implicit flush on
647:          * @link http://www.php.net/manual/en/function.ob-implicit-flush.php
648:          */
649:         ob_implicit_flush();
650: 
651:         $this->send($content);
652: 
653:         /**
654:          * Send blank characters for output_buffering
655:          * @link http://www.php.net/manual/en/outcontrol.configuration.php
656:          */
657:         if ($length = ini_get('output_buffering')) {
658:             echo str_pad('', $length);
659:         }
660: 
661:         while (ob_get_level()) {
662:             ob_end_flush();
663:         }
664: 
665:         return $this;
666:     }
667: 
668:     /**
669:      * Send file download response
670:      *
671:      * @param string $file The path of file
672:      * @param array $downloadOptions The download options
673:      * @return $this
674:      * @throws \RuntimeException When file not found
675:      */
676:     public function download($file = null, array $downloadOptions = array())
677:     {
678:         $o = $downloadOptions + $this->downloadOption;
679: 
680:         if (!is_file($file)) {
681:             throw new \RuntimeException('File not found', 404);
682:         }
683: 
684:         $name = $o['filename'] ? : basename($file);
685:         $name = rawurlencode($name);
686: 
687:         // For IE
688:         $userAgent = $this->request->getServer('HTTP_USER_AGENT');
689:         if (preg_match('/MSIE ([\w.]+)/', $userAgent)) {
690:             $filename = '=' . $name;
691:         } else {
692:             $filename = "*=UTF-8''" . $name;
693:         }
694: 
695:         $this->setHeader(array(
696:             'Content-Description' => 'File Transfer',
697:             'Content-Type' => $o['type'],
698:             'Content-Disposition' => $o['disposition'] . ';filename' . $filename,
699:             'Content-Transfer-Encoding' => 'binary',
700:             'Expires' => '0',
701:             'Cache-Control' => 'must-revalidate',
702:             'Pragma' => 'public',
703:             'Content-Length' => filesize($file),
704:         ));
705: 
706:         // Send headers
707:         $this->send();
708: 
709:         // Send file content
710:         readfile($file);
711: 
712:         return $this;
713:     }
714: }
715: 
Wei Framework API documentation generated by ApiGen