Slim.php (48223B)
1 <?php 2 /** 3 * Slim - a micro PHP 5 framework 4 * 5 * @author Josh Lockhart <info@slimframework.com> 6 * @copyright 2011 Josh Lockhart 7 * @link http://www.slimframework.com 8 * @license http://www.slimframework.com/license 9 * @version 2.4.2 10 * @package Slim 11 * 12 * MIT LICENSE 13 * 14 * Permission is hereby granted, free of charge, to any person obtaining 15 * a copy of this software and associated documentation files (the 16 * "Software"), to deal in the Software without restriction, including 17 * without limitation the rights to use, copy, modify, merge, publish, 18 * distribute, sublicense, and/or sell copies of the Software, and to 19 * permit persons to whom the Software is furnished to do so, subject to 20 * the following conditions: 21 * 22 * The above copyright notice and this permission notice shall be 23 * included in all copies or substantial portions of the Software. 24 * 25 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 */ 33 namespace Slim; 34 35 // Ensure mcrypt constants are defined even if mcrypt extension is not loaded 36 if (!extension_loaded('mcrypt')) { 37 define('MCRYPT_MODE_CBC', 0); 38 define('MCRYPT_RIJNDAEL_256', 0); 39 } 40 41 /** 42 * Slim 43 * @package Slim 44 * @author Josh Lockhart 45 * @since 1.0.0 46 * 47 * @property \Slim\Environment $environment 48 * @property \Slim\Http\Response $response 49 * @property \Slim\Http\Request $request 50 * @property \Slim\Router $router 51 */ 52 class Slim 53 { 54 /** 55 * @const string 56 */ 57 const VERSION = '2.4.2'; 58 59 /** 60 * @var \Slim\Helper\Set 61 */ 62 public $container; 63 64 /** 65 * @var array[\Slim] 66 */ 67 protected static $apps = array(); 68 69 /** 70 * @var string 71 */ 72 protected $name; 73 74 /** 75 * @var array 76 */ 77 protected $middleware; 78 79 /** 80 * @var mixed Callable to be invoked if application error 81 */ 82 protected $error; 83 84 /** 85 * @var mixed Callable to be invoked if no matching routes are found 86 */ 87 protected $notFound; 88 89 /** 90 * @var array 91 */ 92 protected $hooks = array( 93 'slim.before' => array(array()), 94 'slim.before.router' => array(array()), 95 'slim.before.dispatch' => array(array()), 96 'slim.after.dispatch' => array(array()), 97 'slim.after.router' => array(array()), 98 'slim.after' => array(array()) 99 ); 100 101 /******************************************************************************** 102 * PSR-0 Autoloader 103 * 104 * Do not use if you are using Composer to autoload dependencies. 105 *******************************************************************************/ 106 107 /** 108 * Slim PSR-0 autoloader 109 */ 110 public static function autoload($className) 111 { 112 $thisClass = str_replace(__NAMESPACE__.'\\', '', __CLASS__); 113 114 $baseDir = __DIR__; 115 116 if (substr($baseDir, -strlen($thisClass)) === $thisClass) { 117 $baseDir = substr($baseDir, 0, -strlen($thisClass)); 118 } 119 120 $className = ltrim($className, '\\'); 121 $fileName = $baseDir; 122 $namespace = ''; 123 if ($lastNsPos = strripos($className, '\\')) { 124 $namespace = substr($className, 0, $lastNsPos); 125 $className = substr($className, $lastNsPos + 1); 126 $fileName .= str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; 127 } 128 $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; 129 130 if (file_exists($fileName)) { 131 require $fileName; 132 } 133 } 134 135 /** 136 * Register Slim's PSR-0 autoloader 137 */ 138 public static function registerAutoloader() 139 { 140 spl_autoload_register(__NAMESPACE__ . "\\Slim::autoload"); 141 } 142 143 /******************************************************************************** 144 * Instantiation and Configuration 145 *******************************************************************************/ 146 147 /** 148 * Constructor 149 * @param array $userSettings Associative array of application settings 150 */ 151 public function __construct(array $userSettings = array()) 152 { 153 // Setup IoC container 154 $this->container = new \Slim\Helper\Set(); 155 $this->container['settings'] = array_merge(static::getDefaultSettings(), $userSettings); 156 157 // Default environment 158 $this->container->singleton('environment', function ($c) { 159 return \Slim\Environment::getInstance(); 160 }); 161 162 // Default request 163 $this->container->singleton('request', function ($c) { 164 return new \Slim\Http\Request($c['environment']); 165 }); 166 167 // Default response 168 $this->container->singleton('response', function ($c) { 169 return new \Slim\Http\Response(); 170 }); 171 172 // Default router 173 $this->container->singleton('router', function ($c) { 174 return new \Slim\Router(); 175 }); 176 177 // Default view 178 $this->container->singleton('view', function ($c) { 179 $viewClass = $c['settings']['view']; 180 $templatesPath = $c['settings']['templates.path']; 181 182 $view = ($viewClass instanceOf \Slim\View) ? $viewClass : new $viewClass; 183 $view->setTemplatesDirectory($templatesPath); 184 return $view; 185 }); 186 187 // Default log writer 188 $this->container->singleton('logWriter', function ($c) { 189 $logWriter = $c['settings']['log.writer']; 190 191 return is_object($logWriter) ? $logWriter : new \Slim\LogWriter($c['environment']['slim.errors']); 192 }); 193 194 // Default log 195 $this->container->singleton('log', function ($c) { 196 $log = new \Slim\Log($c['logWriter']); 197 $log->setEnabled($c['settings']['log.enabled']); 198 $log->setLevel($c['settings']['log.level']); 199 $env = $c['environment']; 200 $env['slim.log'] = $log; 201 202 return $log; 203 }); 204 205 // Default mode 206 $this->container['mode'] = function ($c) { 207 $mode = $c['settings']['mode']; 208 209 if (isset($_ENV['SLIM_MODE'])) { 210 $mode = $_ENV['SLIM_MODE']; 211 } else { 212 $envMode = getenv('SLIM_MODE'); 213 if ($envMode !== false) { 214 $mode = $envMode; 215 } 216 } 217 218 return $mode; 219 }; 220 221 // Define default middleware stack 222 $this->middleware = array($this); 223 $this->add(new \Slim\Middleware\Flash()); 224 $this->add(new \Slim\Middleware\MethodOverride()); 225 226 // Make default if first instance 227 if (is_null(static::getInstance())) { 228 $this->setName('default'); 229 } 230 } 231 232 public function __get($name) 233 { 234 return $this->container[$name]; 235 } 236 237 public function __set($name, $value) 238 { 239 $this->container[$name] = $value; 240 } 241 242 public function __isset($name) 243 { 244 return isset($this->container[$name]); 245 } 246 247 public function __unset($name) 248 { 249 unset($this->container[$name]); 250 } 251 252 /** 253 * Get application instance by name 254 * @param string $name The name of the Slim application 255 * @return \Slim\Slim|null 256 */ 257 public static function getInstance($name = 'default') 258 { 259 return isset(static::$apps[$name]) ? static::$apps[$name] : null; 260 } 261 262 /** 263 * Set Slim application name 264 * @param string $name The name of this Slim application 265 */ 266 public function setName($name) 267 { 268 $this->name = $name; 269 static::$apps[$name] = $this; 270 } 271 272 /** 273 * Get Slim application name 274 * @return string|null 275 */ 276 public function getName() 277 { 278 return $this->name; 279 } 280 281 /** 282 * Get default application settings 283 * @return array 284 */ 285 public static function getDefaultSettings() 286 { 287 return array( 288 // Application 289 'mode' => 'development', 290 // Debugging 291 'debug' => true, 292 // Logging 293 'log.writer' => null, 294 'log.level' => \Slim\Log::DEBUG, 295 'log.enabled' => true, 296 // View 297 'templates.path' => './templates', 298 'view' => '\Slim\View', 299 // Cookies 300 'cookies.encrypt' => false, 301 'cookies.lifetime' => '20 minutes', 302 'cookies.path' => '/', 303 'cookies.domain' => null, 304 'cookies.secure' => false, 305 'cookies.httponly' => false, 306 // Encryption 307 'cookies.secret_key' => 'CHANGE_ME', 308 'cookies.cipher' => MCRYPT_RIJNDAEL_256, 309 'cookies.cipher_mode' => MCRYPT_MODE_CBC, 310 // HTTP 311 'http.version' => '1.1', 312 // Routing 313 'routes.case_sensitive' => true 314 ); 315 } 316 317 /** 318 * Configure Slim Settings 319 * 320 * This method defines application settings and acts as a setter and a getter. 321 * 322 * If only one argument is specified and that argument is a string, the value 323 * of the setting identified by the first argument will be returned, or NULL if 324 * that setting does not exist. 325 * 326 * If only one argument is specified and that argument is an associative array, 327 * the array will be merged into the existing application settings. 328 * 329 * If two arguments are provided, the first argument is the name of the setting 330 * to be created or updated, and the second argument is the setting value. 331 * 332 * @param string|array $name If a string, the name of the setting to set or retrieve. Else an associated array of setting names and values 333 * @param mixed $value If name is a string, the value of the setting identified by $name 334 * @return mixed The value of a setting if only one argument is a string 335 */ 336 public function config($name, $value = null) 337 { 338 $c = $this->container; 339 340 if (is_array($name)) { 341 if (true === $value) { 342 $c['settings'] = array_merge_recursive($c['settings'], $name); 343 } else { 344 $c['settings'] = array_merge($c['settings'], $name); 345 } 346 } elseif (func_num_args() === 1) { 347 return isset($c['settings'][$name]) ? $c['settings'][$name] : null; 348 } else { 349 $settings = $c['settings']; 350 $settings[$name] = $value; 351 $c['settings'] = $settings; 352 } 353 } 354 355 /******************************************************************************** 356 * Application Modes 357 *******************************************************************************/ 358 359 /** 360 * Get application mode 361 * 362 * This method determines the application mode. It first inspects the $_ENV 363 * superglobal for key `SLIM_MODE`. If that is not found, it queries 364 * the `getenv` function. Else, it uses the application `mode` setting. 365 * 366 * @return string 367 */ 368 public function getMode() 369 { 370 return $this->mode; 371 } 372 373 /** 374 * Configure Slim for a given mode 375 * 376 * This method will immediately invoke the callable if 377 * the specified mode matches the current application mode. 378 * Otherwise, the callable is ignored. This should be called 379 * only _after_ you initialize your Slim app. 380 * 381 * @param string $mode 382 * @param mixed $callable 383 * @return void 384 */ 385 public function configureMode($mode, $callable) 386 { 387 if ($mode === $this->getMode() && is_callable($callable)) { 388 call_user_func($callable); 389 } 390 } 391 392 /******************************************************************************** 393 * Logging 394 *******************************************************************************/ 395 396 /** 397 * Get application log 398 * @return \Slim\Log 399 */ 400 public function getLog() 401 { 402 return $this->log; 403 } 404 405 /******************************************************************************** 406 * Routing 407 *******************************************************************************/ 408 409 /** 410 * Add GET|POST|PUT|PATCH|DELETE route 411 * 412 * Adds a new route to the router with associated callable. This 413 * route will only be invoked when the HTTP request's method matches 414 * this route's method. 415 * 416 * ARGUMENTS: 417 * 418 * First: string The URL pattern (REQUIRED) 419 * In-Between: mixed Anything that returns TRUE for `is_callable` (OPTIONAL) 420 * Last: mixed Anything that returns TRUE for `is_callable` (REQUIRED) 421 * 422 * The first argument is required and must always be the 423 * route pattern (ie. '/books/:id'). 424 * 425 * The last argument is required and must always be the callable object 426 * to be invoked when the route matches an HTTP request. 427 * 428 * You may also provide an unlimited number of in-between arguments; 429 * each interior argument must be callable and will be invoked in the 430 * order specified before the route's callable is invoked. 431 * 432 * USAGE: 433 * 434 * Slim::get('/foo'[, middleware, middleware, ...], callable); 435 * 436 * @param array (See notes above) 437 * @return \Slim\Route 438 */ 439 protected function mapRoute($args) 440 { 441 $pattern = array_shift($args); 442 $callable = array_pop($args); 443 $route = new \Slim\Route($pattern, $callable, $this->settings['routes.case_sensitive']); 444 $this->router->map($route); 445 if (count($args) > 0) { 446 $route->setMiddleware($args); 447 } 448 449 return $route; 450 } 451 452 /** 453 * Add generic route without associated HTTP method 454 * @see mapRoute() 455 * @return \Slim\Route 456 */ 457 public function map() 458 { 459 $args = func_get_args(); 460 461 return $this->mapRoute($args); 462 } 463 464 /** 465 * Add GET route 466 * @see mapRoute() 467 * @return \Slim\Route 468 */ 469 public function get() 470 { 471 $args = func_get_args(); 472 473 return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_GET, \Slim\Http\Request::METHOD_HEAD); 474 } 475 476 /** 477 * Add POST route 478 * @see mapRoute() 479 * @return \Slim\Route 480 */ 481 public function post() 482 { 483 $args = func_get_args(); 484 485 return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_POST); 486 } 487 488 /** 489 * Add PUT route 490 * @see mapRoute() 491 * @return \Slim\Route 492 */ 493 public function put() 494 { 495 $args = func_get_args(); 496 497 return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PUT); 498 } 499 500 /** 501 * Add PATCH route 502 * @see mapRoute() 503 * @return \Slim\Route 504 */ 505 public function patch() 506 { 507 $args = func_get_args(); 508 509 return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PATCH); 510 } 511 512 /** 513 * Add DELETE route 514 * @see mapRoute() 515 * @return \Slim\Route 516 */ 517 public function delete() 518 { 519 $args = func_get_args(); 520 521 return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_DELETE); 522 } 523 524 /** 525 * Add OPTIONS route 526 * @see mapRoute() 527 * @return \Slim\Route 528 */ 529 public function options() 530 { 531 $args = func_get_args(); 532 533 return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_OPTIONS); 534 } 535 536 /** 537 * Route Groups 538 * 539 * This method accepts a route pattern and a callback all Route 540 * declarations in the callback will be prepended by the group(s) 541 * that it is in 542 * 543 * Accepts the same parameters as a standard route so: 544 * (pattern, middleware1, middleware2, ..., $callback) 545 */ 546 public function group() 547 { 548 $args = func_get_args(); 549 $pattern = array_shift($args); 550 $callable = array_pop($args); 551 $this->router->pushGroup($pattern, $args); 552 if (is_callable($callable)) { 553 call_user_func($callable); 554 } 555 $this->router->popGroup(); 556 } 557 558 /* 559 * Add route for any HTTP method 560 * @see mapRoute() 561 * @return \Slim\Route 562 */ 563 public function any() 564 { 565 $args = func_get_args(); 566 567 return $this->mapRoute($args)->via("ANY"); 568 } 569 570 /** 571 * Not Found Handler 572 * 573 * This method defines or invokes the application-wide Not Found handler. 574 * There are two contexts in which this method may be invoked: 575 * 576 * 1. When declaring the handler: 577 * 578 * If the $callable parameter is not null and is callable, this 579 * method will register the callable to be invoked when no 580 * routes match the current HTTP request. It WILL NOT invoke the callable. 581 * 582 * 2. When invoking the handler: 583 * 584 * If the $callable parameter is null, Slim assumes you want 585 * to invoke an already-registered handler. If the handler has been 586 * registered and is callable, it is invoked and sends a 404 HTTP Response 587 * whose body is the output of the Not Found handler. 588 * 589 * @param mixed $callable Anything that returns true for is_callable() 590 */ 591 public function notFound ($callable = null) 592 { 593 if (is_callable($callable)) { 594 $this->notFound = $callable; 595 } else { 596 ob_start(); 597 if (is_callable($this->notFound)) { 598 call_user_func($this->notFound); 599 } else { 600 call_user_func(array($this, 'defaultNotFound')); 601 } 602 $this->halt(404, ob_get_clean()); 603 } 604 } 605 606 /** 607 * Error Handler 608 * 609 * This method defines or invokes the application-wide Error handler. 610 * There are two contexts in which this method may be invoked: 611 * 612 * 1. When declaring the handler: 613 * 614 * If the $argument parameter is callable, this 615 * method will register the callable to be invoked when an uncaught 616 * Exception is detected, or when otherwise explicitly invoked. 617 * The handler WILL NOT be invoked in this context. 618 * 619 * 2. When invoking the handler: 620 * 621 * If the $argument parameter is not callable, Slim assumes you want 622 * to invoke an already-registered handler. If the handler has been 623 * registered and is callable, it is invoked and passed the caught Exception 624 * as its one and only argument. The error handler's output is captured 625 * into an output buffer and sent as the body of a 500 HTTP Response. 626 * 627 * @param mixed $argument Callable|\Exception 628 */ 629 public function error($argument = null) 630 { 631 if (is_callable($argument)) { 632 //Register error handler 633 $this->error = $argument; 634 } else { 635 //Invoke error handler 636 $this->response->status(500); 637 $this->response->body(''); 638 $this->response->write($this->callErrorHandler($argument)); 639 $this->stop(); 640 } 641 } 642 643 /** 644 * Call error handler 645 * 646 * This will invoke the custom or default error handler 647 * and RETURN its output. 648 * 649 * @param \Exception|null $argument 650 * @return string 651 */ 652 protected function callErrorHandler($argument = null) 653 { 654 ob_start(); 655 if (is_callable($this->error)) { 656 call_user_func_array($this->error, array($argument)); 657 } else { 658 call_user_func_array(array($this, 'defaultError'), array($argument)); 659 } 660 661 return ob_get_clean(); 662 } 663 664 /******************************************************************************** 665 * Application Accessors 666 *******************************************************************************/ 667 668 /** 669 * Get a reference to the Environment object 670 * @return \Slim\Environment 671 */ 672 public function environment() 673 { 674 return $this->environment; 675 } 676 677 /** 678 * Get the Request object 679 * @return \Slim\Http\Request 680 */ 681 public function request() 682 { 683 return $this->request; 684 } 685 686 /** 687 * Get the Response object 688 * @return \Slim\Http\Response 689 */ 690 public function response() 691 { 692 return $this->response; 693 } 694 695 /** 696 * Get the Router object 697 * @return \Slim\Router 698 */ 699 public function router() 700 { 701 return $this->router; 702 } 703 704 /** 705 * Get and/or set the View 706 * 707 * This method declares the View to be used by the Slim application. 708 * If the argument is a string, Slim will instantiate a new object 709 * of the same class. If the argument is an instance of View or a subclass 710 * of View, Slim will use the argument as the View. 711 * 712 * If a View already exists and this method is called to create a 713 * new View, data already set in the existing View will be 714 * transferred to the new View. 715 * 716 * @param string|\Slim\View $viewClass The name or instance of a \Slim\View subclass 717 * @return \Slim\View 718 */ 719 public function view($viewClass = null) 720 { 721 if (!is_null($viewClass)) { 722 $existingData = is_null($this->view) ? array() : $this->view->getData(); 723 if ($viewClass instanceOf \Slim\View) { 724 $this->view = $viewClass; 725 } else { 726 $this->view = new $viewClass(); 727 } 728 $this->view->appendData($existingData); 729 $this->view->setTemplatesDirectory($this->config('templates.path')); 730 } 731 732 return $this->view; 733 } 734 735 /******************************************************************************** 736 * Rendering 737 *******************************************************************************/ 738 739 /** 740 * Render a template 741 * 742 * Call this method within a GET, POST, PUT, PATCH, DELETE, NOT FOUND, or ERROR 743 * callable to render a template whose output is appended to the 744 * current HTTP response body. How the template is rendered is 745 * delegated to the current View. 746 * 747 * @param string $template The name of the template passed into the view's render() method 748 * @param array $data Associative array of data made available to the view 749 * @param int $status The HTTP response status code to use (optional) 750 */ 751 public function render($template, $data = array(), $status = null) 752 { 753 if (!is_null($status)) { 754 $this->response->status($status); 755 } 756 $this->view->appendData($data); 757 $this->view->display($template); 758 } 759 760 /******************************************************************************** 761 * HTTP Caching 762 *******************************************************************************/ 763 764 /** 765 * Set Last-Modified HTTP Response Header 766 * 767 * Set the HTTP 'Last-Modified' header and stop if a conditional 768 * GET request's `If-Modified-Since` header matches the last modified time 769 * of the resource. The `time` argument is a UNIX timestamp integer value. 770 * When the current request includes an 'If-Modified-Since' header that 771 * matches the specified last modified time, the application will stop 772 * and send a '304 Not Modified' response to the client. 773 * 774 * @param int $time The last modified UNIX timestamp 775 * @throws \InvalidArgumentException If provided timestamp is not an integer 776 */ 777 public function lastModified($time) 778 { 779 if (is_integer($time)) { 780 $this->response->headers->set('Last-Modified', gmdate('D, d M Y H:i:s T', $time)); 781 if ($time === strtotime($this->request->headers->get('IF_MODIFIED_SINCE'))) { 782 $this->halt(304); 783 } 784 } else { 785 throw new \InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.'); 786 } 787 } 788 789 /** 790 * Set ETag HTTP Response Header 791 * 792 * Set the etag header and stop if the conditional GET request matches. 793 * The `value` argument is a unique identifier for the current resource. 794 * The `type` argument indicates whether the etag should be used as a strong or 795 * weak cache validator. 796 * 797 * When the current request includes an 'If-None-Match' header with 798 * a matching etag, execution is immediately stopped. If the request 799 * method is GET or HEAD, a '304 Not Modified' response is sent. 800 * 801 * @param string $value The etag value 802 * @param string $type The type of etag to create; either "strong" or "weak" 803 * @throws \InvalidArgumentException If provided type is invalid 804 */ 805 public function etag($value, $type = 'strong') 806 { 807 //Ensure type is correct 808 if (!in_array($type, array('strong', 'weak'))) { 809 throw new \InvalidArgumentException('Invalid Slim::etag type. Expected "strong" or "weak".'); 810 } 811 812 //Set etag value 813 $value = '"' . $value . '"'; 814 if ($type === 'weak') { 815 $value = 'W/'.$value; 816 } 817 $this->response['ETag'] = $value; 818 819 //Check conditional GET 820 if ($etagsHeader = $this->request->headers->get('IF_NONE_MATCH')) { 821 $etags = preg_split('@\s*,\s*@', $etagsHeader); 822 if (in_array($value, $etags) || in_array('*', $etags)) { 823 $this->halt(304); 824 } 825 } 826 } 827 828 /** 829 * Set Expires HTTP response header 830 * 831 * The `Expires` header tells the HTTP client the time at which 832 * the current resource should be considered stale. At that time the HTTP 833 * client will send a conditional GET request to the server; the server 834 * may return a 200 OK if the resource has changed, else a 304 Not Modified 835 * if the resource has not changed. The `Expires` header should be used in 836 * conjunction with the `etag()` or `lastModified()` methods above. 837 * 838 * @param string|int $time If string, a time to be parsed by `strtotime()`; 839 * If int, a UNIX timestamp; 840 */ 841 public function expires($time) 842 { 843 if (is_string($time)) { 844 $time = strtotime($time); 845 } 846 $this->response->headers->set('Expires', gmdate('D, d M Y H:i:s T', $time)); 847 } 848 849 /******************************************************************************** 850 * HTTP Cookies 851 *******************************************************************************/ 852 853 /** 854 * Set HTTP cookie to be sent with the HTTP response 855 * 856 * @param string $name The cookie name 857 * @param string $value The cookie value 858 * @param int|string $time The duration of the cookie; 859 * If integer, should be UNIX timestamp; 860 * If string, converted to UNIX timestamp with `strtotime`; 861 * @param string $path The path on the server in which the cookie will be available on 862 * @param string $domain The domain that the cookie is available to 863 * @param bool $secure Indicates that the cookie should only be transmitted over a secure 864 * HTTPS connection to/from the client 865 * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol 866 */ 867 public function setCookie($name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null) 868 { 869 $settings = array( 870 'value' => $value, 871 'expires' => is_null($time) ? $this->config('cookies.lifetime') : $time, 872 'path' => is_null($path) ? $this->config('cookies.path') : $path, 873 'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain, 874 'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure, 875 'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly 876 ); 877 $this->response->cookies->set($name, $settings); 878 } 879 880 /** 881 * Get value of HTTP cookie from the current HTTP request 882 * 883 * Return the value of a cookie from the current HTTP request, 884 * or return NULL if cookie does not exist. Cookies created during 885 * the current request will not be available until the next request. 886 * 887 * @param string $name 888 * @param bool $deleteIfInvalid 889 * @return string|null 890 */ 891 public function getCookie($name, $deleteIfInvalid = true) 892 { 893 // Get cookie value 894 $value = $this->request->cookies->get($name); 895 896 // Decode if encrypted 897 if ($this->config('cookies.encrypt')) { 898 $value = \Slim\Http\Util::decodeSecureCookie( 899 $value, 900 $this->config('cookies.secret_key'), 901 $this->config('cookies.cipher'), 902 $this->config('cookies.cipher_mode') 903 ); 904 if ($value === false && $deleteIfInvalid) { 905 $this->deleteCookie($name); 906 } 907 } 908 909 return $value; 910 } 911 912 /** 913 * DEPRECATION WARNING! Use `setCookie` with the `cookies.encrypt` app setting set to `true`. 914 * 915 * Set encrypted HTTP cookie 916 * 917 * @param string $name The cookie name 918 * @param mixed $value The cookie value 919 * @param mixed $expires The duration of the cookie; 920 * If integer, should be UNIX timestamp; 921 * If string, converted to UNIX timestamp with `strtotime`; 922 * @param string $path The path on the server in which the cookie will be available on 923 * @param string $domain The domain that the cookie is available to 924 * @param bool $secure Indicates that the cookie should only be transmitted over a secure 925 * HTTPS connection from the client 926 * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol 927 */ 928 public function setEncryptedCookie($name, $value, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false) 929 { 930 $this->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly); 931 } 932 933 /** 934 * DEPRECATION WARNING! Use `getCookie` with the `cookies.encrypt` app setting set to `true`. 935 * 936 * Get value of encrypted HTTP cookie 937 * 938 * Return the value of an encrypted cookie from the current HTTP request, 939 * or return NULL if cookie does not exist. Encrypted cookies created during 940 * the current request will not be available until the next request. 941 * 942 * @param string $name 943 * @param bool $deleteIfInvalid 944 * @return string|bool 945 */ 946 public function getEncryptedCookie($name, $deleteIfInvalid = true) 947 { 948 return $this->getCookie($name, $deleteIfInvalid); 949 } 950 951 /** 952 * Delete HTTP cookie (encrypted or unencrypted) 953 * 954 * Remove a Cookie from the client. This method will overwrite an existing Cookie 955 * with a new, empty, auto-expiring Cookie. This method's arguments must match 956 * the original Cookie's respective arguments for the original Cookie to be 957 * removed. If any of this method's arguments are omitted or set to NULL, the 958 * default Cookie setting values (set during Slim::init) will be used instead. 959 * 960 * @param string $name The cookie name 961 * @param string $path The path on the server in which the cookie will be available on 962 * @param string $domain The domain that the cookie is available to 963 * @param bool $secure Indicates that the cookie should only be transmitted over a secure 964 * HTTPS connection from the client 965 * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol 966 */ 967 public function deleteCookie($name, $path = null, $domain = null, $secure = null, $httponly = null) 968 { 969 $settings = array( 970 'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain, 971 'path' => is_null($path) ? $this->config('cookies.path') : $path, 972 'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure, 973 'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly 974 ); 975 $this->response->cookies->remove($name, $settings); 976 } 977 978 /******************************************************************************** 979 * Helper Methods 980 *******************************************************************************/ 981 982 /** 983 * Get the absolute path to this Slim application's root directory 984 * 985 * This method returns the absolute path to the Slim application's 986 * directory. If the Slim application is installed in a public-accessible 987 * sub-directory, the sub-directory path will be included. This method 988 * will always return an absolute path WITH a trailing slash. 989 * 990 * @return string 991 */ 992 public function root() 993 { 994 return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/'; 995 } 996 997 /** 998 * Clean current output buffer 999 */ 1000 protected function cleanBuffer() 1001 { 1002 if (ob_get_level() !== 0) { 1003 ob_clean(); 1004 } 1005 } 1006 1007 /** 1008 * Stop 1009 * 1010 * The thrown exception will be caught in application's `call()` method 1011 * and the response will be sent as is to the HTTP client. 1012 * 1013 * @throws \Slim\Exception\Stop 1014 */ 1015 public function stop() 1016 { 1017 throw new \Slim\Exception\Stop(); 1018 } 1019 1020 /** 1021 * Halt 1022 * 1023 * Stop the application and immediately send the response with a 1024 * specific status and body to the HTTP client. This may send any 1025 * type of response: info, success, redirect, client error, or server error. 1026 * If you need to render a template AND customize the response status, 1027 * use the application's `render()` method instead. 1028 * 1029 * @param int $status The HTTP response status 1030 * @param string $message The HTTP response body 1031 */ 1032 public function halt($status, $message = '') 1033 { 1034 $this->cleanBuffer(); 1035 $this->response->status($status); 1036 $this->response->body($message); 1037 $this->stop(); 1038 } 1039 1040 /** 1041 * Pass 1042 * 1043 * The thrown exception is caught in the application's `call()` method causing 1044 * the router's current iteration to stop and continue to the subsequent route if available. 1045 * If no subsequent matching routes are found, a 404 response will be sent to the client. 1046 * 1047 * @throws \Slim\Exception\Pass 1048 */ 1049 public function pass() 1050 { 1051 $this->cleanBuffer(); 1052 throw new \Slim\Exception\Pass(); 1053 } 1054 1055 /** 1056 * Set the HTTP response Content-Type 1057 * @param string $type The Content-Type for the Response (ie. text/html) 1058 */ 1059 public function contentType($type) 1060 { 1061 $this->response->headers->set('Content-Type', $type); 1062 } 1063 1064 /** 1065 * Set the HTTP response status code 1066 * @param int $code The HTTP response status code 1067 */ 1068 public function status($code) 1069 { 1070 $this->response->setStatus($code); 1071 } 1072 1073 /** 1074 * Get the URL for a named route 1075 * @param string $name The route name 1076 * @param array $params Associative array of URL parameters and replacement values 1077 * @throws \RuntimeException If named route does not exist 1078 * @return string 1079 */ 1080 public function urlFor($name, $params = array()) 1081 { 1082 return $this->request->getRootUri() . $this->router->urlFor($name, $params); 1083 } 1084 1085 /** 1086 * Redirect 1087 * 1088 * This method immediately redirects to a new URL. By default, 1089 * this issues a 302 Found response; this is considered the default 1090 * generic redirect response. You may also specify another valid 1091 * 3xx status code if you want. This method will automatically set the 1092 * HTTP Location header for you using the URL parameter. 1093 * 1094 * @param string $url The destination URL 1095 * @param int $status The HTTP redirect status code (optional) 1096 */ 1097 public function redirect($url, $status = 302) 1098 { 1099 $this->response->redirect($url, $status); 1100 $this->halt($status); 1101 } 1102 1103 /** 1104 * RedirectTo 1105 * 1106 * Redirects to a specific named route 1107 * 1108 * @param string $route The route name 1109 * @param array $params Associative array of URL parameters and replacement values 1110 */ 1111 public function redirectTo($route, $params = array(), $status = 302){ 1112 $this->redirect($this->urlFor($route, $params), $status); 1113 } 1114 1115 /******************************************************************************** 1116 * Flash Messages 1117 *******************************************************************************/ 1118 1119 /** 1120 * Set flash message for subsequent request 1121 * @param string $key 1122 * @param mixed $value 1123 */ 1124 public function flash($key, $value) 1125 { 1126 if (isset($this->environment['slim.flash'])) { 1127 $this->environment['slim.flash']->set($key, $value); 1128 } 1129 } 1130 1131 /** 1132 * Set flash message for current request 1133 * @param string $key 1134 * @param mixed $value 1135 */ 1136 public function flashNow($key, $value) 1137 { 1138 if (isset($this->environment['slim.flash'])) { 1139 $this->environment['slim.flash']->now($key, $value); 1140 } 1141 } 1142 1143 /** 1144 * Keep flash messages from previous request for subsequent request 1145 */ 1146 public function flashKeep() 1147 { 1148 if (isset($this->environment['slim.flash'])) { 1149 $this->environment['slim.flash']->keep(); 1150 } 1151 } 1152 1153 /******************************************************************************** 1154 * Hooks 1155 *******************************************************************************/ 1156 1157 /** 1158 * Assign hook 1159 * @param string $name The hook name 1160 * @param mixed $callable A callable object 1161 * @param int $priority The hook priority; 0 = high, 10 = low 1162 */ 1163 public function hook($name, $callable, $priority = 10) 1164 { 1165 if (!isset($this->hooks[$name])) { 1166 $this->hooks[$name] = array(array()); 1167 } 1168 if (is_callable($callable)) { 1169 $this->hooks[$name][(int) $priority][] = $callable; 1170 } 1171 } 1172 1173 /** 1174 * Invoke hook 1175 * @param string $name The hook name 1176 * @param mixed $hookArg (Optional) Argument for hooked functions 1177 */ 1178 public function applyHook($name, $hookArg = null) 1179 { 1180 if (!isset($this->hooks[$name])) { 1181 $this->hooks[$name] = array(array()); 1182 } 1183 if (!empty($this->hooks[$name])) { 1184 // Sort by priority, low to high, if there's more than one priority 1185 if (count($this->hooks[$name]) > 1) { 1186 ksort($this->hooks[$name]); 1187 } 1188 foreach ($this->hooks[$name] as $priority) { 1189 if (!empty($priority)) { 1190 foreach ($priority as $callable) { 1191 call_user_func($callable, $hookArg); 1192 } 1193 } 1194 } 1195 } 1196 } 1197 1198 /** 1199 * Get hook listeners 1200 * 1201 * Return an array of registered hooks. If `$name` is a valid 1202 * hook name, only the listeners attached to that hook are returned. 1203 * Else, all listeners are returned as an associative array whose 1204 * keys are hook names and whose values are arrays of listeners. 1205 * 1206 * @param string $name A hook name (Optional) 1207 * @return array|null 1208 */ 1209 public function getHooks($name = null) 1210 { 1211 if (!is_null($name)) { 1212 return isset($this->hooks[(string) $name]) ? $this->hooks[(string) $name] : null; 1213 } else { 1214 return $this->hooks; 1215 } 1216 } 1217 1218 /** 1219 * Clear hook listeners 1220 * 1221 * Clear all listeners for all hooks. If `$name` is 1222 * a valid hook name, only the listeners attached 1223 * to that hook will be cleared. 1224 * 1225 * @param string $name A hook name (Optional) 1226 */ 1227 public function clearHooks($name = null) 1228 { 1229 if (!is_null($name) && isset($this->hooks[(string) $name])) { 1230 $this->hooks[(string) $name] = array(array()); 1231 } else { 1232 foreach ($this->hooks as $key => $value) { 1233 $this->hooks[$key] = array(array()); 1234 } 1235 } 1236 } 1237 1238 /******************************************************************************** 1239 * Middleware 1240 *******************************************************************************/ 1241 1242 /** 1243 * Add middleware 1244 * 1245 * This method prepends new middleware to the application middleware stack. 1246 * The argument must be an instance that subclasses Slim_Middleware. 1247 * 1248 * @param \Slim\Middleware 1249 */ 1250 public function add(\Slim\Middleware $newMiddleware) 1251 { 1252 if(in_array($newMiddleware, $this->middleware)) { 1253 $middleware_class = get_class($newMiddleware); 1254 throw new \RuntimeException("Circular Middleware setup detected. Tried to queue the same Middleware instance ({$middleware_class}) twice."); 1255 } 1256 $newMiddleware->setApplication($this); 1257 $newMiddleware->setNextMiddleware($this->middleware[0]); 1258 array_unshift($this->middleware, $newMiddleware); 1259 } 1260 1261 /******************************************************************************** 1262 * Runner 1263 *******************************************************************************/ 1264 1265 /** 1266 * Run 1267 * 1268 * This method invokes the middleware stack, including the core Slim application; 1269 * the result is an array of HTTP status, header, and body. These three items 1270 * are returned to the HTTP client. 1271 */ 1272 public function run() 1273 { 1274 set_error_handler(array('\Slim\Slim', 'handleErrors')); 1275 1276 //Apply final outer middleware layers 1277 if ($this->config('debug')) { 1278 //Apply pretty exceptions only in debug to avoid accidental information leakage in production 1279 $this->add(new \Slim\Middleware\PrettyExceptions()); 1280 } 1281 1282 //Invoke middleware and application stack 1283 $this->middleware[0]->call(); 1284 1285 //Fetch status, header, and body 1286 list($status, $headers, $body) = $this->response->finalize(); 1287 1288 // Serialize cookies (with optional encryption) 1289 \Slim\Http\Util::serializeCookies($headers, $this->response->cookies, $this->settings); 1290 1291 //Send headers 1292 if (headers_sent() === false) { 1293 //Send status 1294 if (strpos(PHP_SAPI, 'cgi') === 0) { 1295 header(sprintf('Status: %s', \Slim\Http\Response::getMessageForCode($status))); 1296 } else { 1297 header(sprintf('HTTP/%s %s', $this->config('http.version'), \Slim\Http\Response::getMessageForCode($status))); 1298 } 1299 1300 //Send headers 1301 foreach ($headers as $name => $value) { 1302 $hValues = explode("\n", $value); 1303 foreach ($hValues as $hVal) { 1304 header("$name: $hVal", false); 1305 } 1306 } 1307 } 1308 1309 //Send body, but only if it isn't a HEAD request 1310 if (!$this->request->isHead()) { 1311 echo $body; 1312 } 1313 1314 $this->applyHook('slim.after'); 1315 1316 restore_error_handler(); 1317 } 1318 1319 /** 1320 * Call 1321 * 1322 * This method finds and iterates all route objects that match the current request URI. 1323 */ 1324 public function call() 1325 { 1326 try { 1327 if (isset($this->environment['slim.flash'])) { 1328 $this->view()->setData('flash', $this->environment['slim.flash']); 1329 } 1330 $this->applyHook('slim.before'); 1331 ob_start(); 1332 $this->applyHook('slim.before.router'); 1333 $dispatched = false; 1334 $matchedRoutes = $this->router->getMatchedRoutes($this->request->getMethod(), $this->request->getResourceUri()); 1335 foreach ($matchedRoutes as $route) { 1336 try { 1337 $this->applyHook('slim.before.dispatch'); 1338 $dispatched = $route->dispatch(); 1339 $this->applyHook('slim.after.dispatch'); 1340 if ($dispatched) { 1341 break; 1342 } 1343 } catch (\Slim\Exception\Pass $e) { 1344 continue; 1345 } 1346 } 1347 if (!$dispatched) { 1348 $this->notFound(); 1349 } 1350 $this->applyHook('slim.after.router'); 1351 $this->stop(); 1352 } catch (\Slim\Exception\Stop $e) { 1353 $this->response()->write(ob_get_clean()); 1354 } catch (\Exception $e) { 1355 if ($this->config('debug')) { 1356 throw $e; 1357 } else { 1358 try { 1359 $this->error($e); 1360 } catch (\Slim\Exception\Stop $e) { 1361 // Do nothing 1362 } 1363 } 1364 } 1365 } 1366 1367 /******************************************************************************** 1368 * Error Handling and Debugging 1369 *******************************************************************************/ 1370 1371 /** 1372 * Convert errors into ErrorException objects 1373 * 1374 * This method catches PHP errors and converts them into \ErrorException objects; 1375 * these \ErrorException objects are then thrown and caught by Slim's 1376 * built-in or custom error handlers. 1377 * 1378 * @param int $errno The numeric type of the Error 1379 * @param string $errstr The error message 1380 * @param string $errfile The absolute path to the affected file 1381 * @param int $errline The line number of the error in the affected file 1382 * @return bool 1383 * @throws \ErrorException 1384 */ 1385 public static function handleErrors($errno, $errstr = '', $errfile = '', $errline = '') 1386 { 1387 if (!($errno & error_reporting())) { 1388 return; 1389 } 1390 1391 throw new \ErrorException($errstr, $errno, 0, $errfile, $errline); 1392 } 1393 1394 /** 1395 * Generate diagnostic template markup 1396 * 1397 * This method accepts a title and body content to generate an HTML document layout. 1398 * 1399 * @param string $title The title of the HTML template 1400 * @param string $body The body content of the HTML template 1401 * @return string 1402 */ 1403 protected static function generateTemplateMarkup($title, $body) 1404 { 1405 return sprintf("<html><head><title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:65px;}</style></head><body><h1>%s</h1>%s</body></html>", $title, $title, $body); 1406 } 1407 1408 /** 1409 * Default Not Found handler 1410 */ 1411 protected function defaultNotFound() 1412 { 1413 echo static::generateTemplateMarkup('404 Page Not Found', '<p>The page you are looking for could not be found. Check the address bar to ensure your URL is spelled correctly. If all else fails, you can visit our home page at the link below.</p><a href="' . $this->request->getRootUri() . '/">Visit the Home Page</a>'); 1414 } 1415 1416 /** 1417 * Default Error handler 1418 */ 1419 protected function defaultError($e) 1420 { 1421 $this->getLog()->error($e); 1422 echo self::generateTemplateMarkup('Error', '<p>A website error has occurred. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.</p>'); 1423 } 1424 }