add external files for Routes (Espo/routes.json, Espo/Modules/Crm/routes.json)

This commit is contained in:
Taras Machyshyn
2013-12-23 17:24:40 +02:00
parent 714cf5a15c
commit 5a1a74b2bc
103 changed files with 4206 additions and 3217 deletions
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Espo\Controllers;
class App extends \Espo\Core\Controllers\Record
{
public function actionUser()
{
return '{"user":{"modified_by_name":"Administrator","created_by_name":"","id":"1","user_name":"admin","user_hash":"","system_generated_password":"0","pwd_last_changed":"","authenticate_id":"","sugar_login":"1","first_name":"","last_name":"Administrator","full_name":"Administrator","name":"Administrator","is_admin":"1","external_auth_only":"0","receive_notifications":"1","description":"","date_entered":"2013-06-13 12:18:44","date_modified":"2013-06-13 12:19:48","modified_user_id":"1","created_by":"","title":"Administrator","department":"","phone_home":"","phone_mobile":"","phone_work":"","phone_other":"","phone_fax":"","status":"Active","address_street":"","address_city":"","address_state":"","address_country":"","address_postalcode":"","UserType":"","deleted":"0","portal_only":"0","show_on_employees":"1","employee_status":"Active","messenger_id":"","messenger_type":"","reports_to_id":"","reports_to_name":"","email1":"test@letrium.com","email_link_type":"","is_group":"0","c_accept_status_fields":" ","m_accept_status_fields":" ","accept_status_id":"","accept_status_name":""},"preferences":{}}';
}
}
+1
View File
@@ -4,5 +4,6 @@ namespace Espo\Controllers;
class User extends \Espo\Core\Controllers\Record
{
}
+25 -30
View File
@@ -90,18 +90,10 @@ class Application
$auth = new \Espo\Core\Utils\Api\Auth($container->get('entityManager'), $container);
$this->getSlim()->add($auth);
$this->getSlim()->hook('slim.before.dispatch', function () use ($slim, $container) {
$conditions = $slim->router()->getCurrentRoute()->getConditions();
$routeParams = $slim->router()->getCurrentRoute()->getParams();
if (!empty($routeParams)) {
$slim->router()->getCurrentRoute()->setParams($routeParams);
}
});
$this->getSlim()->hook('slim.before.dispatch', function () use ($slim, $container, $serviceFactory) {
$route = $slim->router()->getCurrentRoute();
$conditions = $route->getConditions();
$conditions = $route->getConditions();
if (isset($conditions['useController']) && $conditions['useController'] == false) {
return;
@@ -123,7 +115,7 @@ class Application
$value = $params[$paramName];
}
$controllerParams[$key] = $value;
}
}
$controllerName = ucfirst($controllerParams['controller']);
@@ -135,7 +127,7 @@ class Application
}
try {
$controllerManager = new \Espo\Core\ControllerManager($container, $serviceFactory);
$controllerManager = new \Espo\Core\ControllerManager($container, $serviceFactory);
$result = $controllerManager->process($controllerName, $actionName, $params, $data);
$container->get('output')->render($result);
} catch (\Exception $e) {
@@ -152,26 +144,28 @@ class Application
protected function initRoutes()
{
//$this->getSlim()->get('/', '\Espo\Utils\Api\Rest::main')->conditions( array('useController' => false) );
$routes = new \Espo\Core\Utils\Route($this->getContainer()->get('config'), $this->getContainer()->get('fileManager'));
//TODO move routing to metadata
$routes = array(
array(
'method' => 'get',
'route' => 'metadata',
'params' => array(
'controller' => 'Metadata',
),
),
array(
'method' => 'get',
'route' => 'settings',
'params' => array(
'controller' => 'Settings',
),
),
);
$crudList = array_keys( (array) $this->getContainer()->get('config')->get('crud') );
foreach($routes->getAll() as $route) {
$method = strtolower($route['method']);
if (!in_array($method, $crudList)) {
$GLOBALS['log']->add('ERROR', 'Route: Method ['.$method.'] does not exist. Please check your route ['.$route['route'].']');
continue;
}
$currentRoute = $this->getSlim()->$method($route['route'], function() use ($route) { //todo change "use" for php 5.4
return $route['params'];
});
if (isset($route['conditions'])) {
$currentRoute->conditions($route['conditions']);
}
}
/* //todo remove when it is tested
$this->getSlim()->get('/', function() {
return $template = "<h1>EspoCRM REST API</h1>";
});
@@ -270,7 +264,8 @@ class Application
'link' => ':link',
'foreignId' => ':foreignId'
);
});
}); */
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
namespace Espo\Core\Utils;
class Route
{
protected $fileName = 'routes.json';
protected $cacheFileName = 'application/routes.php';
protected $data = null;
private $fileManager;
private $config;
public function __construct(Config $config, File\Manager $fileManager)
{
$this->config = $config;
$this->fileManager = $fileManager;
}
protected function getConfig()
{
return $this->config;
}
protected function getFileManager()
{
return $this->fileManager;
}
public function get($key = '', $returns = null)
{
if (!isset($this->data)) {
$this->init();
}
if (empty($key)) {
return $this->data;
}
$keys = explode('.', $key);
$lastRoute = $this->data;
foreach($keys as $keyName) {
if (isset($lastRoute[$keyName]) && is_array($lastRoute)) {
$lastRoute = $lastRoute[$keyName];
} else {
return $returns;
}
}
return $lastRoute;
}
public function getAll()
{
return $this->get();
}
protected function init()
{
$cacheFile = Util::concatPath($this->getConfig()->get('cachePath'), $this->cacheFileName);
if (file_exists($cacheFile) && $this->getConfig()->get('useCache')) {
$this->data = $this->getFileManager()->getContent($cacheFile);
} else {
$this->data = $this->uniteFiles();
$result = $this->getFileManager()->setContentPHP($this->data, $cacheFile);
if ($result == false) {
$GLOBALS['log']->add('EXCEPTION', 'Route::init() - Cannot save Routes to a file');
throw new \Espo\Core\Exceptions\Error();
}
}
}
protected function uniteFiles($isCustom = false)
{
$dirName = $isCustom ? '/Custom' : '';
$data = array();
$moduleDir = 'application/Espo'.$dirName.'/Modules';
if (file_exists($moduleDir)) {
$dirList= $this->getFileManager()->getFileList($moduleDir, false, '', 'dir');
foreach($dirList as $currentDirName) {
$dirNameFull = Util::concatPath($moduleDir, $currentDirName);
$routeFile = Util::concatPath($dirNameFull, $this->fileName);
$data = $this->getAddData($data, $routeFile);
}
}
//if need this path to high priority, move up this code
$routeFile = Util::concatPath('application/Espo'.$dirName, $this->fileName);
$data = $this->getAddData($data, $routeFile);
if (!$isCustom) {
$data = $this->addToData($this->uniteFiles(true), $data);
}
return $data;
}
protected function getAddData($currData, $routeFile)
{
if (file_exists($routeFile)) {
$content= $this->getFileManager()->getContent($routeFile);
$arrayContent = Json::getArrayData($content);
if (empty($arrayContent)) {
$GLOBALS['log']->add('ERROR', 'Route::uniteFiles() - Empty file or syntax error - ['.$routeFile.']');
return $currData;
}
$currData = $this->addToData($currData, $arrayContent);
}
return $currData;
}
protected function addToData($data, $newData)
{
if (!is_array($newData)) {
return $data;
}
foreach($newData as $route) {
$data[] = $route;
}
return $data;
}
}
+2
View File
@@ -0,0 +1,2 @@
[
]
+140
View File
@@ -0,0 +1,140 @@
[
{
"route":"/",
"method":"get",
"params":"<h1>EspoCRM REST API<\/h1>"
},
{
"route":"/App/user/",
"method":"get",
"params":{
"controller":"App",
"action":"user"
}
},
{
"route":"/Metadata/",
"method":"get",
"params":{
"controller":"Metadata"
}
},
{
"route":"/Settings/",
"method":"get",
"params":{
"controller":"Settings"
},
"conditions":{
"auth":false
}
},
{
"route":"/Settings/",
"method":"patch",
"params":{
"controller":"Settings"
}
},
{
"route":"/:controller/layout/:name/",
"method":"get",
"params":{
"controller":"Layout",
"scope":":controller"
}
},
{
"route":"/:controller/layout/:name/",
"method":"put",
"params":{
"controller":"Layout",
"scope":":controller"
}
},
{
"route":"/:controller/layout/:name/",
"method":"patch",
"params":{
"controller":"Layout",
"scope":":controller"
}
},
{
"route":"/Admin/rebuild/",
"method":"get",
"params":{
"controller":"Admin",
"action":"rebuild"
}
},
{
"route":"/:controller/:id/",
"method":"get",
"params":{
"controller":":controller",
"action":"read",
"id":":id"
}
},
{
"route":"/:controller/",
"method":"get",
"params":{
"controller":":controller",
"action":"index"
}
},
{
"route":"/:controller/",
"method":"post",
"params":{
"controller":":controller",
"action":"create"
}
},
{
"route":"/:controller/:id/",
"method":"put",
"params":{
"controller":":controller",
"action":"update",
"id":":id"
}
},
{
"route":"/:controller/:id/",
"method":"patch",
"params":{
"controller":":controller",
"action":"patch",
"id":":id"
}
},
{
"route":"/:controller/:id/:link/:foreignId/",
"method":"get",
"params":{
"controller":":controller",
"action":"readRelated",
"id":":id",
"link":":link",
"foreignId":":foreignId"
}
}
]
Generated
+20 -17
View File
@@ -554,21 +554,24 @@
},
{
"name": "slim/slim",
"version": "2.2.0",
"version": "2.4.0",
"source": {
"type": "git",
"url": "https://github.com/codeguy/Slim.git",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053"
"reference": "ff7d7148848fa4f45712984d7b91ac3cbced586b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/codeguy/Slim/zipball/b8181de1112a1e2f565b40158b621c34ded38053",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053",
"url": "https://api.github.com/repos/codeguy/Slim/zipball/ff7d7148848fa4f45712984d7b91ac3cbced586b",
"reference": "ff7d7148848fa4f45712984d7b91ac3cbced586b",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"suggest": {
"ext-mcrypt": "Required for HTTP cookie encryption"
},
"type": "library",
"autoload": {
"psr-0": {
@@ -593,21 +596,21 @@
"rest",
"router"
],
"time": "2012-12-13 02:15:50"
"time": "2013-11-29 20:33:36"
},
{
"name": "symfony/console",
"version": "v2.3.7",
"version": "v2.4.0",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18"
"reference": "3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/00848d3e13cf512e77c7498c2b3b0192f61f4b18",
"reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18",
"url": "https://api.github.com/repos/symfony/Console/zipball/3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c",
"reference": "3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c",
"shasum": ""
},
"require": {
@@ -622,7 +625,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
"dev-master": "2.4-dev"
}
},
"autoload": {
@@ -646,21 +649,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2013-11-13 21:27:40"
"time": "2013-11-27 09:10:40"
},
{
"name": "symfony/yaml",
"version": "v2.3.7",
"version": "v2.4.0",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "c1bda5b459d792cb253de12c65beba3040163b2b"
"reference": "1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/c1bda5b459d792cb253de12c65beba3040163b2b",
"reference": "c1bda5b459d792cb253de12c65beba3040163b2b",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5",
"reference": "1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5",
"shasum": ""
},
"require": {
@@ -669,7 +672,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
"dev-master": "2.4-dev"
}
},
"autoload": {
@@ -693,7 +696,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2013-10-17 11:48:01"
"time": "2013-11-26 16:40:27"
}
],
"packages-dev": [
Regular → Executable
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitcb7c1594bf378622122ae8e77daf1532::getLoader();
return ComposerAutoloaderInit1983a0e55fe28bbf984fa65ef179598e::getLoader();
+3 -3
View File
@@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitcb7c1594bf378622122ae8e77daf1532
class ComposerAutoloaderInit1983a0e55fe28bbf984fa65ef179598e
{
private static $loader;
@@ -19,9 +19,9 @@ class ComposerAutoloaderInitcb7c1594bf378622122ae8e77daf1532
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitcb7c1594bf378622122ae8e77daf1532', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit1983a0e55fe28bbf984fa65ef179598e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitcb7c1594bf378622122ae8e77daf1532', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit1983a0e55fe28bbf984fa65ef179598e', 'loadClassLoader'));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
+115 -112
View File
@@ -559,61 +559,6 @@
"spl"
]
},
{
"name": "symfony/console",
"version": "v2.3.7",
"version_normalized": "2.3.7.0",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/00848d3e13cf512e77c7498c2b3b0192f61f4b18",
"reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/event-dispatcher": "~2.1"
},
"suggest": {
"symfony/event-dispatcher": ""
},
"time": "2013-11-13 21:27:40",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com"
},
{
"name": "doctrine/dbal",
"version": "v2.4.1",
@@ -760,55 +705,6 @@
"orm"
]
},
{
"name": "symfony/yaml",
"version": "v2.3.7",
"version_normalized": "2.3.7.0",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "c1bda5b459d792cb253de12c65beba3040163b2b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/c1bda5b459d792cb253de12c65beba3040163b2b",
"reference": "c1bda5b459d792cb253de12c65beba3040163b2b",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2013-10-17 11:48:01",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com"
},
{
"name": "phpunit/php-token-stream",
"version": "1.2.1",
@@ -1048,24 +944,76 @@
]
},
{
"name": "slim/slim",
"version": "2.2.0",
"version_normalized": "2.2.0.0",
"name": "symfony/yaml",
"version": "v2.4.0",
"version_normalized": "2.4.0.0",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/codeguy/Slim.git",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053"
"url": "https://github.com/symfony/Yaml.git",
"reference": "1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/codeguy/Slim/zipball/b8181de1112a1e2f565b40158b621c34ded38053",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5",
"reference": "1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2013-11-26 16:40:27",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com"
},
{
"name": "slim/slim",
"version": "2.4.0",
"version_normalized": "2.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/codeguy/Slim.git",
"reference": "ff7d7148848fa4f45712984d7b91ac3cbced586b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/codeguy/Slim/zipball/ff7d7148848fa4f45712984d7b91ac3cbced586b",
"reference": "ff7d7148848fa4f45712984d7b91ac3cbced586b",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"time": "2012-12-13 02:15:50",
"suggest": {
"ext-mcrypt": "Required for HTTP cookie encryption"
},
"time": "2013-11-29 20:33:36",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1091,5 +1039,60 @@
"rest",
"router"
]
},
{
"name": "symfony/console",
"version": "v2.4.0",
"version_normalized": "2.4.0.0",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c",
"reference": "3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/event-dispatcher": "~2.1"
},
"suggest": {
"symfony/event-dispatcher": ""
},
"time": "2013-11-27 09:10:40",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com"
}
]
+1
View File
@@ -3,5 +3,6 @@ language: php
php:
- 5.3
- 5.4
- 5.5
script: phpunit --coverage-text
+54 -5
View File
@@ -1,6 +1,6 @@
# Slim Framework
[![Build Status](https://secure.travis-ci.org/codeguy/Slim.png)](http://travis-ci.org/codeguy/Slim)
[![Build Status](https://secure.travis-ci.org/codeguy/Slim.png?branch=master)](http://travis-ci.org/codeguy/Slim)
Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
Slim is easy to use for both beginners and professionals. Slim favors cleanliness over terseness and common cases
@@ -14,6 +14,7 @@ Thank you for choosing the Slim Framework for your next project. I think you're
* Route parameters with wildcards and conditions
* Route redirect, halt, and pass
* Route middleware
* Resource Locator and DI container
* Template rendering with custom views
* Flash messages
* Secure cookies with AES-256 encryption
@@ -29,7 +30,7 @@ Thank you for choosing the Slim Framework for your next project. I think you're
You may install the Slim Framework with Composer (recommended) or manually.
[Read how to install Slim](http://docs.slimframework.com/pages/getting-started-install)
[Read how to install Slim](http://docs.slimframework.com/#Installation)
### System Requirements
@@ -64,12 +65,38 @@ should contain this code:
#### Nginx
Your nginx configuration file should contain this code (along with other settings you may need) in your `location` block:
The nginx configuration file should contain this code (along with other settings you may need) in your `location` block:
try_files $uri $uri/ /index.php;
try_files $uri $uri/ /index.php?$args;
This assumes that Slim's `index.php` is in the root folder of your project (www root).
#### HipHop Virtual Machine for PHP
Your HipHop Virtual Machine configuration file should contain this code (along with other settings you may need).
Be sure you change the `ServerRoot` setting to point to your Slim app's document root directory.
Server {
SourceRoot = /path/to/public/directory
}
ServerVariables {
SCRIPT_NAME = /index.php
}
VirtualHost {
* {
Pattern = .*
RewriteRules {
* {
pattern = ^(.*)$
to = index.php/$1
qsa = true
}
}
}
}
#### lighttpd ####
Your lighttpd configuration file should contain this code (along with other settings you may need). This code requires
@@ -79,6 +106,28 @@ lighttpd >= 1.4.24.
This assumes that Slim's `index.php` is in the root folder of your project (www root).
#### IIS
Ensure the `Web.config` and `index.php` files are in the same public-accessible directory. The `Web.config` file should contain this code:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="slim" patternSyntax="Wildcard">
<match url="*" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
## Documentation
<http://docs.slimframework.com/>
@@ -118,7 +167,7 @@ Follow [@slimphp](http://www.twitter.com/slimphp) on Twitter to receive news and
## Author
The Slim Framework is created and maintained by [Josh Lockhart](https://www.joshlockhart.com). Josh is a senior
The Slim Framework is created and maintained by [Josh Lockhart](http://www.joshlockhart.com). Josh is a senior
web developer at [New Media Campaigns](http://www.newmediacampaigns.com/). Josh also created and maintains
[PHP: The Right Way](http://www.phptherightway.com/), a popular movement in the PHP community to introduce new
PHP programmers to best practices and good information.
+29 -42
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -87,7 +87,7 @@ class Environment implements \ArrayAccess, \IteratorAggregate
*/
public static function mock($userSettings = array())
{
self::$environment = new self(array_merge(array(
$defaults = array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '',
'PATH_INFO' => '',
@@ -102,7 +102,8 @@ class Environment implements \ArrayAccess, \IteratorAggregate
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => @fopen('php://stderr', 'w')
), $userSettings));
);
self::$environment = new self(array_merge($defaults, $userSettings));
return self::$environment;
}
@@ -125,35 +126,26 @@ class Environment implements \ArrayAccess, \IteratorAggregate
//The IP
$env['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
/**
* Application paths
*
* This derives two paths: SCRIPT_NAME and PATH_INFO. The SCRIPT_NAME
* is the real, physical path to the application, be it in the root
* directory or a subdirectory of the public document root. The PATH_INFO is the
* virtual path to the requested resource within the application context.
*
* With htaccess, the SCRIPT_NAME will be an absolute path (without file name);
* if not using htaccess, it will also include the file name. If it is "/",
* it is set to an empty string (since it cannot have a trailing slash).
*
* The PATH_INFO will be an absolute path with a leading slash; this will be
* used for application routing.
*/
if (strpos($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']) === 0) {
$env['SCRIPT_NAME'] = $_SERVER['SCRIPT_NAME']; //Without URL rewrite
} else {
$env['SCRIPT_NAME'] = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']) ); //With URL rewrite
}
$env['PATH_INFO'] = substr_replace($_SERVER['REQUEST_URI'], '', 0, strlen($env['SCRIPT_NAME']));
if (strpos($env['PATH_INFO'], '?') !== false) {
$env['PATH_INFO'] = substr_replace($env['PATH_INFO'], '', strpos($env['PATH_INFO'], '?')); //query string is not removed automatically
}
$env['SCRIPT_NAME'] = rtrim($env['SCRIPT_NAME'], '/');
$env['PATH_INFO'] = '/' . ltrim($env['PATH_INFO'], '/');
// Server params
$scriptName = $_SERVER['SCRIPT_NAME']; // <-- "/foo/index.php"
$requestUri = $_SERVER['REQUEST_URI']; // <-- "/foo/bar?test=abc" or "/foo/index.php/bar?test=abc"
$queryString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; // <-- "test=abc" or ""
//The portion of the request URI following the '?'
$env['QUERY_STRING'] = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
// Physical path
if (strpos($requestUri, $scriptName) !== false) {
$physicalPath = $scriptName; // <-- Without rewriting
} else {
$physicalPath = str_replace('\\', '', dirname($scriptName)); // <-- With rewriting
}
$env['SCRIPT_NAME'] = rtrim($physicalPath, '/'); // <-- Remove trailing slashes
// Virtual path
$env['PATH_INFO'] = substr_replace($requestUri, '', 0, strlen($physicalPath)); // <-- Remove physical path
$env['PATH_INFO'] = str_replace('?' . $queryString, '', $env['PATH_INFO']); // <-- Remove query string
$env['PATH_INFO'] = '/' . ltrim($env['PATH_INFO'], '/'); // <-- Ensure leading slash
// Query string (without leading "?")
$env['QUERY_STRING'] = $queryString;
//Name of server host that is running the script
$env['SERVER_NAME'] = $_SERVER['SERVER_NAME'];
@@ -161,21 +153,16 @@ class Environment implements \ArrayAccess, \IteratorAggregate
//Number of server port that is running the script
$env['SERVER_PORT'] = $_SERVER['SERVER_PORT'];
//HTTP request headers
$specialHeaders = array('CONTENT_TYPE', 'CONTENT_LENGTH', 'PHP_AUTH_USER', 'PHP_AUTH_PW', 'PHP_AUTH_DIGEST', 'AUTH_TYPE');
foreach ($_SERVER as $key => $value) {
$value = is_string($value) ? trim($value) : $value;
if (strpos($key, 'HTTP_') === 0) {
$env[substr($key, 5)] = $value;
} elseif (strpos($key, 'X_') === 0 || in_array($key, $specialHeaders)) {
$env[$key] = $value;
}
//HTTP request headers (retains HTTP_ prefix to match $_SERVER)
$headers = \Slim\Http\Headers::extract($_SERVER);
foreach ($headers as $key => $value) {
$env[$key] = $value;
}
//Is the application running under HTTPS or HTTP protocol?
$env['slim.url_scheme'] = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http' : 'https';
//Input stream (readable one time only; not available for mutipart/form-data requests)
//Input stream (readable one time only; not available for multipart/form-data requests)
$rawInput = @file_get_contents('php://input');
if (!$rawInput) {
$rawInput = '';
@@ -183,7 +170,7 @@ class Environment implements \ArrayAccess, \IteratorAggregate
$env['slim.input'] = $rawInput;
//Error stream
$env['slim.errors'] = fopen('php://stderr', 'w');
$env['slim.errors'] = @fopen('php://stderr', 'w');
$this->properties = $env;
}
+1 -2
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -46,5 +46,4 @@ namespace Slim\Exception;
*/
class Pass extends \Exception
{
}
+1 -2
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -44,5 +44,4 @@ namespace Slim\Exception;
*/
class Stop extends \Exception
{
}
+45 -122
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -35,147 +35,70 @@ namespace Slim\Http;
/**
* HTTP Headers
*
* This class is an abstraction of the HTTP response headers and
* provides array access to the header list while automatically
* stores and retrieves headers with lowercase canonical keys regardless
* of the input format.
*
* This class also implements the `Iterator` and `Countable`
* interfaces for even more convenient usage.
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class Headers implements \ArrayAccess, \Iterator, \Countable
class Headers extends \Slim\Helper\Set
{
/**
* @var array HTTP headers
*/
protected $headers;
/********************************************************************************
* Static interface
*******************************************************************************/
/**
* @var array Map canonical header name to original header name
* Special-case HTTP headers that are otherwise unidentifiable as HTTP headers.
* Typically, HTTP headers in the $_SERVER array will be prefixed with
* `HTTP_` or `X_`. These are not so we list them here for later reference.
*
* @var array
*/
protected $map;
protected static $special = array(
'CONTENT_TYPE',
'CONTENT_LENGTH',
'PHP_AUTH_USER',
'PHP_AUTH_PW',
'PHP_AUTH_DIGEST',
'AUTH_TYPE'
);
/**
* Constructor
* @param array $headers
* Extract HTTP headers from an array of data (e.g. $_SERVER)
* @param array $data
* @return array
*/
public function __construct($headers = array())
public static function extract($data)
{
$this->merge($headers);
}
/**
* Merge Headers
* @param array $headers
*/
public function merge($headers)
{
foreach ($headers as $name => $value) {
$this[$name] = $value;
$results = array();
foreach ($data as $key => $value) {
$key = strtoupper($key);
if (strpos($key, 'X_') === 0 || strpos($key, 'HTTP_') === 0 || in_array($key, static::$special)) {
if ($key === 'HTTP_CONTENT_TYPE' || $key === 'HTTP_CONTENT_LENGTH') {
continue;
}
$results[$key] = $value;
}
}
return $results;
}
/********************************************************************************
* Instance interface
*******************************************************************************/
/**
* Transform header name into canonical form
* @param string $name
* @param string $key
* @return string
*/
protected function canonical($name)
protected function normalizeKey($key)
{
return strtolower(trim($name));
}
$key = strtolower($key);
$key = str_replace(array('-', '_'), ' ', $key);
$key = preg_replace('#^http #', '', $key);
$key = ucwords($key);
$key = str_replace(' ', '-', $key);
/**
* Array Access: Offset Exists
*/
public function offsetExists($offset)
{
return isset($this->headers[$this->canonical($offset)]);
}
/**
* Array Access: Offset Get
*/
public function offsetGet($offset)
{
$canonical = $this->canonical($offset);
if (isset($this->headers[$canonical])) {
return $this->headers[$canonical];
} else {
return null;
}
}
/**
* Array Access: Offset Set
*/
public function offsetSet($offset, $value)
{
$canonical = $this->canonical($offset);
$this->headers[$canonical] = $value;
$this->map[$canonical] = $offset;
}
/**
* Array Access: Offset Unset
*/
public function offsetUnset($offset)
{
$canonical = $this->canonical($offset);
unset($this->headers[$canonical], $this->map[$canonical]);
}
/**
* Countable: Count
*/
public function count()
{
return count($this->headers);
}
/**
* Iterator: Rewind
*/
public function rewind()
{
reset($this->headers);
}
/**
* Iterator: Current
*/
public function current()
{
return current($this->headers);
}
/**
* Iterator: Key
*/
public function key()
{
$key = key($this->headers);
return $this->map[$key];
}
/**
* Iterator: Next
*/
public function next()
{
return next($this->headers);
}
/**
* Iterator: Valid
*/
public function valid()
{
return current($this->headers) !== false;
return $key;
}
}
+112 -83
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -48,6 +48,7 @@ class Request
const METHOD_GET = 'GET';
const METHOD_POST = 'POST';
const METHOD_PUT = 'PUT';
const METHOD_PATCH = 'PATCH';
const METHOD_DELETE = 'DELETE';
const METHOD_OPTIONS = 'OPTIONS';
const METHOD_OVERRIDE = '_METHOD';
@@ -58,18 +59,32 @@ class Request
protected static $formDataMediaTypes = array('application/x-www-form-urlencoded');
/**
* @var array
* Application Environment
* @var \Slim\Environment
*/
protected $env;
/**
* Constructor
* @param array $env
* @see \Slim\Environment
* HTTP Headers
* @var \Slim\Http\Headers
*/
public function __construct($env)
public $headers;
/**
* HTTP Cookies
* @var \Slim\Helper\Set
*/
public $cookies;
/**
* Constructor
* @param \Slim\Environment $env
*/
public function __construct(\Slim\Environment $env)
{
$this->env = $env;
$this->headers = new \Slim\Http\Headers(\Slim\Http\Headers::extract($env));
$this->cookies = new \Slim\Helper\Set(\Slim\Http\Util::parseCookieHeader($env['HTTP_COOKIE']));
}
/**
@@ -108,6 +123,15 @@ class Request
return $this->getMethod() === self::METHOD_PUT;
}
/**
* Is this a PATCH request?
* @return bool
*/
public function isPatch()
{
return $this->getMethod() === self::METHOD_PATCH;
}
/**
* Is this a DELETE request?
* @return bool
@@ -143,7 +167,7 @@ class Request
{
if ($this->params('isajax')) {
return true;
} elseif (isset($this->env['X_REQUESTED_WITH']) && $this->env['X_REQUESTED_WITH'] === 'XMLHttpRequest') {
} elseif (isset($this->headers['X_REQUESTED_WITH']) && $this->headers['X_REQUESTED_WITH'] === 'XMLHttpRequest') {
return true;
} else {
return false;
@@ -172,14 +196,10 @@ class Request
{
$union = array_merge($this->get(), $this->post());
if ($key) {
if (isset($union[$key])) {
return $union[$key];
} else {
return null;
}
} else {
return $union;
return isset($union[$key]) ? $union[$key] : null;
}
return $union;
}
/**
@@ -189,9 +209,10 @@ class Request
* the value of the array key if requested; if the array key does not exist, NULL is returned.
*
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function get($key = null)
public function get($key = null, $default = null)
{
if (!isset($this->env['slim.request.query_hash'])) {
$output = array();
@@ -206,7 +227,7 @@ class Request
if (isset($this->env['slim.request.query_hash'][$key])) {
return $this->env['slim.request.query_hash'][$key];
} else {
return null;
return $default;
}
} else {
return $this->env['slim.request.query_hash'];
@@ -220,10 +241,11 @@ class Request
* the value of a hash key if requested; if the array key does not exist, NULL is returned.
*
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
* @throws \RuntimeException If environment input is not available
*/
public function post($key = null)
public function post($key = null, $default = null)
{
if (!isset($this->env['slim.input'])) {
throw new \RuntimeException('Missing slim.input in environment variables');
@@ -246,7 +268,7 @@ class Request
if (isset($this->env['slim.request.form_hash'][$key])) {
return $this->env['slim.request.form_hash'][$key];
} else {
return null;
return $default;
}
} else {
return $this->env['slim.request.form_hash'];
@@ -256,21 +278,34 @@ class Request
/**
* Fetch PUT data (alias for \Slim\Http\Request::post)
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function put($key = null)
public function put($key = null, $default = null)
{
return $this->post($key);
return $this->post($key, $default);
}
/**
* Fetch PATCH data (alias for \Slim\Http\Request::post)
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function patch($key = null, $default = null)
{
return $this->post($key, $default);
}
/**
* Fetch DELETE data (alias for \Slim\Http\Request::post)
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function delete($key = null)
public function delete($key = null, $default = null)
{
return $this->post($key);
return $this->post($key, $default);
}
/**
@@ -284,23 +319,28 @@ class Request
*/
public function cookies($key = null)
{
if (!isset($this->env['slim.request.cookie_hash'])) {
$cookieHeader = isset($this->env['COOKIE']) ? $this->env['COOKIE'] : '';
$this->env['slim.request.cookie_hash'] = Util::parseCookieHeader($cookieHeader);
}
if ($key) {
if (isset($this->env['slim.request.cookie_hash'][$key])) {
return $this->env['slim.request.cookie_hash'][$key];
} else {
return null;
}
} else {
return $this->env['slim.request.cookie_hash'];
return $this->cookies->get($key);
}
return $this->cookies;
// if (!isset($this->env['slim.request.cookie_hash'])) {
// $cookieHeader = isset($this->env['COOKIE']) ? $this->env['COOKIE'] : '';
// $this->env['slim.request.cookie_hash'] = Util::parseCookieHeader($cookieHeader);
// }
// if ($key) {
// if (isset($this->env['slim.request.cookie_hash'][$key])) {
// return $this->env['slim.request.cookie_hash'][$key];
// } else {
// return null;
// }
// } else {
// return $this->env['slim.request.cookie_hash'];
// }
}
/**
* Does the Request body contain parseable form data?
* Does the Request body contain parsed form data?
* @return bool
*/
public function isFormData()
@@ -323,24 +363,29 @@ class Request
public function headers($key = null, $default = null)
{
if ($key) {
$key = strtoupper($key);
$key = str_replace('-', '_', $key);
$key = preg_replace('@^HTTP_@', '', $key);
if (isset($this->env[$key])) {
return $this->env[$key];
} else {
return $default;
}
} else {
$headers = array();
foreach ($this->env as $key => $value) {
if (strpos($key, 'slim.') !== 0) {
$headers[$key] = $value;
}
}
return $headers;
return $this->headers->get($key, $default);
}
return $this->headers;
// if ($key) {
// $key = strtoupper($key);
// $key = str_replace('-', '_', $key);
// $key = preg_replace('@^HTTP_@', '', $key);
// if (isset($this->env[$key])) {
// return $this->env[$key];
// } else {
// return $default;
// }
// } else {
// $headers = array();
// foreach ($this->env as $key => $value) {
// if (strpos($key, 'slim.') !== 0) {
// $headers[$key] = $value;
// }
// }
//
// return $headers;
// }
}
/**
@@ -354,15 +399,11 @@ class Request
/**
* Get Content Type
* @return string
* @return string|null
*/
public function getContentType()
{
if (isset($this->env['CONTENT_TYPE'])) {
return $this->env['CONTENT_TYPE'];
} else {
return null;
}
return $this->headers->get('CONTENT_TYPE');
}
/**
@@ -376,9 +417,9 @@ class Request
$contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
return strtolower($contentTypeParts[0]);
} else {
return null;
}
return null;
}
/**
@@ -410,9 +451,9 @@ class Request
$mediaTypeParams = $this->getMediaTypeParams();
if (isset($mediaTypeParams['charset'])) {
return $mediaTypeParams['charset'];
} else {
return null;
}
return null;
}
/**
@@ -421,11 +462,7 @@ class Request
*/
public function getContentLength()
{
if (isset($this->env['CONTENT_LENGTH'])) {
return (int) $this->env['CONTENT_LENGTH'];
} else {
return 0;
}
return $this->headers->get('CONTENT_LENGTH', 0);
}
/**
@@ -434,17 +471,17 @@ class Request
*/
public function getHost()
{
if (isset($this->env['HOST'])) {
if (strpos($this->env['HOST'], ':') !== false) {
$hostParts = explode(':', $this->env['HOST']);
if (isset($this->env['HTTP_HOST'])) {
if (strpos($this->env['HTTP_HOST'], ':') !== false) {
$hostParts = explode(':', $this->env['HTTP_HOST']);
return $hostParts[0];
}
return $this->env['HOST'];
} else {
return $this->env['SERVER_NAME'];
return $this->env['HTTP_HOST'];
}
return $this->env['SERVER_NAME'];
}
/**
@@ -462,7 +499,7 @@ class Request
*/
public function getPort()
{
return (int) $this->env['SERVER_PORT'];
return (int)$this->env['SERVER_PORT'];
}
/**
@@ -554,11 +591,7 @@ class Request
*/
public function getReferrer()
{
if (isset($this->env['REFERER'])) {
return $this->env['REFERER'];
} else {
return null;
}
return $this->headers->get('HTTP_REFERER');
}
/**
@@ -576,10 +609,6 @@ class Request
*/
public function getUserAgent()
{
if (isset($this->env['USER_AGENT'])) {
return $this->env['USER_AGENT'];
} else {
return null;
}
return $this->headers->get('HTTP_USER_AGENT');
}
}
+110 -57
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -51,9 +51,14 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
protected $status;
/**
* @var \Slim\Http\Headers List of HTTP response headers
* @var \Slim\Http\Headers
*/
protected $header;
public $headers;
/**
* @var \Slim\Http\Cookies
*/
public $cookies;
/**
* @var string HTTP response body
@@ -108,6 +113,7 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
415 => '415 Unsupported Media Type',
416 => '416 Requested Range Not Satisfiable',
417 => '417 Expectation Failed',
418 => '418 I\'m a teapot',
422 => '422 Unprocessable Entity',
423 => '423 Locked',
//Server Error 5xx
@@ -123,21 +129,30 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
* Constructor
* @param string $body The HTTP response body
* @param int $status The HTTP response status
* @param \Slim\Http\Headers|array $header The HTTP response headers
* @param \Slim\Http\Headers|array $headers The HTTP response headers
*/
public function __construct($body = '', $status = 200, $header = array())
public function __construct($body = '', $status = 200, $headers = array())
{
$this->status = (int) $status;
$headers = array();
foreach ($header as $key => $value) {
$headers[$key] = $value;
}
$this->header = new Headers(array_merge(array('Content-Type' => 'text/html'), $headers));
$this->body = '';
$this->setStatus($status);
$this->headers = new \Slim\Http\Headers(array('Content-Type' => 'text/html'));
$this->headers->replace($headers);
$this->cookies = new \Slim\Http\Cookies();
$this->write($body);
}
public function getStatus()
{
return $this->status;
}
public function setStatus($status)
{
$this->status = (int)$status;
}
/**
* DEPRECATION WARNING! Use `getStatus` or `setStatus` instead.
*
* Get and set status
* @param int|null $status
* @return int
@@ -152,6 +167,8 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
}
/**
* DEPRECATION WARNING! Access `headers` property directly.
*
* Get and set header
* @param string $name Header name
* @param string|null $value Header value
@@ -160,22 +177,36 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
public function header($name, $value = null)
{
if (!is_null($value)) {
$this[$name] = $value;
$this->headers->set($name, $value);
}
return $this[$name];
return $this->headers->get($name);
}
/**
* DEPRECATION WARNING! Access `headers` property directly.
*
* Get headers
* @return \Slim\Http\Headers
*/
public function headers()
{
return $this->header;
return $this->headers;
}
public function getBody()
{
return $this->body;
}
public function setBody($content)
{
$this->write($content, true);
}
/**
* DEPRECATION WARNING! use `getBody` or `setBody` instead.
*
* Get and set body
* @param string|null $body Content of HTTP response body
* @return string
@@ -190,6 +221,31 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
}
/**
* Append HTTP response body
* @param string $body Content to append to the current HTTP response body
* @param bool $replace Overwrite existing response body?
* @return string The updated HTTP response body
*/
public function write($body, $replace = false)
{
if ($replace) {
$this->body = $body;
} else {
$this->body .= (string)$body;
}
$this->length = strlen($this->body);
return $this->body;
}
public function getLength()
{
return $this->length;
}
/**
* DEPRECATION WARNING! Use `getLength` or `write` or `body` instead.
*
* Get and set length
* @param int|null $length
* @return int
@@ -203,24 +259,6 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
return $this->length;
}
/**
* Append HTTP response body
* @param string $body Content to append to the current HTTP response body
* @param bool $replace Overwrite existing response body?
* @return string The updated HTTP response body
*/
public function write($body, $replace = false)
{
if ($replace) {
$this->body = $body;
} else {
$this->body .= (string) $body;
}
$this->length = strlen($this->body);
return $this->body;
}
/**
* Finalize
*
@@ -232,16 +270,19 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
*/
public function finalize()
{
// Prepare response
if (in_array($this->status, array(204, 304))) {
unset($this['Content-Type'], $this['Content-Length']);
return array($this->status, $this->header, '');
} else {
return array($this->status, $this->header, $this->body);
$this->headers->remove('Content-Type');
$this->headers->remove('Content-Length');
$this->setBody('');
}
return array($this->status, $this->headers, $this->body);
}
/**
* DEPRECATION WARNING! Access `cookies` property directly.
*
* Set cookie
*
* Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
@@ -256,10 +297,13 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
*/
public function setCookie($name, $value)
{
Util::setCookieHeader($this->header, $name, $value);
// Util::setCookieHeader($this->header, $name, $value);
$this->cookies->set($name, $value);
}
/**
* DEPRECATION WARNING! Access `cookies` property directly.
*
* Delete cookie
*
* Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
@@ -274,12 +318,13 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
* array, only the Cookie with the given name AND domain will be removed. The invalidating cookie
* sent with this response will adopt all properties of the second argument.
*
* @param string $name The name of the cookie
* @param array $value Properties for cookie including: value, expire, path, domain, secure, httponly
* @param string $name The name of the cookie
* @param array $settings Properties for cookie including: value, expire, path, domain, secure, httponly
*/
public function deleteCookie($name, $value = array())
public function deleteCookie($name, $settings = array())
{
Util::deleteCookieHeader($this->header, $name, $value);
$this->cookies->remove($name, $settings);
// Util::deleteCookieHeader($this->header, $name, $value);
}
/**
@@ -293,8 +338,8 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
*/
public function redirect ($url, $status = 302)
{
$this->status = $status;
$this['Location'] = $url;
$this->setStatus($status);
$this->headers->set('Location', $url);
}
/**
@@ -387,24 +432,25 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
return $this->status >= 500 && $this->status < 600;
}
/**
* DEPRECATION WARNING! ArrayAccess interface will be removed from \Slim\Http\Response.
* Iterate `headers` or `cookies` properties directly.
*/
/**
* Array Access: Offset Exists
*/
public function offsetExists( $offset )
public function offsetExists($offset)
{
return isset($this->header[$offset]);
return isset($this->headers[$offset]);
}
/**
* Array Access: Offset Get
*/
public function offsetGet( $offset )
public function offsetGet($offset)
{
if (isset($this->header[$offset])) {
return $this->header[$offset];
} else {
return null;
}
return $this->headers[$offset];
}
/**
@@ -412,7 +458,7 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
*/
public function offsetSet($offset, $value)
{
$this->header[$offset] = $value;
$this->headers[$offset] = $value;
}
/**
@@ -420,18 +466,24 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
*/
public function offsetUnset($offset)
{
unset($this->header[$offset]);
unset($this->headers[$offset]);
}
/**
* DEPRECATION WARNING! Countable interface will be removed from \Slim\Http\Response.
* Call `count` on `headers` or `cookies` properties directly.
*
* Countable: Count
*/
public function count()
{
return count($this->header);
return count($this->headers);
}
/**
* DEPRECATION WARNING! IteratorAggregate interface will be removed from \Slim\Http\Response.
* Iterate `headers` or `cookies` properties directly.
*
* Get Iterator
*
* This returns the contained `\Slim\Http\Headers` instance which
@@ -441,11 +493,12 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate
*/
public function getIterator()
{
return $this->header;
return $this->headers->getIterator();
}
/**
* Get message for HTTP status code
* @param int $status
* @return string|null
*/
public static function getMessageForCode($status)
+74 -29
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -51,14 +51,15 @@ class Util
* override the magic quotes setting with either TRUE or FALSE as the send argument
* to force this method to strip or not strip slashes from its input.
*
* @var array|string $rawData
* @param array|string $rawData
* @param bool $overrideStripSlashes
* @return array|string
*/
public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes = null)
{
$strip = is_null($overrideStripSlashes) ? get_magic_quotes_gpc() : $overrideStripSlashes;
if ($strip) {
return self::_stripSlashes($rawData);
return self::stripSlashes($rawData);
} else {
return $rawData;
}
@@ -69,9 +70,9 @@ class Util
* @param array|string $rawData
* @return array|string
*/
protected static function _stripSlashes($rawData)
protected static function stripSlashes($rawData)
{
return is_array($rawData) ? array_map(array('self', '_stripSlashes'), $rawData) : stripslashes($rawData);
return is_array($rawData) ? array_map(array('self', 'stripSlashes'), $rawData) : stripslashes($rawData);
}
/**
@@ -95,10 +96,11 @@ class Util
}
//Merge settings with defaults
$settings = array_merge(array(
$defaults = array(
'algorithm' => MCRYPT_RIJNDAEL_256,
'mode' => MCRYPT_MODE_CBC
), $settings);
);
$settings = array_merge($defaults, $settings);
//Get module
$module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
@@ -144,10 +146,11 @@ class Util
}
//Merge settings with defaults
$settings = array_merge(array(
$defaults = array(
'algorithm' => MCRYPT_RIJNDAEL_256,
'mode' => MCRYPT_MODE_CBC
), $settings);
);
$settings = array_merge($defaults, $settings);
//Get module
$module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
@@ -167,12 +170,44 @@ class Util
//Decrypt value
mcrypt_generic_init($module, $key, $iv);
$decryptedData = @mdecrypt_generic($module, $data);
$res = str_replace("\x0", '', $decryptedData);
$res = rtrim($decryptedData, "\0");
mcrypt_generic_deinit($module);
return $res;
}
/**
* Serialize Response cookies into raw HTTP header
* @param \Slim\Http\Headers $headers The Response headers
* @param \Slim\Http\Cookies $cookies The Response cookies
* @param array $config The Slim app settings
*/
public static function serializeCookies(\Slim\Http\Headers &$headers, \Slim\Http\Cookies $cookies, array $config)
{
if ($config['cookies.encrypt']) {
foreach ($cookies as $name => $settings) {
if (is_string($settings['expires'])) {
$expires = strtotime($settings['expires']);
} else {
$expires = (int) $settings['expires'];
}
$settings['value'] = static::encodeSecureCookie(
$settings['value'],
$expires,
$config['cookies.secret_key'],
$config['cookies.cipher'],
$config['cookies.cipher_mode']
);
static::setCookieHeader($headers, $name, $settings);
}
} else {
foreach ($cookies as $name => $settings) {
static::setCookieHeader($headers, $name, $settings);
}
}
}
/**
* Encode secure cookie value
*
@@ -180,21 +215,28 @@ class Util
* cookie value is encrypted and hashed so that its value is
* secure and checked for integrity when read in subsequent requests.
*
* @param string $value The unsecure HTTP cookie value
* @param string $value The insecure HTTP cookie value
* @param int $expires The UNIX timestamp at which this cookie will expire
* @param string $secret The secret key used to hash the cookie value
* @param int $algorithm The algorithm to use for encryption
* @param int $mode The algorithm mode to use for encryption
* @param string
* @return string
*/
public static function encodeSecureCookie($value, $expires, $secret, $algorithm, $mode)
{
$key = hash_hmac('sha1', $expires, $secret);
$iv = self::get_iv($expires, $secret);
$secureString = base64_encode(self::encrypt($value, $key, $iv, array(
'algorithm' => $algorithm,
'mode' => $mode
)));
$iv = self::getIv($expires, $secret);
$secureString = base64_encode(
self::encrypt(
$value,
$key,
$iv,
array(
'algorithm' => $algorithm,
'mode' => $mode
)
)
);
$verificationString = hash_hmac('sha1', $expires . $value, $key);
return implode('|', array($expires, $secureString, $verificationString));
@@ -208,11 +250,10 @@ class Util
* secure and checked for integrity when read in subsequent requests.
*
* @param string $value The secure HTTP cookie value
* @param int $expires The UNIX timestamp at which this cookie will expire
* @param string $secret The secret key used to hash the cookie value
* @param int $algorithm The algorithm to use for encryption
* @param int $mode The algorithm mode to use for encryption
* @param string
* @return bool|string
*/
public static function decodeSecureCookie($value, $secret, $algorithm, $mode)
{
@@ -220,11 +261,16 @@ class Util
$value = explode('|', $value);
if (count($value) === 3 && ((int) $value[0] === 0 || (int) $value[0] > time())) {
$key = hash_hmac('sha1', $value[0], $secret);
$iv = self::get_iv($value[0], $secret);
$data = self::decrypt(base64_decode($value[1]), $key, $iv, array(
'algorithm' => $algorithm,
'mode' => $mode
));
$iv = self::getIv($value[0], $secret);
$data = self::decrypt(
base64_decode($value[1]),
$key,
$iv,
array(
'algorithm' => $algorithm,
'mode' => $mode
)
);
$verificationString = hash_hmac('sha1', $value[0] . $data, $key);
if ($verificationString === $value[2]) {
return $data;
@@ -310,7 +356,7 @@ class Util
*
* @param array $header
* @param string $name
* @param string $value
* @param array $value
*/
public static function deleteCookieHeader(&$header, $name, $value = array())
{
@@ -343,7 +389,7 @@ class Util
/**
* Parse cookie header
*
* This method will parse the HTTP requst's `Cookie` header
* This method will parse the HTTP request's `Cookie` header
* and extract cookies into an associative array.
*
* @param string
@@ -376,14 +422,13 @@ class Util
*
* @param int $expires The UNIX timestamp at which this cookie will expire
* @param string $secret The secret key used to hash the cookie value
* @return binary string with length 40
* @return string Hash
*/
private static function get_iv($expires, $secret)
private static function getIv($expires, $secret)
{
$data1 = hash_hmac('sha1', 'a'.$expires.'b', $secret);
$data2 = hash_hmac('sha1', 'z'.$expires.'y', $secret);
return pack("h*", $data1.$data2);
}
}
+150 -38
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -39,11 +39,15 @@ namespace Slim;
* a Log Writer in conjunction with this Log to write to various output
* destinations (e.g. a file). This class provides this interface:
*
* debug( mixed $object )
* info( mixed $object )
* warn( mixed $object )
* error( mixed $object )
* fatal( mixed $object )
* debug( mixed $object, array $context )
* info( mixed $object, array $context )
* notice( mixed $object, array $context )
* warning( mixed $object, array $context )
* error( mixed $object, array $context )
* critical( mixed $object, array $context )
* alert( mixed $object, array $context )
* emergency( mixed $object, array $context )
* log( mixed $level, mixed $object, array $context )
*
* This class assumes only that your Log Writer has a public `write()` method
* that accepts any object as its one and only argument. The Log Writer
@@ -56,21 +60,28 @@ namespace Slim;
*/
class Log
{
const FATAL = 0;
const ERROR = 1;
const WARN = 2;
const INFO = 3;
const DEBUG = 4;
const EMERGENCY = 1;
const ALERT = 2;
const CRITICAL = 3;
const FATAL = 3; //DEPRECATED replace with CRITICAL
const ERROR = 4;
const WARN = 5;
const NOTICE = 6;
const INFO = 7;
const DEBUG = 8;
/**
* @var array
*/
protected static $levels = array(
self::FATAL => 'FATAL',
self::ERROR => 'ERROR',
self::WARN => 'WARN',
self::INFO => 'INFO',
self::DEBUG => 'DEBUG'
self::EMERGENCY => 'EMERGENCY',
self::ALERT => 'ALERT',
self::CRITICAL => 'CRITICAL',
self::ERROR => 'ERROR',
self::WARN => 'WARNING',
self::NOTICE => 'NOTICE',
self::INFO => 'INFO',
self::DEBUG => 'DEBUG'
);
/**
@@ -173,65 +184,166 @@ class Log
/**
* Log debug message
* @param mixed $object
* @return mixed|false What the Logger returns, or false if Logger not set or not enabled
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function debug($object)
public function debug($object, $context = array())
{
return $this->write($object, self::DEBUG);
return $this->log(self::DEBUG, $object, $context);
}
/**
* Log info message
* @param mixed $object
* @return mixed|false What the Logger returns, or false if Logger not set or not enabled
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function info($object)
public function info($object, $context = array())
{
return $this->write($object, self::INFO);
return $this->log(self::INFO, $object, $context);
}
/**
* Log warn message
* Log notice message
* @param mixed $object
* @return mixed|false What the Logger returns, or false if Logger not set or not enabled
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function warn($object)
public function notice($object, $context = array())
{
return $this->write($object, self::WARN);
return $this->log(self::NOTICE, $object, $context);
}
/**
* Log warning message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function warning($object, $context = array())
{
return $this->log(self::WARN, $object, $context);
}
/**
* DEPRECATED for function warning
* Log warning message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function warn($object, $context = array())
{
return $this->log(self::WARN, $object, $context);
}
/**
* Log error message
* @param mixed $object
* @return mixed|false What the Logger returns, or false if Logger not set or not enabled
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function error($object)
public function error($object, $context = array())
{
return $this->write($object, self::ERROR);
return $this->log(self::ERROR, $object, $context);
}
/**
* Log critical message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function critical($object, $context = array())
{
return $this->log(self::CRITICAL, $object, $context);
}
/**
* DEPRECATED for function critical
* Log fatal message
* @param mixed $object
* @return mixed|false What the Logger returns, or false if Logger not set or not enabled
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function fatal($object)
public function fatal($object, $context = array())
{
return $this->write($object, self::FATAL);
return $this->log(self::CRITICAL, $object, $context);
}
/**
* Log alert message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function alert($object, $context = array())
{
return $this->log(self::ALERT, $object, $context);
}
/**
* Log emergency message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function emergency($object, $context = array())
{
return $this->log(self::EMERGENCY, $object, $context);
}
/**
* Log message
* @param mixed The object to log
* @param int The message level
* @return int|false
* @param mixed $level
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
* @throws \InvalidArgumentException If invalid log level
*/
protected function write($object, $level)
public function log($level, $object, $context = array())
{
if ($this->enabled && $this->writer && $level <= $this->level) {
return $this->writer->write($object, $level);
if (!isset(self::$levels[$level])) {
throw new \InvalidArgumentException('Invalid log level supplied to function');
} else if ($this->enabled && $this->writer && $level <= $this->level) {
$message = (string)$object;
if (count($context) > 0) {
if (isset($context['exception']) && $context['exception'] instanceof \Exception) {
$message .= ' - ' . $context['exception'];
unset($context['exception']);
}
$message = $this->interpolate($message, $context);
}
return $this->writer->write($message, $level);
} else {
return false;
}
}
/**
* DEPRECATED for function log
* Log message
* @param mixed $object The object to log
* @param int $level The message level
* @return int|bool
*/
protected function write($object, $level)
{
return $this->log($level, $object);
}
/**
* Interpolate log message
* @param mixed $message The log message
* @param array $context An array of placeholder values
* @return string The processed string
*/
protected function interpolate($message, $context = array())
{
$replace = array();
foreach ($context as $key => $value) {
$replace['{' . $key . '}'] = $value;
}
return strtr($message, $replace);
}
}
+2 -2
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -66,7 +66,7 @@ class LogWriter
* Write message
* @param mixed $message
* @param int $level
* @return int|false
* @return int|bool
*/
public function write($message, $level = null)
{
+5 -5
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -42,7 +42,7 @@ namespace Slim;
abstract class Middleware
{
/**
* @var \Slim Reference to the primary application instance
* @var \Slim\Slim Reference to the primary application instance
*/
protected $app;
@@ -57,7 +57,7 @@ abstract class Middleware
* This method injects the primary Slim application instance into
* this middleware.
*
* @param \Slim $application
* @param \Slim\Slim $application
*/
final public function setApplication($application)
{
@@ -70,7 +70,7 @@ abstract class Middleware
* This method retrieves the application previously injected
* into this middleware.
*
* @return \Slim
* @return \Slim\Slim
*/
final public function getApplication()
{
@@ -97,7 +97,7 @@ abstract class Middleware
* This method retrieves the next downstream middleware
* previously injected into this middleware.
*
* @return \Slim|\Slim\Middleware
* @return \Slim\Slim|\Slim\Middleware
*/
final public function getNextMiddleware()
{
+8 -4
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -58,12 +58,13 @@ class ContentTypes extends \Slim\Middleware
*/
public function __construct($settings = array())
{
$this->contentTypes = array_merge(array(
$defaults = array(
'application/json' => array($this, 'parseJson'),
'application/xml' => array($this, 'parseXml'),
'text/xml' => array($this, 'parseXml'),
'text/csv' => array($this, 'parseCsv')
), $settings);
);
$this->contentTypes = array_merge($defaults, $settings);
}
/**
@@ -136,7 +137,10 @@ class ContentTypes extends \Slim\Middleware
{
if (class_exists('SimpleXMLElement')) {
try {
return new \SimpleXMLElement($input);
$backup = libxml_disable_entity_loader(true);
$result = new \SimpleXMLElement($input);
libxml_disable_entity_loader($backup);
return $result;
} catch (\Exception $e) {
// Do nothing
}
+13 -3
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -45,7 +45,7 @@ namespace Slim\Middleware;
* @author Josh Lockhart
* @since 1.6.0
*/
class Flash extends \Slim\Middleware implements \ArrayAccess, \IteratorAggregate
class Flash extends \Slim\Middleware implements \ArrayAccess, \IteratorAggregate, \Countable
{
/**
* @var array
@@ -59,7 +59,6 @@ class Flash extends \Slim\Middleware implements \ArrayAccess, \IteratorAggregate
/**
* Constructor
* @param \Slim $app
* @param array $settings
*/
public function __construct($settings = array())
@@ -199,4 +198,15 @@ class Flash extends \Slim\Middleware implements \ArrayAccess, \IteratorAggregate
return new \ArrayIterator($messages);
}
/**
* Countable: Count
*/
public function count()
{
return count($this->getMessages());
}
}
+4 -6
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -36,7 +36,7 @@ namespace Slim\Middleware;
* HTTP Method Override
*
* This is middleware for a Slim application that allows traditional
* desktop browsers to submit psuedo PUT and DELETE requests by relying
* desktop browsers to submit pseudo PUT and DELETE requests by relying
* on a pre-determined request parameter. Without this middleware,
* desktop browsers are only able to submit GET and POST requests.
*
@@ -55,7 +55,6 @@ class MethodOverride extends \Slim\Middleware
/**
* Constructor
* @param \Slim $app
* @param array $settings
*/
public function __construct($settings = array())
@@ -72,16 +71,15 @@ class MethodOverride extends \Slim\Middleware
* modifies the environment settings so downstream middleware and/or the Slim
* application will treat the request with the desired HTTP method.
*
* @param array $env
* @return array[status, header, body]
*/
public function call()
{
$env = $this->app->environment();
if (isset($env['X_HTTP_METHOD_OVERRIDE'])) {
if (isset($env['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
// Header commonly used by Backbone.js and others
$env['slim.method_override.original_method'] = $env['REQUEST_METHOD'];
$env['REQUEST_METHOD'] = strtoupper($env['X_HTTP_METHOD_OVERRIDE']);
$env['REQUEST_METHOD'] = strtoupper($env['HTTP_X_HTTP_METHOD_OVERRIDE']);
} elseif (isset($env['REQUEST_METHOD']) && $env['REQUEST_METHOD'] === 'POST') {
// HTML Form Override
$req = new \Slim\Http\Request($env);
+3 -1
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -66,7 +66,9 @@ class PrettyExceptions extends \Slim\Middleware
try {
$this->next->call();
} catch (\Exception $e) {
$log = $this->app->getLog(); // Force Slim to append log to env if not already
$env = $this->app->environment();
$env['slim.log'] = $log;
$env['slim.log']->error($e);
$this->app->contentType('text/html');
$this->app->response()->status(500);
+47 -40
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -40,18 +40,14 @@ namespace Slim\Middleware;
* and instead serializes/unserializes the $_SESSION global
* variable to/from an HTTP cookie.
*
* If a secret key is provided with this middleware, the HTTP
* cookie will be checked for integrity to ensure the client-side
* cookie is not changed.
*
* You should NEVER store sensitive data in a client-side cookie
* in any format, encrypted or not. If you need to store sensitive
* user information in a session, you should rely on PHP's native
* session implementation, or use other middleware to store
* session data in a database or alternative server-side cache.
* in any format, encrypted (with cookies.encrypt) or not. If you
* need to store sensitive user information in a session, you should
* rely on PHP's native session implementation, or use other middleware
* to store session data in a database or alternative server-side cache.
*
* Because this class stores serialized session data in an HTTP cookie,
* you are inherently limtied to 4 Kb. If you attempt to store
* you are inherently limited to 4 Kb. If you attempt to store
* more than this amount, serialization will fail.
*
* @package Slim
@@ -68,21 +64,19 @@ class SessionCookie extends \Slim\Middleware
/**
* Constructor
*
* @param array $settings
* @param array $settings
*/
public function __construct($settings = array())
{
$this->settings = array_merge(array(
$defaults = array(
'expires' => '20 minutes',
'path' => '/',
'domain' => null,
'secure' => false,
'httponly' => false,
'name' => 'slim_session',
'secret' => 'CHANGE_ME',
'cipher' => MCRYPT_RIJNDAEL_256,
'cipher_mode' => MCRYPT_MODE_CBC
), $settings);
);
$this->settings = array_merge($defaults, $settings);
if (is_string($this->settings['expires'])) {
$this->settings['expires'] = strtotime($this->settings['expires']);
}
@@ -119,7 +113,6 @@ class SessionCookie extends \Slim\Middleware
/**
* Load session
* @param array $env
*/
protected function loadSession()
{
@@ -127,14 +120,14 @@ class SessionCookie extends \Slim\Middleware
session_start();
}
$value = \Slim\Http\Util::decodeSecureCookie(
$this->app->request()->cookies($this->settings['name']),
$this->settings['secret'],
$this->settings['cipher'],
$this->settings['cipher_mode']
);
$value = $this->app->getCookie($this->settings['name']);
if ($value) {
$_SESSION = unserialize($value);
try {
$_SESSION = unserialize($value);
} catch (\Exception $e) {
$this->app->getLog()->error('Error unserializing session cookie value! ' . $e->getMessage());
}
} else {
$_SESSION = array();
}
@@ -145,57 +138,71 @@ class SessionCookie extends \Slim\Middleware
*/
protected function saveSession()
{
$value = \Slim\Http\Util::encodeSecureCookie(
serialize($_SESSION),
$this->settings['expires'],
$this->settings['secret'],
$this->settings['cipher'],
$this->settings['cipher_mode']
);
$value = serialize($_SESSION);
if (strlen($value) > 4096) {
$this->app->getLog()->error('WARNING! Slim\Middleware\SessionCookie data size is larger than 4KB. Content save failed.');
} else {
$this->app->response()->setCookie($this->settings['name'], array(
'value' => $value,
'domain' => $this->settings['domain'],
'path' => $this->settings['path'],
'expires' => $this->settings['expires'],
'secure' => $this->settings['secure'],
'httponly' => $this->settings['httponly']
));
$this->app->setCookie(
$this->settings['name'],
$value,
$this->settings['expires'],
$this->settings['path'],
$this->settings['domain'],
$this->settings['secure'],
$this->settings['httponly']
);
}
session_destroy();
// session_destroy();
}
/********************************************************************************
* Session Handler
*******************************************************************************/
/**
* @codeCoverageIgnore
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function close()
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function read($id)
{
return '';
}
/**
* @codeCoverageIgnore
*/
public function write($id, $data)
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function destroy($id)
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function gc($maxlifetime)
{
return true;
+46 -18
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.0.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -92,8 +92,8 @@ class Route
/**
* Constructor
* @param string $pattern The URL pattern (e.g. "/books/:id")
* @param mixed $callable Anything that returns TRUE for is_callable()
* @param string $pattern The URL pattern (e.g. "/books/:id")
* @param mixed $callable Anything that returns TRUE for is_callable()
*/
public function __construct($pattern, $callable)
{
@@ -154,6 +154,11 @@ class Route
*/
public function setCallable($callable)
{
$matches = array();
if (is_string($callable) && preg_match('!^([^\:]+)\:([[:alnum:]]+)$!', $callable, $matches)) {
$callable = array(new $matches[1], $matches[2]);
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Route callable must be callable');
}
@@ -194,7 +199,7 @@ class Route
*/
public function setName($name)
{
$this->name = (string) $name;
$this->name = (string)$name;
}
/**
@@ -217,7 +222,7 @@ class Route
/**
* Get route parameter value
* @param string $index Name of URL parameter
* @param string $index Name of URL parameter
* @return string
* @throws \InvalidArgumentException If route parameter does not exist at index
*/
@@ -232,8 +237,8 @@ class Route
/**
* Set route parameter value
* @param string $index Name of URL parameter
* @param mixed $value The new parameter value
* @param string $index Name of URL parameter
* @param mixed $value The new parameter value
* @throws \InvalidArgumentException If route parameter does not exist at index
*/
public function setParam($index, $value)
@@ -285,6 +290,7 @@ class Route
/**
* Detect support for an HTTP method
* @param string $method
* @return bool
*/
public function supportsHttpMethod($method)
@@ -320,7 +326,7 @@ class Route
if (is_callable($middleware)) {
$this->middleware[] = $middleware;
} elseif (is_array($middleware)) {
foreach($middleware as $callable) {
foreach ($middleware as $callable) {
if (!is_callable($callable)) {
throw new \InvalidArgumentException('All Route middleware must be callable');
}
@@ -347,8 +353,11 @@ class Route
public function matches($resourceUri)
{
//Convert URL params into regex patterns, construct a regex for this route, init params
$patternAsRegex = preg_replace_callback('#:([\w]+)\+?#', array($this, 'matchesCallback'),
str_replace(')', ')?', (string) $this->pattern));
$patternAsRegex = preg_replace_callback(
'#:([\w]+)\+?#',
array($this, 'matchesCallback'),
str_replace(')', ')?', (string)$this->pattern)
);
if (substr($this->pattern, -1) === '/') {
$patternAsRegex .= '?';
}
@@ -359,7 +368,7 @@ class Route
}
foreach ($this->paramNames as $name) {
if (isset($paramValues[$name])) {
if (isset($this->paramNamesPath[ $name ])) {
if (isset($this->paramNamesPath[$name])) {
$this->params[$name] = explode('/', urldecode($paramValues[$name]));
} else {
$this->params[$name] = urldecode($paramValues[$name]);
@@ -372,17 +381,17 @@ class Route
/**
* Convert a URL parameter (e.g. ":id", ":id+") into a regular expression
* @param array URL parameters
* @return string Regular expression for URL parameter
* @param array $m URL parameters
* @return string Regular expression for URL parameter
*/
protected function matchesCallback($m)
{
$this->paramNames[] = $m[1];
if (isset($this->conditions[ $m[1] ])) {
return '(?P<' . $m[1] . '>' . $this->conditions[ $m[1] ] . ')';
if (isset($this->conditions[$m[1]])) {
return '(?P<' . $m[1] . '>' . $this->conditions[$m[1]] . ')';
}
if (substr($m[0], -1) === '+') {
$this->paramNamesPath[ $m[1] ] = 1;
$this->paramNamesPath[$m[1]] = 1;
return '(?P<' . $m[1] . '>.+)';
}
@@ -392,7 +401,7 @@ class Route
/**
* Set route name
* @param string $name The name of the route
* @param string $name The name of the route
* @return \Slim\Route
*/
public function name($name)
@@ -404,7 +413,7 @@ class Route
/**
* Merge route conditions
* @param array $conditions Key-value array of URL parameter conditions
* @param array $conditions Key-value array of URL parameter conditions
* @return \Slim\Route
*/
public function conditions(array $conditions)
@@ -413,4 +422,23 @@ class Route
return $this;
}
/**
* Dispatch route
*
* This method invokes the route object's callable. If middleware is
* registered for the route, each callable middleware is invoked in
* the order specified.
*
* @return bool
*/
public function dispatch()
{
foreach ($this->middleware as $mw) {
call_user_func_array($mw, array($this));
}
$return = call_user_func_array($this->getCallable(), array_values($this->getParams()));
return ($return === false) ? false : true;
}
}
+64 -42
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -63,12 +63,18 @@ class Router
*/
protected $matchedRoutes;
/**
* @var array Array containing all route groups
*/
protected $routeGroups;
/**
* Constructor
*/
public function __construct()
{
$this->routes = array();
$this->routeGroups = array();
}
/**
@@ -100,7 +106,7 @@ class Router
if ($reload || is_null($this->matchedRoutes)) {
$this->matchedRoutes = array();
foreach ($this->routes as $route) {
if (!$route->supportsHttpMethod($httpMethod)) {
if (!$route->supportsHttpMethod($httpMethod) && !$route->supportsHttpMethod("ANY")) {
continue;
}
@@ -114,25 +120,66 @@ class Router
}
/**
* Map a route object to a callback function
* @param string $pattern The URL pattern (ie. "/books/:id")
* @param mixed $callable Anything that returns TRUE for is_callable()
* @return \Slim\Route
* Add a route object to the router
* @param \Slim\Route $route The Slim Route
*/
public function map($pattern, $callable)
public function map(\Slim\Route $route)
{
$route = new \Slim\Route($pattern, $callable);
list($groupPattern, $groupMiddleware) = $this->processGroups();
$route->setPattern($groupPattern . $route->getPattern());
$this->routes[] = $route;
return $route;
foreach ($groupMiddleware as $middleware) {
$route->setMiddleware($middleware);
}
}
/**
* A helper function for processing the group's pattern and middleware
* @return array Returns an array with the elements: pattern, middlewareArr
*/
protected function processGroups()
{
$pattern = "";
$middleware = array();
foreach ($this->routeGroups as $group) {
$k = key($group);
$pattern .= $k;
if (is_array($group[$k])) {
$middleware = array_merge($middleware, $group[$k]);
}
}
return array($pattern, $middleware);
}
/**
* Add a route group to the array
* @param string $group The group pattern (ie. "/books/:id")
* @param array|null $middleware Optional parameter array of middleware
* @return int The index of the new group
*/
public function pushGroup($group, $middleware = array())
{
return array_push($this->routeGroups, array($group => $middleware));
}
/**
* Removes the last route group from the array
* @return bool True if successful, else False
*/
public function popGroup()
{
return (array_pop($this->routeGroups) !== null);
}
/**
* Get URL for named route
* @param string $name The name of the route
* @param array Associative array of URL parameter names and replacement values
* @throws RuntimeException If named route not found
* @return string The URL for the given route populated with provided replacement values
* @param array $params Associative array of URL parameter names and replacement values
* @throws \RuntimeException If named route not found
* @return string The URL for the given route populated with provided replacement values
*/
public function urlFor($name, $params = array())
{
@@ -140,45 +187,20 @@ class Router
throw new \RuntimeException('Named route not found for name: ' . $name);
}
$search = array();
foreach (array_keys($params) as $key) {
$search[] = '#:' . $key . '\+?(?!\w)#';
foreach ($params as $key => $value) {
$search[] = '#:' . preg_quote($key, '#') . '\+?(?!\w)#';
}
$pattern = preg_replace($search, $params, $this->getNamedRoute($name)->getPattern());
//Remove remnants of unpopulated, trailing optional pattern segments
return preg_replace('#\(/?:.+\)|\(|\)#', '', $pattern);
}
/**
* Dispatch route
*
* This method invokes the route object's callable. If middleware is
* registered for the route, each callable middleware is invoked in
* the order specified.
*
* @param \Slim\Route $route The route object
* @return bool Was route callable invoked successfully?
*/
public function dispatch(\Slim\Route $route)
{
$this->currentRoute = $route;
//Invoke middleware
foreach ($route->getMiddleware() as $mw) {
call_user_func_array($mw, array($route));
}
//Invoke callable
call_user_func_array($route->getCallable(), array_values($route->getParams()));
return true;
//Remove remnants of unpopulated, trailing optional pattern segments, escaped special characters
return preg_replace('#\(/?:.+\)|\(|\)|\\\\#', '', $pattern);
}
/**
* Add named route
* @param string $name The route name
* @param \Slim\Route $route The route object
* @throws \RuntimeException If a named route already exists with the same name
* @throws \RuntimeException If a named route already exists with the same name
*/
public function addNamedRoute($name, \Slim\Route $route)
{
+233 -141
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -40,16 +40,26 @@ if (!extension_loaded('mcrypt')) {
/**
* Slim
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*
* @property \Slim\Environment $environment
* @property \Slim\Http\Response $response
* @property \Slim\Http\Request $request
* @property \Slim\Router $router
*/
class Slim
{
/**
* @const string
*/
const VERSION = '2.2.0';
const VERSION = '2.4.0';
/**
* @var \Slim\Helper\Set
*/
public $container;
/**
* @var array[\Slim]
@@ -61,41 +71,6 @@ class Slim
*/
protected $name;
/**
* @var array
*/
protected $environment;
/**
* @var \Slim\Http\Request
*/
protected $request;
/**
* @var \Slim\Http\Response
*/
protected $response;
/**
* @var \Slim\Router
*/
protected $router;
/**
* @var \Slim\View
*/
protected $view;
/**
* @var array
*/
protected $settings;
/**
* @var string
*/
protected $mode;
/**
* @var array
*/
@@ -173,44 +148,111 @@ class Slim
* Constructor
* @param array $userSettings Associative array of application settings
*/
public function __construct($userSettings = array())
public function __construct(array $userSettings = array())
{
// Setup Slim application
$this->settings = array_merge(static::getDefaultSettings(), $userSettings);
$this->environment = \Slim\Environment::getInstance();
$this->request = new \Slim\Http\Request($this->environment);
$this->response = new \Slim\Http\Response();
$this->router = new \Slim\Router();
// Setup IoC container
$this->container = new \Slim\Helper\Set();
$this->container['settings'] = array_merge(static::getDefaultSettings(), $userSettings);
// Default environment
$this->container->singleton('environment', function ($c) {
return \Slim\Environment::getInstance();
});
// Default request
$this->container->singleton('request', function ($c) {
return new \Slim\Http\Request($c['environment']);
});
// Default response
$this->container->singleton('response', function ($c) {
return new \Slim\Http\Response();
});
// Default router
$this->container->singleton('router', function ($c) {
return new \Slim\Router();
});
// Default view
$this->container->singleton('view', function ($c) {
$viewClass = $c['settings']['view'];
$templatesPath = $c['settings']['templates.path'];
$view = ($viewClass instanceOf \Slim\View) ? $viewClass : new $viewClass;
$view->setTemplatesDirectory($templatesPath);
return $view;
});
// Default log writer
$this->container->singleton('logWriter', function ($c) {
$logWriter = $c['settings']['log.writer'];
return is_object($logWriter) ? $logWriter : new \Slim\LogWriter($c['environment']['slim.errors']);
});
// Default log
$this->container->singleton('log', function ($c) {
$log = new \Slim\Log($c['logWriter']);
$log->setEnabled($c['settings']['log.enabled']);
$log->setLevel($c['settings']['log.level']);
$env = $c['environment'];
$env['slim.log'] = $log;
return $log;
});
// Default mode
$this->container['mode'] = function ($c) {
$mode = $c['settings']['mode'];
if (isset($_ENV['SLIM_MODE'])) {
$mode = $_ENV['SLIM_MODE'];
} else {
$envMode = getenv('SLIM_MODE');
if ($envMode !== false) {
$mode = $envMode;
}
}
return $mode;
};
// Define default middleware stack
$this->middleware = array($this);
$this->add(new \Slim\Middleware\Flash());
$this->add(new \Slim\Middleware\MethodOverride());
// Determine application mode
$this->getMode();
// Setup view
$this->view($this->config('view'));
// Make default if first instance
if (is_null(static::getInstance())) {
$this->setName('default');
}
}
// Set default logger that writes to stderr (may be overridden with middleware)
$logWriter = $this->config('log.writer');
if (!$logWriter) {
$logWriter = new \Slim\LogWriter($this->environment['slim.errors']);
}
$log = new \Slim\Log($logWriter);
$log->setEnabled($this->config('log.enabled'));
$log->setLevel($this->config('log.level'));
$this->environment['slim.log'] = $log;
public function __get($name)
{
return $this->container[$name];
}
public function __set($name, $value)
{
$this->container[$name] = $value;
}
public function __isset($name)
{
return isset($this->container[$name]);
}
public function __unset($name)
{
unset($this->container[$name]);
}
/**
* Get application instance by name
* @param string $name The name of the Slim application
* @return \Slim|null
* @return \Slim\Slim|null
*/
public static function getInstance($name = 'default')
{
@@ -255,6 +297,7 @@ class Slim
'templates.path' => './templates',
'view' => '\Slim\View',
// Cookies
'cookies.encrypt' => false,
'cookies.lifetime' => '20 minutes',
'cookies.path' => '/',
'cookies.domain' => null,
@@ -297,7 +340,9 @@ class Slim
return isset($this->settings[$name]) ? $this->settings[$name] : null;
}
} else {
$this->settings[$name] = $value;
$settings = $this->settings;
$settings[$name] = $value;
$this->settings = $settings;
}
}
@@ -316,19 +361,6 @@ class Slim
*/
public function getMode()
{
if (!isset($this->mode)) {
if (isset($_ENV['SLIM_MODE'])) {
$this->mode = $_ENV['SLIM_MODE'];
} else {
$envMode = getenv('SLIM_MODE');
if ($envMode !== false) {
$this->mode = $envMode;
} else {
$this->mode = $this->config('mode');
}
}
}
return $this->mode;
}
@@ -361,7 +393,7 @@ class Slim
*/
public function getLog()
{
return $this->environment['slim.log'];
return $this->log;
}
/********************************************************************************
@@ -369,7 +401,7 @@ class Slim
*******************************************************************************/
/**
* Add GET|POST|PUT|DELETE route
* Add GET|POST|PUT|PATCH|DELETE route
*
* Adds a new route to the router with associated callable. This
* route will only be invoked when the HTTP request's method matches
@@ -402,7 +434,8 @@ class Slim
{
$pattern = array_shift($args);
$callable = array_pop($args);
$route = $this->router->map($pattern, $callable);
$route = new \Slim\Route($pattern, $callable);
$this->router->map($route);
if (count($args) > 0) {
$route->setMiddleware($args);
}
@@ -458,6 +491,18 @@ class Slim
return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PUT);
}
/**
* Add PATCH route
* @see mapRoute()
* @return \Slim\Route
*/
public function patch()
{
$args = func_get_args();
return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PATCH);
}
/**
* Add DELETE route
* @see mapRoute()
@@ -482,6 +527,40 @@ class Slim
return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_OPTIONS);
}
/**
* Route Groups
*
* This method accepts a route pattern and a callback all Route
* declarations in the callback will be prepended by the group(s)
* that it is in
*
* Accepts the same parameters as a standard route so:
* (pattern, middleware1, middleware2, ..., $callback)
*/
public function group()
{
$args = func_get_args();
$pattern = array_shift($args);
$callable = array_pop($args);
$this->router->pushGroup($pattern, $args);
if (is_callable($callable)) {
call_user_func($callable);
}
$this->router->popGroup();
}
/*
* Add route for any HTTP method
* @see mapRoute()
* @return \Slim\Route
*/
public function any()
{
$args = func_get_args();
return $this->mapRoute($args)->via("ANY");
}
/**
* Not Found Handler
*
@@ -503,12 +582,13 @@ class Slim
*
* @param mixed $callable Anything that returns true for is_callable()
*/
public function notFound( $callable = null ) {
if ( is_callable($callable) ) {
public function notFound ($callable = null)
{
if (is_callable($callable)) {
$this->notFound = $callable;
} else {
ob_start();
if ( is_callable($this->notFound) ) {
if (is_callable($this->notFound)) {
call_user_func($this->notFound);
} else {
call_user_func(array($this, 'defaultNotFound'));
@@ -566,7 +646,7 @@ class Slim
protected function callErrorHandler($argument = null)
{
ob_start();
if ( is_callable($this->error) ) {
if (is_callable($this->error)) {
call_user_func_array($this->error, array($argument));
} else {
call_user_func_array(array($this, 'defaultError'), array($argument));
@@ -653,7 +733,7 @@ class Slim
/**
* Render a template
*
* Call this method within a GET, POST, PUT, DELETE, NOT FOUND, or ERROR
* Call this method within a GET, POST, PUT, PATCH, DELETE, NOT FOUND, or ERROR
* callable to render a template whose output is appended to the
* current HTTP response body. How the template is rendered is
* delegated to the current View.
@@ -667,7 +747,6 @@ class Slim
if (!is_null($status)) {
$this->response->status($status);
}
$this->view->setTemplatesDirectory($this->config('templates.path'));
$this->view->appendData($data);
$this->view->display($template);
}
@@ -692,8 +771,8 @@ class Slim
public function lastModified($time)
{
if (is_integer($time)) {
$this->response['Last-Modified'] = date(DATE_RFC1123, $time);
if ($time === strtotime($this->request->headers('IF_MODIFIED_SINCE'))) {
$this->response->headers->set('Last-Modified', gmdate('D, d M Y H:i:s T', $time));
if ($time === strtotime($this->request->headers->get('IF_MODIFIED_SINCE'))) {
$this->halt(304);
}
} else {
@@ -726,11 +805,13 @@ class Slim
//Set etag value
$value = '"' . $value . '"';
if ($type === 'weak') $value = 'W/'.$value;
if ($type === 'weak') {
$value = 'W/'.$value;
}
$this->response['ETag'] = $value;
//Check conditional GET
if ($etagsHeader = $this->request->headers('IF_NONE_MATCH')) {
if ($etagsHeader = $this->request->headers->get('IF_NONE_MATCH')) {
$etags = preg_split('@\s*,\s*@', $etagsHeader);
if (in_array($value, $etags) || in_array('*', $etags)) {
$this->halt(304);
@@ -756,7 +837,7 @@ class Slim
if (is_string($time)) {
$time = strtotime($time);
}
$this->response['Expires'] = gmdate(DATE_RFC1123, $time);
$this->response->headers->set('Expires', gmdate('D, d M Y H:i:s T', $time));
}
/********************************************************************************
@@ -764,7 +845,7 @@ class Slim
*******************************************************************************/
/**
* Set unencrypted HTTP cookie
* Set HTTP cookie to be sent with the HTTP response
*
* @param string $name The cookie name
* @param string $value The cookie value
@@ -779,32 +860,52 @@ class Slim
*/
public function setCookie($name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null)
{
$this->response->setCookie($name, array(
$settings = array(
'value' => $value,
'expires' => is_null($time) ? $this->config('cookies.lifetime') : $time,
'path' => is_null($path) ? $this->config('cookies.path') : $path,
'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain,
'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure,
'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly
));
);
$this->response->cookies->set($name, $settings);
}
/**
* Get value of unencrypted HTTP cookie
* Get value of HTTP cookie from the current HTTP request
*
* Return the value of a cookie from the current HTTP request,
* or return NULL if cookie does not exist. Cookies created during
* the current request will not be available until the next request.
*
* @param string $name
* @param bool $deleteIfInvalid
* @return string|null
*/
public function getCookie($name)
public function getCookie($name, $deleteIfInvalid = true)
{
return $this->request->cookies($name);
// Get cookie value
$value = $this->request->cookies->get($name);
// Decode if encrypted
if ($this->config('cookies.encrypt')) {
$value = \Slim\Http\Util::decodeSecureCookie(
$value,
$this->config('cookies.secret_key'),
$this->config('cookies.cipher'),
$this->config('cookies.cipher_mode')
);
if ($value === false && $deleteIfInvalid) {
$this->deleteCookie($name);
}
}
return $value;
}
/**
* DEPRECATION WARNING! Use `setCookie` with the `cookies.encrypt` app setting set to `true`.
*
* Set encrypted HTTP cookie
*
* @param string $name The cookie name
@@ -818,23 +919,14 @@ class Slim
* HTTPS connection from the client
* @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol
*/
public function setEncryptedCookie($name, $value, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null)
public function setEncryptedCookie($name, $value, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false)
{
$expires = is_null($expires) ? $this->config('cookies.lifetime') : $expires;
if (is_string($expires)) {
$expires = strtotime($expires);
}
$secureValue = \Slim\Http\Util::encodeSecureCookie(
$value,
$expires,
$this->config('cookies.secret_key'),
$this->config('cookies.cipher'),
$this->config('cookies.cipher_mode')
);
$this->setCookie($name, $secureValue, $expires, $path, $domain, $secure, $httponly);
$this->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly);
}
/**
* DEPRECATION WARNING! Use `getCookie` with the `cookies.encrypt` app setting set to `true`.
*
* Get value of encrypted HTTP cookie
*
* Return the value of an encrypted cookie from the current HTTP request,
@@ -842,21 +934,12 @@ class Slim
* the current request will not be available until the next request.
*
* @param string $name
* @return string|false
* @param bool $deleteIfInvalid
* @return string|bool
*/
public function getEncryptedCookie($name, $deleteIfInvalid = true)
{
$value = \Slim\Http\Util::decodeSecureCookie(
$this->request->cookies($name),
$this->config('cookies.secret_key'),
$this->config('cookies.cipher'),
$this->config('cookies.cipher_mode')
);
if ($value === false && $deleteIfInvalid) {
$this->deleteCookie($name);
}
return $value;
return $this->getCookie($name, $deleteIfInvalid);
}
/**
@@ -877,12 +960,13 @@ class Slim
*/
public function deleteCookie($name, $path = null, $domain = null, $secure = null, $httponly = null)
{
$this->response->deleteCookie($name, array(
$settings = array(
'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain,
'path' => is_null($path) ? $this->config('cookies.path') : $path,
'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure,
'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly
));
);
$this->response->cookies->remove($name, $settings);
}
/********************************************************************************
@@ -968,16 +1052,16 @@ class Slim
*/
public function contentType($type)
{
$this->response['Content-Type'] = $type;
$this->response->headers->set('Content-Type', $type);
}
/**
* Set the HTTP response status code
* @param int $status The HTTP response status code
* @param int $code The HTTP response status code
*/
public function status($code)
{
$this->response->status($code);
$this->response->setStatus($code);
}
/**
@@ -1071,7 +1155,7 @@ class Slim
/**
* Invoke hook
* @param string $name The hook name
* @param mixed $hookArgs (Optional) Argument for hooked functions
* @param mixed $hookArg (Optional) Argument for hooked functions
*/
public function applyHook($name, $hookArg = null)
{
@@ -1165,16 +1249,22 @@ class Slim
*/
public function run()
{
//set_error_handler(array('\Slim\Slim', 'handleErrors')); //Espo: no needs to use this handler
//set_error_handler(array('\Slim\Slim', 'handleErrors')); //Espo: no needs to use this handler
//Apply final outer middleware layers
//$this->add(new \Slim\Middleware\PrettyExceptions()); //Espo: no needs to use this handler
if ($this->config('debug')) {
//Apply pretty exceptions only in debug to avoid accidental information leakage in production
//$this->add(new \Slim\Middleware\PrettyExceptions()); //Espo: no needs to use this handler
}
//Invoke middleware and application stack
$this->middleware[0]->call();
//Fetch status, header, and body
list($status, $header, $body) = $this->response->finalize();
list($status, $headers, $body) = $this->response->finalize();
// Serialize cookies (with optional encryption)
\Slim\Http\Util::serializeCookies($headers, $this->response->cookies, $this->settings);
//Send headers
if (headers_sent() === false) {
@@ -1186,7 +1276,7 @@ class Slim
}
//Send headers
foreach ($header as $name => $value) {
foreach ($headers as $name => $value) {
$hValues = explode("\n", $value);
foreach ($hValues as $hVal) {
header("$name: $hVal", false);
@@ -1194,8 +1284,10 @@ class Slim
}
}
//Send body
echo $body;
//Send body, but only if it isn't a HEAD request
if (!$this->request->isHead()) {
echo $body;
}
restore_error_handler();
}
@@ -1219,7 +1311,7 @@ class Slim
foreach ($matchedRoutes as $route) {
try {
$this->applyHook('slim.before.dispatch');
$dispatched = $this->router->dispatch($route);
$dispatched = $route->dispatch();
$this->applyHook('slim.after.dispatch');
if ($dispatched) {
break;
@@ -1229,7 +1321,7 @@ class Slim
}
}
if (!$dispatched) {
$this->notFound();
$this->notFound();
}
$this->applyHook('slim.after.router');
$this->stop();
@@ -1264,16 +1356,16 @@ class Slim
* @param string $errstr The error message
* @param string $errfile The absolute path to the affected file
* @param int $errline The line number of the error in the affected file
* @return true
* @return bool
* @throws \ErrorException
*/
public static function handleErrors($errno, $errstr = '', $errfile = '', $errline = '')
{
if (error_reporting() & $errno) {
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
if (!($errno & error_reporting())) {
return;
}
return true;
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
}
/**
@@ -1304,6 +1396,6 @@ class Slim
protected function defaultError($e)
{
$this->getLog()->error($e);
echo self::generateTemplateMarkup('Error', '<p>A website error has occured. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.</p>');
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>');
}
}
+159 -93
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
* @package Slim
*
* MIT LICENSE
@@ -50,95 +50,166 @@ namespace Slim;
class View
{
/**
* @var string Absolute or relative filesystem path to a specific template
*
* DEPRECATION WARNING!
* This variable will be removed in the near future
* Data available to the view templates
* @var \Slim\Helper\Set
*/
protected $templatePath = '';
protected $data;
/**
* @var array Associative array of template variables
*/
protected $data = array();
/**
* @var string Absolute or relative path to the application's templates directory
* Path to templates base directory (without trailing slash)
* @var string
*/
protected $templatesDirectory;
/**
* Constructor
*
* This is empty but may be implemented in a subclass
*/
public function __construct()
{
$this->data = new \Slim\Helper\Set();
}
/********************************************************************************
* Data methods
*******************************************************************************/
/**
* Does view data have value with key?
* @param string $key
* @return boolean
*/
public function has($key)
{
return $this->data->has($key);
}
/**
* Get data
* @param string|null $key
* @return mixed If key is null, array of template data;
* If key exists, value of datum with key;
* If key does not exist, null;
* Return view data value with key
* @param string $key
* @return mixed
*/
public function get($key)
{
return $this->data->get($key);
}
/**
* Set view data value with key
* @param string $key
* @param mixed $value
*/
public function set($key, $value)
{
$this->data->set($key, $value);
}
/**
* Set view data value as Closure with key
* @param string $key
* @param mixed $value
*/
public function keep($key, Closure $value)
{
$this->data->keep($key, $value);
}
/**
* Return view data
* @return array
*/
public function all()
{
return $this->data->all();
}
/**
* Replace view data
* @param array $data
*/
public function replace(array $data)
{
$this->data->replace($data);
}
/**
* Clear view data
*/
public function clear()
{
$this->data->clear();
}
/********************************************************************************
* Legacy data methods
*******************************************************************************/
/**
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* Get data from view
*/
public function getData($key = null)
{
if (!is_null($key)) {
return isset($this->data[$key]) ? $this->data[$key] : null;
} else {
return $this->data;
return $this->data->all();
}
}
/**
* Set data
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* If two arguments:
* A single datum with key is assigned value;
*
* $view->setData('color', 'red');
*
* If one argument:
* Replace all data with provided array keys and values;
*
* $view->setData(array('color' => 'red', 'number' => 1));
*
* @param mixed
* @param mixed
* @throws InvalidArgumentException If incorrect method signature
* Set data for view
*/
public function setData()
{
$args = func_get_args();
if (count($args) === 1 && is_array($args[0])) {
$this->data = $args[0];
$this->data->replace($args[0]);
} elseif (count($args) === 2) {
$this->data[(string) $args[0]] = $args[1];
// Ensure original behavior is maintained. DO NOT invoke stored Closures.
if (is_object($args[1]) && method_exists($args[1], '__invoke')) {
$this->data->set($args[0], $this->data->protect($args[1]));
} else {
$this->data->set($args[0], $args[1]);
}
} else {
throw new \InvalidArgumentException('Cannot set View data with provided arguments. Usage: `View::setData( $key, $value );` or `View::setData([ key => value, ... ]);`');
}
}
/**
* Append new data to existing template data
* @param array
* @throws InvalidArgumentException If not given an array argument
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* Append data to view
* @param array $data
*/
public function appendData($data)
{
if (!is_array($data)) {
throw new \InvalidArgumentException('Cannot append view data. Expected array argument.');
}
$this->data = array_merge($this->data, $data);
$this->data->replace($data);
}
/********************************************************************************
* Resolve template paths
*******************************************************************************/
/**
* Set the base directory that contains view templates
* @param string $directory
* @throws \InvalidArgumentException If directory is not a directory
*/
public function setTemplatesDirectory($directory)
{
$this->templatesDirectory = rtrim($directory, DIRECTORY_SEPARATOR);
}
/**
* Get templates directory
* @return string|null Path to templates directory without trailing slash;
* Returns null if templates directory not set;
* Get templates base directory
* @return string
*/
public function getTemplatesDirectory()
{
@@ -146,70 +217,65 @@ class View
}
/**
* Set templates directory
* @param string $dir
* Get fully qualified path to template file using templates base directory
* @param string $file The template file pathname relative to templates base directory
* @return string
*/
public function setTemplatesDirectory($dir)
public function getTemplatePathname($file)
{
$this->templatesDirectory = rtrim($dir, '/');
return $this->templatesDirectory . DIRECTORY_SEPARATOR . ltrim($file, DIRECTORY_SEPARATOR);
}
/**
* Set template
* @param string $template
* @throws RuntimeException If template file does not exist
*
* DEPRECATION WARNING!
* This method will be removed in the near future.
*/
public function setTemplate($template)
{
$this->templatePath = $this->getTemplatesDirectory() . '/' . ltrim($template, '/');
if (!file_exists($this->templatePath)) {
throw new \RuntimeException('View cannot render template `' . $this->templatePath . '`. Template does not exist.');
}
}
/********************************************************************************
* Rendering
*******************************************************************************/
/**
* Display template
*
* This method echoes the rendered template to the current output buffer
*
* @param string $template Pathname of template file relative to templates directoy
*/
public function display($template)
{
echo $this->fetch($template);
}
/**
* Fetch rendered template
*
* This method returns the rendered template
*
* @param string $template Pathname of template file relative to templates directory
* @return string
*/
public function fetch($template)
{
return $this->render($template);
}
/**
* Render template
*
* @param string $template Pathname of template file relative to templates directory
* @return string
*
* DEPRECATION WARNING!
* Use `\Slim\View::fetch` to return a rendered template instead of `\Slim\View::render`.
* @param array $data Any additonal data to be passed to the template.
*/
public function render($template)
public function display($template, $data = null)
{
$this->setTemplate($template);
extract($this->data);
echo $this->fetch($template, $data);
}
/**
* Return the contents of a rendered template file
*
* @param string $template The template pathname, relative to the template base directory
* @param array $data Any additonal data to be passed to the template.
* @return string The rendered template
*/
public function fetch($template, $data = null)
{
return $this->render($template, $data);
}
/**
* Render a template file
*
* NOTE: This method should be overridden by custom view subclasses
*
* @param string $template The template pathname, relative to the template base directory
* @param array $data Any additonal data to be passed to the template.
* @return string The rendered template
* @throws \RuntimeException If resolved template pathname is not a valid file
*/
protected function render($template, $data = null)
{
$templatePathname = $this->getTemplatePathname($template);
if (!is_file($templatePathname)) {
throw new \RuntimeException("View cannot render `$template` because the template does not exist");
}
$data = array_merge($this->data->all(), (array) $data);
extract($data);
ob_start();
require $this->templatePath;
require $templatePathname;
return ob_get_clean();
}
+3
View File
@@ -15,6 +15,9 @@
"require": {
"php": ">=5.3.0"
},
"suggest": {
"ext-mcrypt": "Required for HTTP cookie encryption"
},
"autoload": {
"psr-0": { "Slim": "." }
}
+30 -13
View File
@@ -26,13 +26,15 @@ $app = new \Slim\Slim();
*
* Here we define several Slim application routes that respond
* to appropriate HTTP request methods. In this example, the second
* argument for `Slim::get`, `Slim::post`, `Slim::put`, and `Slim::delete`
* argument for `Slim::get`, `Slim::post`, `Slim::put`, `Slim::patch`, and `Slim::delete`
* is an anonymous function.
*/
// GET route
$app->get('/', function () {
$template = <<<EOT
$app->get(
'/',
function () {
$template = <<<EOT
<!DOCTYPE html>
<html>
<head>
@@ -125,23 +127,38 @@ $app->get('/', function () {
</body>
</html>
EOT;
echo $template;
});
echo $template;
}
);
// POST route
$app->post('/post', function () {
echo 'This is a POST route';
});
$app->post(
'/post',
function () {
echo 'This is a POST route';
}
);
// PUT route
$app->put('/put', function () {
echo 'This is a PUT route';
$app->put(
'/put',
function () {
echo 'This is a PUT route';
}
);
// PATCH route
$app->patch('/patch', function () {
echo 'This is a PATCH route';
});
// DELETE route
$app->delete('/delete', function () {
echo 'This is a DELETE route';
});
$app->delete(
'/delete',
function () {
echo 'This is a DELETE route';
}
);
/**
* Step 4: Run the Slim application
+22 -2
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -43,6 +43,8 @@ class EnvironmentTest extends PHPUnit_Framework_TestCase
*/
public function setUp()
{
$_SERVER['DOCUMENT_ROOT'] = '/var/www';
$_SERVER['SCRIPT_FILENAME'] = '/var/www/foo/index.php';
$_SERVER['SERVER_NAME'] = 'slim';
$_SERVER['SERVER_PORT'] = '80';
$_SERVER['SCRIPT_NAME'] = '/foo/index.php';
@@ -106,6 +108,7 @@ class EnvironmentTest extends PHPUnit_Framework_TestCase
*/
public function testParsesPathsWithoutUrlRewriteInRootDirectory()
{
$_SERVER['SCRIPT_FILENAME'] = '/var/www/index.php';
$_SERVER['REQUEST_URI'] = '/index.php/bar/xyz';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$env = \Slim\Environment::getInstance(true);
@@ -123,6 +126,7 @@ class EnvironmentTest extends PHPUnit_Framework_TestCase
*/
public function testParsesPathsWithoutUrlRewriteInRootDirectoryForAppRootUri()
{
$_SERVER['SCRIPT_FILENAME'] = '/var/www/index.php';
$_SERVER['REQUEST_URI'] = '/index.php';
$_SERVER['SCRIPT_NAME'] = '/index.php';
unset($_SERVER['PATH_INFO']);
@@ -157,6 +161,7 @@ class EnvironmentTest extends PHPUnit_Framework_TestCase
*/
public function testParsesPathsWithUrlRewriteInRootDirectory()
{
$_SERVER['SCRIPT_FILENAME'] = '/var/www/index.php';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['REQUEST_URI'] = '/bar/xyz';
unset($_SERVER['PATH_INFO']);
@@ -175,6 +180,7 @@ class EnvironmentTest extends PHPUnit_Framework_TestCase
*/
public function testParsesPathsWithUrlRewriteInRootDirectoryForAppRootUri()
{
$_SERVER['SCRIPT_FILENAME'] = '/var/www/index.php';
$_SERVER['REQUEST_URI'] = '/';
$_SERVER['SCRIPT_NAME'] = '/index.php';
unset($_SERVER['PATH_INFO']);
@@ -224,6 +230,7 @@ class EnvironmentTest extends PHPUnit_Framework_TestCase
*/
public function testPathInfoRetainsUrlEncodedCharacters()
{
$_SERVER['SCRIPT_FILENAME'] = '/var/www/index.php';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['REQUEST_URI'] = '/foo/%23bar'; //<-- URL-encoded "#bar"
$env = \Slim\Environment::getInstance(true);
@@ -292,7 +299,20 @@ class EnvironmentTest extends PHPUnit_Framework_TestCase
$env = \Slim\Environment::getInstance(true);
$this->assertEquals('text/csv', $env['CONTENT_TYPE']);
$this->assertEquals('100', $env['CONTENT_LENGTH']);
$this->assertEquals('XmlHttpRequest', $env['X_REQUESTED_WITH']);
$this->assertEquals('XmlHttpRequest', $env['HTTP_X_REQUESTED_WITH']);
}
/**
* Tests X-HTTP-Method-Override is allowed through unmolested.
*
* Pre-conditions:
* X_HTTP_METHOD_OVERRIDE is sent in client HTTP request;
* X_HTTP_METHOD_OVERRIDE is not empty;
*/
public function testSetsHttpMethodOverrideHeader() {
$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'DELETE';
$env = \Slim\Environment::getInstance(true);
$this->assertEquals('DELETE', $env['HTTP_X_HTTP_METHOD_OVERRIDE']);
}
/**
+19 -103
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -32,112 +32,28 @@
class HeadersTest extends PHPUnit_Framework_TestCase
{
/**
* Test constructor without args
*/
public function testConstructorWithoutArgs()
public function testNormalizesKey()
{
$h = new \Slim\Http\Headers();
$this->assertEquals(0, count($h));
$h->set('Http_Content_Type', 'text/html');
$prop = new \ReflectionProperty($h, 'data');
$prop->setAccessible(true);
$this->assertArrayHasKey('Content-Type', $prop->getValue($h));
}
/**
* Test constructor with args
*/
public function testConstructorWithArgs()
public function testExtractHeaders()
{
$h = new \Slim\Http\Headers(array('Content-Type' => 'text/html'));
$this->assertEquals(1, count($h));
}
/**
* Test get and set header
*/
public function testSetAndGetHeader()
{
$h = new \Slim\Http\Headers();
$h['Content-Type'] = 'text/html';
$this->assertEquals('text/html', $h['Content-Type']);
$this->assertEquals('text/html', $h['Content-type']);
$this->assertEquals('text/html', $h['content-type']);
}
/**
* Test get non-existent header
*/
public function testGetNonExistentHeader()
{
$h = new \Slim\Http\Headers();
$this->assertNull($h['foo']);
}
/**
* Test isset header
*/
public function testHeaderIsSet()
{
$h = new \Slim\Http\Headers();
$h['Content-Type'] = 'text/html';
$this->assertTrue(isset($h['Content-Type']));
$this->assertTrue(isset($h['Content-type']));
$this->assertTrue(isset($h['content-type']));
$this->assertFalse(isset($h['foo']));
}
/**
* Test unset header
*/
public function testUnsetHeader()
{
$h = new \Slim\Http\Headers();
$h['Content-Type'] = 'text/html';
$this->assertEquals(1, count($h));
unset($h['Content-Type']);
$this->assertEquals(0, count($h));
}
/**
* Test merge headers
*/
public function testMergeHeaders()
{
$h = new \Slim\Http\Headers();
$h['Content-Type'] = 'text/html';
$this->assertEquals(1, count($h));
$h->merge(array('Content-type' => 'text/csv', 'content-length' => 10));
$this->assertEquals(2, count($h));
$this->assertEquals('text/csv', $h['content-type']);
$this->assertEquals(10, $h['Content-length']);
}
/**
* Test iteration
*/
public function testIteration()
{
$h = new \Slim\Http\Headers();
$h['One'] = 'Foo';
$h['Two'] = 'Bar';
$output = '';
foreach ($h as $key => $value) {
$output .= $key . $value;
}
$this->assertEquals('OneFooTwoBar', $output);
}
/**
* Test outputs header name in original form, not canonical form
*/
public function testOutputsOriginalNotCanonicalName()
{
$h = new \Slim\Http\Headers();
$h['X-Powered-By'] = 'Slim';
$h['Content-Type'] = 'text/csv';
$keys = array();
foreach ($h as $name => $value) {
$keys[] = $name;
}
$this->assertContains('X-Powered-By', $keys);
$this->assertContains('Content-Type', $keys);
$test = array(
'HTTP_HOST' => 'foo.com',
'SERVER_NAME' => 'foo',
'CONTENT_TYPE' => 'text/html',
'X_FORWARDED_FOR' => '127.0.0.1'
);
$results = \Slim\Http\Headers::extract($test);
$this->assertEquals(array(
'HTTP_HOST' => 'foo.com',
'CONTENT_TYPE' => 'text/html',
'X_FORWARDED_FOR' => '127.0.0.1'
), $results);
}
}
+59 -53
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -54,11 +54,6 @@ class RequestTest extends PHPUnit_Framework_TestCase
));
$req = new \Slim\Http\Request($env);
$this->assertTrue($req->isGet());
$this->assertFalse($req->isPost());
$this->assertFalse($req->isPut());
$this->assertFalse($req->isDelete());
$this->assertFalse($req->isOptions());
$this->assertFalse($req->isHead());
}
/**
@@ -70,12 +65,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
'REQUEST_METHOD' => 'POST',
));
$req = new \Slim\Http\Request($env);
$this->assertFalse($req->isGet());
$this->assertTrue($req->isPost());
$this->assertFalse($req->isPut());
$this->assertFalse($req->isDelete());
$this->assertFalse($req->isOptions());
$this->assertFalse($req->isHead());
}
/**
@@ -87,12 +77,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
'REQUEST_METHOD' => 'PUT',
));
$req = new \Slim\Http\Request($env);
$this->assertFalse($req->isGet());
$this->assertFalse($req->isPost());
$this->assertTrue($req->isPut());
$this->assertFalse($req->isDelete());
$this->assertFalse($req->isOptions());
$this->assertFalse($req->isHead());
}
/**
@@ -104,12 +89,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
'REQUEST_METHOD' => 'DELETE',
));
$req = new \Slim\Http\Request($env);
$this->assertFalse($req->isGet());
$this->assertFalse($req->isPost());
$this->assertFalse($req->isPut());
$this->assertTrue($req->isDelete());
$this->assertFalse($req->isOptions());
$this->assertFalse($req->isHead());
}
/**
@@ -121,12 +101,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
'REQUEST_METHOD' => 'OPTIONS',
));
$req = new \Slim\Http\Request($env);
$this->assertFalse($req->isGet());
$this->assertFalse($req->isPost());
$this->assertFalse($req->isPut());
$this->assertFalse($req->isDelete());
$this->assertTrue($req->isOptions());
$this->assertFalse($req->isHead());
}
/**
@@ -138,14 +113,21 @@ class RequestTest extends PHPUnit_Framework_TestCase
'REQUEST_METHOD' => 'HEAD',
));
$req = new \Slim\Http\Request($env);
$this->assertFalse($req->isGet());
$this->assertFalse($req->isPost());
$this->assertFalse($req->isPut());
$this->assertFalse($req->isDelete());
$this->assertFalse($req->isOptions());
$this->assertTrue($req->isHead());
}
/**
* Test HTTP PATCH method detection
*/
public function testIsPatch()
{
$env = \Slim\Environment::mock(array(
'REQUEST_METHOD' => 'PATCH',
));
$req = new \Slim\Http\Request($env);
$this->assertTrue($req->isPatch());
}
/**
* Test AJAX method detection w/ header
*/
@@ -165,7 +147,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testIsAjaxWithQueryParameter()
{
$env = \Slim\Environment::mock(array(
'QUERY_STRING' => 'one=1&two=2&three=3&isajax=1',
'QUERY_STRING' => 'isajax=1',
));
$req = new \Slim\Http\Request($env);
$this->assertTrue($req->isAjax());
@@ -173,7 +155,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
}
/**
* Test AJAX method detection wihtout header or query paramter
* Test AJAX method detection without header or query parameter
*/
public function testIsAjaxWithoutHeaderOrQueryParameter()
{
@@ -239,6 +221,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
$this->assertEquals(3, count($req->get()));
$this->assertEquals('1', $req->get('one'));
$this->assertNull($req->get('foo'));
$this->assertFalse($req->get('foo', false));
}
/**
@@ -254,6 +237,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
$this->assertEquals(3, count($req->get()));
$this->assertEquals('1', $req->get('one'));
$this->assertNull($req->get('foo'));
$this->assertFalse($req->get('foo', false));
}
/**
@@ -271,6 +255,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
$this->assertEquals(2, count($req->post()));
$this->assertEquals('bar', $req->post('foo'));
$this->assertNull($req->post('xyz'));
$this->assertFalse($req->post('xyz', false));
}
/**
@@ -289,6 +274,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
$this->assertEquals(2, count($req->post()));
$this->assertEquals('bar', $req->post('foo'));
$this->assertNull($req->post('xyz'));
$this->assertFalse($req->post('xyz', false));
}
/**
@@ -337,6 +323,26 @@ class RequestTest extends PHPUnit_Framework_TestCase
$this->assertEquals('bar', $req->put('foo'));
$this->assertEquals('bar', $req->params('foo'));
$this->assertNull($req->put('xyz'));
$this->assertFalse($req->put('xyz', false));
}
/**
* Test fetch PATCH params
*/
public function testPatch()
{
$env = \Slim\Environment::mock(array(
'REQUEST_METHOD' => 'PATCH',
'slim.input' => 'foo=bar&abc=123',
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
'CONTENT_LENGTH' => 15
));
$req = new \Slim\Http\Request($env);
$this->assertEquals(2, count($req->patch()));
$this->assertEquals('bar', $req->patch('foo'));
$this->assertEquals('bar', $req->params('foo'));
$this->assertNull($req->patch('xyz'));
$this->assertFalse($req->patch('xyz', false));
}
/**
@@ -355,6 +361,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
$this->assertEquals('bar', $req->delete('foo'));
$this->assertEquals('bar', $req->params('foo'));
$this->assertNull($req->delete('xyz'));
$this->assertFalse($req->delete('xyz', false));
}
/**
@@ -363,7 +370,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testCookies()
{
$env = \Slim\Environment::mock(array(
'COOKIE' => 'foo=bar; abc=123'
'HTTP_COOKIE' => 'foo=bar; abc=123'
));
$req = new \Slim\Http\Request($env);
$this->assertEquals(2, count($req->cookies()));
@@ -431,12 +438,11 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testHeaders()
{
$env = \Slim\Environment::mock(array(
'ACCEPT_ENCODING' => 'gzip'
'HTTP_ACCEPT_ENCODING' => 'gzip'
));
$req = new \Slim\Http\Request($env);
$headers = $req->headers();
$this->assertTrue(is_array($headers));
$this->assertArrayHasKey('ACCEPT_ENCODING', $headers);
$this->assertInstanceOf('\Slim\Http\Headers', $headers);
$this->assertEquals('gzip', $req->headers('HTTP_ACCEPT_ENCODING'));
$this->assertEquals('gzip', $req->headers('HTTP-ACCEPT-ENCODING'));
$this->assertEquals('gzip', $req->headers('http_accept_encoding'));
@@ -632,7 +638,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
{
$env = \Slim\Environment::mock(array(
'SERVER_NAME' => 'slim',
'HOST' => 'slimframework.com'
'HTTP_HOST' => 'slimframework.com'
));
$req = new \Slim\Http\Request($env);
$this->assertEquals('slimframework.com', $req->getHost()); //Uses HTTP_HOST if available
@@ -645,7 +651,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
{
$env = \Slim\Environment::mock(array(
'SERVER_NAME' => 'slim',
'HOST' => 'slimframework.com:80'
'HTTP_HOST' => 'slimframework.com:80'
));
$req = new \Slim\Http\Request($env);
$this->assertEquals('slimframework.com', $req->getHost()); //Uses HTTP_HOST if available
@@ -658,9 +664,9 @@ class RequestTest extends PHPUnit_Framework_TestCase
{
$env = \Slim\Environment::mock(array(
'SERVER_NAME' => 'slim',
'HOST' => 'slimframework.com'
'HTTP_HOST' => 'slimframework.com'
));
unset($env['HOST']);
unset($env['HTTP_HOST']);
$req = new \Slim\Http\Request($env);
$this->assertEquals('slim', $req->getHost()); //Uses SERVER_NAME as backup
}
@@ -671,7 +677,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testGetHostWithPort()
{
$env = \Slim\Environment::mock(array(
'HOST' => 'slimframework.com',
'HTTP_HOST' => 'slimframework.com',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http'
@@ -681,12 +687,12 @@ class RequestTest extends PHPUnit_Framework_TestCase
}
/**
* Test get host with port doesn't dulplicate port numbers
* Test get host with port doesn't duplicate port numbers
*/
public function testGetHostDoesntDulplicatePort()
public function testGetHostDoesntDuplicatePort()
{
$env = \Slim\Environment::mock(array(
'HOST' => 'slimframework.com:80',
'HTTP_HOST' => 'slimframework.com:80',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http'
@@ -806,7 +812,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testGetUrl()
{
$env = \Slim\Environment::mock(array(
'HOST' => 'slimframework.com',
'HTTP_HOST' => 'slimframework.com',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http'
@@ -821,7 +827,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testGetUrlWithCustomPort()
{
$env = \Slim\Environment::mock(array(
'HOST' => 'slimframework.com',
'HTTP_HOST' => 'slimframework.com',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 8080,
'slim.url_scheme' => 'http'
@@ -836,7 +842,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testGetUrlWithHttps()
{
$env = \Slim\Environment::mock(array(
'HOST' => 'slimframework.com',
'HTTP_HOST' => 'slimframework.com',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 443,
'slim.url_scheme' => 'https'
@@ -885,12 +891,12 @@ class RequestTest extends PHPUnit_Framework_TestCase
}
/**
* Test get refererer
* Test get referer
*/
public function testGetReferrer()
{
$env = \Slim\Environment::mock(array(
'REFERER' => 'http://foo.com'
'HTTP_REFERER' => 'http://foo.com'
));
$req = new \Slim\Http\Request($env);
$this->assertEquals('http://foo.com', $req->getReferrer());
@@ -898,7 +904,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
}
/**
* Test get refererer
* Test get referer
*/
public function testGetReferrerWhenNotExists()
{
@@ -914,7 +920,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testGetUserAgent()
{
$env = \Slim\Environment::mock(array(
'USER_AGENT' => 'user-agent-string'
'HTTP_USER_AGENT' => 'user-agent-string'
));
$req = new \Slim\Http\Request($env);
$this->assertEquals('user-agent-string', $req->getUserAgent());
@@ -926,7 +932,7 @@ class RequestTest extends PHPUnit_Framework_TestCase
public function testGetUserAgentWhenNotExists()
{
$env = \Slim\Environment::mock();
unset($env['USER_AGENT']);
unset($env['HTTP_USER_AGENT']);
$req = new \Slim\Http\Request($env);
$this->assertNull($req->getUserAgent());
}
+95 -398
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -32,541 +32,238 @@
class ResponseTest extends PHPUnit_Framework_TestCase
{
/**
* Test constructor without args
*/
public function testConstructorWithoutArgs()
public function testConstructWithoutArgs()
{
$r = new \Slim\Http\Response();
$this->assertEquals('', $r->body());
$this->assertEquals(200, $r->status());
$this->assertEquals(0, $r->length());
$this->assertEquals('text/html', $r['Content-Type']);
$res = new \Slim\Http\Response();
$this->assertAttributeEquals(200, 'status', $res);
$this->assertAttributeEquals('', 'body', $res);
}
/**
* Test constructor with args
*/
public function testConstructorWithArgs()
public function testConstructWithArgs()
{
$r = new \Slim\Http\Response('Page Not Found', 404, array('Content-Type' => 'application/json', 'X-Created-By' => 'Slim'));
$this->assertEquals('Page Not Found', $r->body());
$this->assertEquals(404, $r->status());
$this->assertEquals(14, $r->length());
$this->assertEquals('application/json', $r['Content-Type']);
$this->assertEquals('Slim', $r['X-Created-By']);
$res = new \Slim\Http\Response('Foo', 201);
$this->assertAttributeEquals(201, 'status', $res);
$this->assertAttributeEquals('Foo', 'body', $res);
}
/**
* Test get status
*/
public function testGetStatus()
{
$r = new \Slim\Http\Response();
$this->assertEquals(200, $r->status());
$res = new \Slim\Http\Response();
$this->assertEquals(200, $res->getStatus());
}
/**
* Test set status
*/
public function testSetStatus()
{
$r = new \Slim\Http\Response();
$r->status(500);
$this->assertEquals(500, $r->status());
$res = new \Slim\Http\Response();
$res->setStatus(301);
$this->assertAttributeEquals(301, 'status', $res);
}
/**
* Test get headers
* DEPRECATION WARNING!
*/
public function testGetHeaders()
public function testStatusGetOld()
{
$r = new \Slim\Http\Response();
$headers = $r->headers();
$this->assertEquals(1, count($headers));
$this->assertEquals('text/html', $headers['Content-Type']);
$res = new \Slim\Http\Response('', 201);
$this->assertEquals(201, $res->status());
}
/**
* Test get and set header (without Array Access)
* DEPRECATION WARNING!
*/
public function testGetAndSetHeader()
public function testStatusSetOld()
{
$r = new \Slim\Http\Response();
$r->header('X-Foo', 'Bar');
$this->assertEquals('Bar', $r->header('X-Foo'));
$res = new \Slim\Http\Response();
$prop = new \ReflectionProperty($res, 'status');
$prop->setAccessible(true);
$res->status(301);
$this->assertEquals(301, $prop->getValue($res));
}
/**
* Test get body
*/
public function testGetBody()
{
$r = new \Slim\Http\Response('Foo');
$this->assertEquals('Foo', $r->body());
$res = new \Slim\Http\Response();
$property = new \ReflectionProperty($res, 'body');
$property->setAccessible(true);
$property->setValue($res, 'foo');
$this->assertEquals('foo', $res->getBody());
}
/**
* Test set body
*/
public function testSetBody()
{
$r = new \Slim\Http\Response();
$r->body('Foo');
$this->assertEquals('Foo', $r->body());
$res = new \Slim\Http\Response('bar');
$res->setBody('foo'); // <-- Should replace body
$this->assertAttributeEquals('foo', 'body', $res);
}
/**
* Test get length
*/
public function testGetLength()
public function testWrite()
{
$r = new \Slim\Http\Response('Foo');
$this->assertEquals(3, $r->length());
$res = new \Slim\Http\Response();
$property = new \ReflectionProperty($res, 'body');
$property->setAccessible(true);
$property->setValue($res, 'foo');
$res->write('bar'); // <-- Should append to body
$this->assertAttributeEquals('foobar', 'body', $res);
}
/**
* Test set length
*/
public function testSetLength()
public function testLength()
{
$r = new \Slim\Http\Response();
$r->length(3);
$this->assertEquals(3, $r->length());
$res = new \Slim\Http\Response('foo'); // <-- Sets body and length
$this->assertEquals(3, $res->getLength());
}
/**
* Test write for appending
*/
public function testWriteAppend()
{
$r = new \Slim\Http\Response('Foo');
$r->write('Bar');
$this->assertEquals('FooBar', $r->body());
}
/**
* Test write for replacing
*/
public function testWriteReplace()
{
$r = new \Slim\Http\Response('Foo');
$r->write('Bar', true);
$this->assertEquals('Bar', $r->body());
}
/**
* Test finalize
*/
public function testFinalize()
{
$r = new \Slim\Http\Response();
$r->status(404);
$r['Content-Type'] = 'application/json';
$r->write('Foo');
$result = $r->finalize();
$this->assertEquals(3, count($result));
$this->assertEquals(404, $result[0]);
$this->assertFalse(is_null($result[1]['Content-Type']));
$res = new \Slim\Http\Response();
$resFinal = $res->finalize();
$this->assertTrue(is_array($resFinal));
$this->assertEquals(3, count($resFinal));
$this->assertEquals(200, $resFinal[0]);
$this->assertInstanceOf('\Slim\Http\Headers', $resFinal[1]);
$this->assertEquals('', $resFinal[2]);
}
/**
* Test finalize
*/
public function testFinalizeWithoutBody()
public function testFinalizeWithEmptyBody()
{
$r = new \Slim\Http\Response();
$r->status(204);
$r['Content-Type'] = 'application/json';
$r->write('Foo');
$result = $r->finalize();
$this->assertEquals(3, count($result));
$this->assertEquals('', $result[2]);
$res = new \Slim\Http\Response('Foo', 304);
$resFinal = $res->finalize();
$this->assertEquals('', $resFinal[2]);
}
/**
* Test set cookie with only name and value
*/
public function testSetCookieWithNameAndValue()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', 'bar');
$this->assertEquals('foo=bar', $r['Set-Cookie']);
}
/**
* Test set multiple cookies with only name and value
*/
public function testSetMultipleCookiesWithNameAndValue()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', 'bar');
$r->setCookie('abc', '123');
$this->assertEquals("foo=bar\nabc=123", $r['Set-Cookie']);
}
/**
* Test set cookie only name and value and expires (as int)
*/
public function testSetMultipleCookiesWithNameAndValueAndExpiresAsInt()
{
$now = time();
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'expires' => $now
));
$this->assertEquals("foo=bar; expires=" . gmdate('D, d-M-Y H:i:s e', $now), $r['Set-Cookie']);
}
/**
* Test set cookie with only name and value and expires (as string)
*/
public function testSetMultipleCookiesWithNameAndValueAndExpiresAsString()
{
$expires = 'next Tuesday';
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'expires' => $expires
));
$this->assertEquals("foo=bar; expires=" . gmdate('D, d-M-Y H:i:s e', strtotime($expires)), $r['Set-Cookie']);
}
/**
* Test set cookie with name, value, domain
*/
public function testSetCookieWithNameAndValueAndDomain()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'domain' => '.slimframework.com'
));
$this->assertEquals('foo=bar; domain=.slimframework.com', $r['Set-Cookie']);
}
/**
* Test set cookie with name, value, domain, path
*/
public function testSetCookieWithNameAndValueAndDomainAndPath()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'domain' => '.slimframework.com',
'path' => '/foo'
));
$this->assertEquals($r['Set-Cookie'], 'foo=bar; domain=.slimframework.com; path=/foo');
}
/**
* Test set cookie with only name and value and secure flag
*/
public function testSetCookieWithNameAndValueAndSecureFlag()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'secure' => true
));
$this->assertEquals('foo=bar; secure', $r['Set-Cookie']);
}
/**
* Test set cookie with only name and value and secure flag (as non-truthy)
*/
public function testSetCookieWithNameAndValueAndSecureFlagAsNonTruthy()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'secure' => 0
));
$this->assertEquals('foo=bar', $r['Set-Cookie']);
}
/**
* Test set cookie with only name and value and httponly flag
*/
public function testSetCookieWithNameAndValueAndHttpOnlyFlag()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'httponly' => true
));
$this->assertEquals('foo=bar; HttpOnly', $r['Set-Cookie']);
}
/**
* Test set cookie with only name and value and httponly flag (as non-truthy)
*/
public function testSetCookieWithNameAndValueAndHttpOnlyFlagAsNonTruthy()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'httponly' => 0
));
$this->assertEquals('foo=bar', $r['Set-Cookie']);
}
/*
* Test delete cookie by name
*/
public function testDeleteCookieByName()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', 'bar');
$r->setCookie('abc', '123');
$r->deleteCookie('foo');
$this->assertEquals(1, preg_match("@abc=123\nfoo=; expires=@", $r['Set-Cookie']));
}
/*
* Test delete cookie by name and domain
*/
public function testDeleteCookieByNameAndDomain1()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', 'bar'); //Note: This does not have domain associated with it
$r->setCookie('abc', '123');
$r->deleteCookie('foo', array('domain' => '.slimframework.com')); //This SHOULD NOT remove the `foo` cookie
$this->assertEquals(1, preg_match("@foo=bar\nabc=123\nfoo=; domain=.slimframework.com; expires=@", $r['Set-Cookie']));
}
/*
* Test delete cookie by name and domain
*/
public function testDeleteCookieByNameAndDomain2()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', array(
'value' => 'bar',
'domain' => '.slimframework.com' //Note: This does have domain associated with it
));
$r->setCookie('abc', '123');
$r->deleteCookie('foo', array('domain' => '.slimframework.com')); //This SHOULD remove the `foo` cookie
$this->assertEquals(1, preg_match("@abc=123\nfoo=; domain=.slimframework.com; expires=@", $r['Set-Cookie']));
}
/**
* Test delete cookie by name and custom props
*/
public function testDeleteCookieByNameAndCustomProps()
{
$r = new \Slim\Http\Response();
$r->setCookie('foo', 'bar');
$r->setCookie('abc', '123');
$r->deleteCookie('foo', array(
'secure' => true,
'httponly' => true
));
$this->assertEquals(1, preg_match("@abc=123\nfoo=; expires=.*; secure; HttpOnly@", $r['Set-Cookie']));
}
/**
* Test redirect
*/
public function testRedirect()
{
$r = new \Slim\Http\Response();
$r->redirect('/foo');
$this->assertEquals(302, $r->status());
$this->assertEquals('/foo', $r['Location']);
$res = new \Slim\Http\Response();
$res->redirect('/foo');
$pStatus = new \ReflectionProperty($res, 'status');
$pStatus->setAccessible(true);
$this->assertEquals(302, $pStatus->getValue($res));
$this->assertEquals('/foo', $res->headers['Location']);
}
/**
* Test redirect with custom status
*/
public function testRedirectWithCustomStatus()
{
$r = new \Slim\Http\Response();
$r->redirect('/foo', 307);
$this->assertEquals(307, $r->status());
$this->assertEquals('/foo', $r['Location']);
}
/**
* Test isEmpty
*/
public function testIsEmpty()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(404);
$r2->status(201);
$r1->setStatus(404);
$r2->setStatus(201);
$this->assertFalse($r1->isEmpty());
$this->assertTrue($r2->isEmpty());
}
/**
* Test isClientError
*/
public function testIsClientError()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(404);
$r2->status(500);
$r1->setStatus(404);
$r2->setStatus(500);
$this->assertTrue($r1->isClientError());
$this->assertFalse($r2->isClientError());
}
/**
* Test isForbidden
*/
public function testIsForbidden()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(403);
$r2->status(500);
$r1->setStatus(403);
$r2->setStatus(500);
$this->assertTrue($r1->isForbidden());
$this->assertFalse($r2->isForbidden());
}
/**
* Test isInformational
*/
public function testIsInformational()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(100);
$r2->status(200);
$r1->setStatus(100);
$r2->setStatus(200);
$this->assertTrue($r1->isInformational());
$this->assertFalse($r2->isInformational());
}
/**
* Test isInformational
*/
public function testIsNotFound()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(404);
$r2->status(200);
$r1->setStatus(404);
$r2->setStatus(200);
$this->assertTrue($r1->isNotFound());
$this->assertFalse($r2->isNotFound());
}
/**
* Test isOk
*/
public function testIsOk()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(200);
$r2->status(201);
$r1->setStatus(200);
$r2->setStatus(201);
$this->assertTrue($r1->isOk());
$this->assertFalse($r2->isOk());
}
/**
* Test isSuccessful
*/
public function testIsSuccessful()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r3 = new \Slim\Http\Response();
$r1->status(200);
$r2->status(201);
$r3->status(302);
$r1->setStatus(200);
$r2->setStatus(201);
$r3->setStatus(302);
$this->assertTrue($r1->isSuccessful());
$this->assertTrue($r2->isSuccessful());
$this->assertFalse($r3->isSuccessful());
}
/**
* Test isRedirect
*/
public function testIsRedirect()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(307);
$r2->status(304);
$r1->setStatus(307);
$r2->setStatus(304);
$this->assertTrue($r1->isRedirect());
$this->assertFalse($r2->isRedirect());
}
/**
* Test isRedirection
*/
public function testIsRedirection()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r3 = new \Slim\Http\Response();
$r1->status(307);
$r2->status(304);
$r3->status(200);
$r1->setStatus(307);
$r2->setStatus(304);
$r3->setStatus(200);
$this->assertTrue($r1->isRedirection());
$this->assertTrue($r2->isRedirection());
$this->assertFalse($r3->isRedirection());
}
/**
* Test isServerError
*/
public function testIsServerError()
{
$r1 = new \Slim\Http\Response();
$r2 = new \Slim\Http\Response();
$r1->status(500);
$r2->status(400);
$r1->setStatus(500);
$r2->setStatus(400);
$this->assertTrue($r1->isServerError());
$this->assertFalse($r2->isServerError());
}
/**
* Test offset exists and offset get
*/
public function testOffsetExistsAndGet()
{
$r = new \Slim\Http\Response();
$this->assertFalse(empty($r['Content-Type']));
$this->assertNull($r['foo']);
}
/**
* Test iteration
*/
public function testIteration()
{
$h = new \Slim\Http\Response();
$output = '';
foreach ($h as $key => $value) {
$output .= $key . $value;
}
$this->assertEquals('Content-Typetext/html', $output);
}
/**
* Test countable
*/
public function testCountable()
{
$r1 = new \Slim\Http\Response();
$this->assertEquals(1, count($r1)); //Content-Type
}
/**
* Test message for code when message exists
*/
public function testMessageForCode()
{
$this->assertEquals('200 OK', \Slim\Http\Response::getMessageForCode(200));
}
/**
* Test message for code when message exists
*/
public function testMessageForCodeWithInvalidCode()
{
$this->assertNull(\Slim\Http\Response::getMessageForCode(600));
+45 -1
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -324,6 +324,50 @@ class SlimHttpUtilTest extends PHPUnit_Framework_TestCase
$this->assertEquals('foo=bar; domain=foo.com; path=/foo; expires=' . $expiresFormat . '; secure; HttpOnly', $header['Set-Cookie']);
}
/**
* Test serializeCookies and decrypt with string expires
*
* In this test a cookie with a string typed value for 'expires' is set,
* which should be parsed by `strtotime` to a timestamp when it's added to
* the headers; this timestamp should then be correctly parsed, and the
* value correctly decrypted, by `decodeSecureCookie`.
*/
public function testSerializeCookiesAndDecryptWithStringExpires()
{
$value = 'bar';
$headers = new \Slim\Http\Headers();
$settings = array(
'cookies.encrypt' => true,
'cookies.secret_key' => 'secret',
'cookies.cipher' => MCRYPT_RIJNDAEL_256,
'cookies.cipher_mode' => MCRYPT_MODE_CBC
);
$cookies = new \Slim\Http\Cookies();
$cookies->set('foo', array(
'value' => $value,
'expires' => '1 hour'
));
\Slim\Http\Util::serializeCookies($headers, $cookies, $settings);
$encrypted = $headers->get('Set-Cookie');
$encrypted = strstr($encrypted, ';', true);
$encrypted = urldecode(substr(strstr($encrypted, '='), 1));
$decrypted = \Slim\Http\Util::decodeSecureCookie(
$encrypted,
$settings['cookies.secret_key'],
$settings['cookies.cipher'],
$settings['cookies.cipher_mode']
);
$this->assertEquals($value, $decrypted);
$this->assertTrue($value !== $encrypted);
}
public function testDeleteCookieHeaderWithSurvivingCookie()
{
$header = array('Set-Cookie' => "foo=bar\none=two");
+63 -7
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -96,6 +96,21 @@ class LogTest extends PHPUnit_Framework_TestCase
}
public function testLogInfoExcludedByLevel()
{
$log = new \Slim\Log(new MyWriter());
$log->setLevel(\Slim\Log::NOTICE);
$this->assertFalse($log->info('Info'));
}
public function testLogNotice()
{
$this->expectOutputString('Notice');
$log = new \Slim\Log(new MyWriter());
$result = $log->notice('Notice');
$this->assertTrue($result);
}
public function testLogNoticeExcludedByLevel()
{
$log = new \Slim\Log(new MyWriter());
$log->setLevel(\Slim\Log::WARN);
@@ -106,7 +121,7 @@ class LogTest extends PHPUnit_Framework_TestCase
{
$this->expectOutputString('Warn');
$log = new \Slim\Log(new MyWriter());
$result = $log->warn('Warn');
$result = $log->warning('Warn');
$this->assertTrue($result);
}
@@ -114,7 +129,7 @@ class LogTest extends PHPUnit_Framework_TestCase
{
$log = new \Slim\Log(new MyWriter());
$log->setLevel(\Slim\Log::ERROR);
$this->assertFalse($log->warn('Warn'));
$this->assertFalse($log->warning('Warn'));
}
public function testLogError()
@@ -128,15 +143,56 @@ class LogTest extends PHPUnit_Framework_TestCase
public function testLogErrorExcludedByLevel()
{
$log = new \Slim\Log(new MyWriter());
$log->setLevel(\Slim\Log::FATAL);
$log->setLevel(\Slim\Log::CRITICAL);
$this->assertFalse($log->error('Error'));
}
public function testLogFatal()
public function testLogCritical()
{
$this->expectOutputString('Fatal');
$this->expectOutputString('Critical');
$log = new \Slim\Log(new MyWriter());
$result = $log->fatal('Fatal');
$result = $log->critical('Critical');
$this->assertTrue($result);
}
public function testLogCriticalExcludedByLevel()
{
$log = new \Slim\Log(new MyWriter());
$log->setLevel(\Slim\Log::ALERT);
$this->assertFalse($log->critical('Critical'));
}
public function testLogAlert()
{
$this->expectOutputString('Alert');
$log = new \Slim\Log(new MyWriter());
$result = $log->alert('Alert');
$this->assertTrue($result);
}
public function testLogAlertExcludedByLevel()
{
$log = new \Slim\Log(new MyWriter());
$log->setLevel(\Slim\Log::EMERGENCY);
$this->assertFalse($log->alert('Alert'));
}
public function testLogEmergency()
{
$this->expectOutputString('Emergency');
$log = new \Slim\Log(new MyWriter());
$result = $log->emergency('Emergency');
$this->assertTrue($result);
}
public function testInterpolateMessage()
{
$this->expectOutputString('Hello Slim !');
$log = new \Slim\Log(new MyWriter());
$result = $log->debug(
'Hello {framework} !',
array('framework' => "Slim")
);
$this->assertTrue($result);
}
+1 -1
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
+5 -4
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -104,10 +104,11 @@ class ContentTypesTest extends PHPUnit_Framework_TestCase
*/
public function testParsesXmlWithError()
{
libxml_use_internal_errors(true);
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'POST',
'CONTENT_TYPE' => 'application/xml',
'CONENT_LENGTH' => 68,
'CONTENT_LENGTH' => 68,
'slim.input' => '<books><book><id>1</id><author>Clive Cussler</book></books>' //<-- This should be incorrect!
));
$s = new \Slim\Slim();
@@ -126,7 +127,7 @@ class ContentTypesTest extends PHPUnit_Framework_TestCase
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'POST',
'CONTENT_TYPE' => 'text/csv',
'CONENT_LENGTH' => 44,
'CONTENT_LENGTH' => 44,
'slim.input' => "John,Doe,000-111-2222\nJane,Doe,111-222-3333"
));
$s = new \Slim\Slim();
@@ -148,7 +149,7 @@ class ContentTypesTest extends PHPUnit_Framework_TestCase
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'POST',
'CONTENT_TYPE' => 'application/json; charset=ISO-8859-4',
'CONENT_LENGTH' => 13,
'CONTENT_LENGTH' => 13,
'slim.input' => '{"foo":"bar"}'
));
$s = new \Slim\Slim();
+15 -2
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -86,7 +86,7 @@ class SlimFlashTest extends PHPUnit_Framework_TestCase
}
/**
* Test flash messages from preivous request do not persist to next request
* Test flash messages from previous request do not persist to next request
*/
public function testFlashMessagesFromPreviousRequestDoNotPersist()
{
@@ -125,4 +125,17 @@ class SlimFlashTest extends PHPUnit_Framework_TestCase
}
$this->assertEquals('infofooerrorbar', $output);
}
/**
* Test countable
*/
public function testCountable()
{
$_SESSION['slim.flash'] = array('info' => 'foo', 'error' => 'bar');
$f = new \Slim\MiddleWare\Flash();
$f->loadMessages();
$this->assertEquals(2, count($f));
}
}
+5 -5
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -64,7 +64,7 @@ class MethodOverrideTest extends PHPUnit_Framework_TestCase
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'POST',
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
'CONENT_LENGTH' => 11,
'CONTENT_LENGTH' => 11,
'slim.input' => '_METHOD=PUT'
));
$app = new CustomAppMethod();
@@ -98,7 +98,7 @@ class MethodOverrideTest extends PHPUnit_Framework_TestCase
}
/**
* Test does not override method if no method ovveride parameter
* Test does not override method if no method override parameter
*/
public function testDoesNotOverrideMethodAsPostWithoutParameter()
{
@@ -132,9 +132,9 @@ class MethodOverrideTest extends PHPUnit_Framework_TestCase
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'POST',
'CONTENT_TYPE' => 'application/json',
'CONENT_LENGTH' => 0,
'CONTENT_LENGTH' => 0,
'slim.input' => '',
'X_HTTP_METHOD_OVERRIDE' => 'DELETE'
'HTTP_X_HTTP_METHOD_OVERRIDE' => 'DELETE'
));
$app = new CustomAppMethod();
$mw = new \Slim\Middleware\MethodOverride();
+29 -1
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -122,4 +122,32 @@ class PrettyExceptionsTest extends PHPUnit_Framework_TestCase
$this->assertContains('LogicException', $app->response()->body());
}
/**
* Test with custom log
*/
public function testWithCustomLogWriter()
{
$this->setExpectedException('\LogicException');
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/index.php',
'PATH_INFO' => '/foo'
));
$app = new \Slim\Slim(array(
'log.enabled' => false
));
$app->container->singleton('log', function () use ($app) {
return new \Slim\Log(new \Slim\LogWriter('php://temp'));
});
$app->get('/foo', function () use ($app) {
throw new \LogicException('Test Message', 100);
});
$mw = new \Slim\Middleware\PrettyExceptions();
$mw->setApplication($app);
$mw->setNextMiddleware($app);
$mw->call();
$this->assertContains('LogicException', $app->response()->body());
}
}
+371 -17
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -34,10 +34,6 @@ class SessionCookieTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
if ( session_id() !== '' ) {
session_unset();
session_destroy();
}
$_SESSION = array();
}
@@ -45,10 +41,11 @@ class SessionCookieTest extends PHPUnit_Framework_TestCase
* Test session cookie is set and constructed correctly
*
* We test for two things:
* 1) That the HTTP cookie is added to the `Set-Cookie:` response header;
* 2) That the HTTP cookie is constructed in the expected format;
*
* 1) That the HTTP cookie exists with the correct name;
* 2) That the HTTP cookie's value is the expected value;
*/
public function testSessionCookieIsCreatedAndEncrypted()
public function testSessionCookieIsCreated()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/index.php',
@@ -56,6 +53,7 @@ class SessionCookieTest extends PHPUnit_Framework_TestCase
));
$app = new \Slim\Slim();
$app->get('/foo', function () {
$_SESSION['foo'] = 'bar';
echo "Success";
});
$mw = new \Slim\Middleware\SessionCookie(array('expires' => '10 years'));
@@ -63,26 +61,29 @@ class SessionCookieTest extends PHPUnit_Framework_TestCase
$mw->setNextMiddleware($app);
$mw->call();
list($status, $header, $body) = $app->response()->finalize();
$matches = array();
preg_match_all('@^slim_session=.+|.+|.+; expires=@', $header['Set-Cookie'], $matches, PREG_SET_ORDER);
$this->assertEquals(1, count($matches));
$this->assertTrue($app->response->cookies->has('slim_session'));
$cookie = $app->response->cookies->get('slim_session');
$this->assertEquals(serialize(array('foo' => 'bar')), $cookie['value']);
}
/**
* Test $_SESSION is populated from HTTP cookie
* Test $_SESSION is populated from an encrypted HTTP cookie
*
* The HTTP cookie in this test was created using the previous test; the encrypted cookie contains
* the serialized array ['foo' => 'bar']. The middleware secret, cipher, and cipher mode are assumed
* to be the default values.
* The encrypted cookie contains the serialized array ['foo' => 'bar']. The
* global secret, cipher, and cipher mode are assumed to be the default
* values.
*/
public function testSessionIsPopulatedFromCookie()
public function testSessionIsPopulatedFromEncryptedCookie()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/index.php',
'PATH_INFO' => '/foo',
'COOKIE' => 'slim_session=1644004961%7CLKkYPwqKIMvBK7MWl6D%2BxeuhLuMaW4quN%2F512ZAaVIY%3D%7Ce0f007fa852c7101e8224bb529e26be4d0dfbd63',
'HTTP_COOKIE' => 'slim_session=1644004961%7CLKkYPwqKIMvBK7MWl6D%2BxeuhLuMaW4quN%2F512ZAaVIY%3D%7Ce0f007fa852c7101e8224bb529e26be4d0dfbd63',
));
$app = new \Slim\Slim();
// The cookie value in the test is encrypted, so cookies.encrypt must
// be set to true
$app->config('cookies.encrypt', true);
$app->get('/foo', function () {
echo "Success";
});
@@ -93,6 +94,66 @@ class SessionCookieTest extends PHPUnit_Framework_TestCase
$this->assertEquals(array('foo' => 'bar'), $_SESSION);
}
/**
* Test $_SESSION is populated from an unencrypted HTTP cookie
*
* The unencrypted cookie contains the serialized array ['foo' => 'bar'].
* The global cookies.encrypt setting is set to false
*/
public function testSessionIsPopulatedFromUnencryptedCookie()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/index.php',
'PATH_INFO' => '/foo',
'HTTP_COOKIE' => 'slim_session=a%3A1%3A%7Bs%3A3%3A%22foo%22%3Bs%3A3%3A%22bar%22%3B%7D',
));
$app = new \Slim\Slim();
// The cookie value in the test is unencrypted, so cookies.encrypt must
// be set to false
$app->config('cookies.encrypt', false);
$app->get('/foo', function () {
echo "Success";
});
$mw = new \Slim\Middleware\SessionCookie(array('expires' => '10 years'));
$mw->setApplication($app);
$mw->setNextMiddleware($app);
$mw->call();
$this->assertEquals(array('foo' => 'bar'), $_SESSION);
}
public function testUnserializeErrorsAreCaughtAndLogged()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/index.php',
'PATH_INFO' => '/foo',
'HTTP_COOKIE' => 'slim_session=1644004961%7CLKkYPwqKIMvBK7MWl6D%2BxeuhLuMaW4quN%2F512ZAaVIY%3D%7Ce0f007fa852c7101e8224bb529e26be4d0dfbd63',
));
$logWriter = $this->getMockBuilder('Slim\LogWriter')
->disableOriginalConstructor()
->getMock();
$logWriter->expects($this->once())
->method('write');
$oldLevel = error_reporting(E_ALL);
$app = new \Slim\Slim(array(
'log.writer' => $logWriter
));
// Unserializing an encrypted value fails if cookie not decrpyted
$app->config('cookies.encrypt', false);
$app->get('/foo', function () {
echo "Success";
});
$mw = new \Slim\Middleware\SessionCookie(array('expires' => '10 years'));
$mw->setApplication($app);
$mw->setNextMiddleware($app);
$mw->call();
$this->assertEquals(array(), $_SESSION);
error_reporting($oldLevel);
}
/**
* Test $_SESSION is populated as empty array if no HTTP cookie
*/
@@ -112,4 +173,297 @@ class SessionCookieTest extends PHPUnit_Framework_TestCase
$mw->call();
$this->assertEquals(array(), $_SESSION);
}
public function testSerializingTooLongValueWritesLogAndDoesntCreateCookie()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/index.php',
'PATH_INFO' => '/foo'
));
$logWriter = $this->getMockBuilder('Slim\LogWriter')
->disableOriginalConstructor()
->getMock();
$logWriter->expects($this->once())
->method('write')
->with('WARNING! Slim\Middleware\SessionCookie data size is larger than 4KB. Content save failed.', \Slim\Log::ERROR);
$app = new \Slim\Slim(array(
'log.writer' => $logWriter
));
$tooLongValue = $this->getTooLongValue();
$app->get('/foo', function () use ($tooLongValue) {
$_SESSION['test'] = $tooLongValue;
echo "Success";
});
$mw = new \Slim\Middleware\SessionCookie(array('expires' => '10 years'));
$mw->setApplication($app);
$mw->setNextMiddleware($app);
$mw->call();
list($status, $header, $body) = $app->response()->finalize();
$this->assertFalse($app->response->cookies->has('slim_session'));
}
/**
* Generated by http://www.random.org/strings/
*/
protected function getTooLongValue()
{
return <<<EOF
8Y7WpaR3Fiyv0wF0QhKn
hwgh0SYA5cNOh85lSY3E
POUv7tHdFAKK0rmJnNUT
dxVjXuDUlStKTiC6B2rE
BMnchGCK2IIC83agjZ8t
K5U9tmPok3z7n7qFJPp4
YfMPI07qRBgZnYW3vvrj
mY1082KeqiegFNwGiUSs
HYE16N7PChio33DZWjsD
urQLFxD1I0FsxPO7rora
Nmas8nhLl1SiwnlL8eZX
y2xe18BWfXcNHDGkfaih
zXT7MxHXmnq0s4lowjcc
5n8lrXmjYtIdHxl2QcMb
emFTXQpPX9bw8WjulQCB
Peq12lgmurt988RZiquy
lQ5Dw86wMIcIm3uULhr7
T8Obj45ubR8poc1l5sIs
EG6kvcDIHVeQjUdrJuiw
sBLmZnLll23QGK8hMFO1
Pii0BXpzL9wpUt3hQnfe
prkTuA8zuxU8vMOu5uSi
Zynrx9BniMwYGPTOJVSd
ygUsr1GQ1KGJu6ukLvgo
7zrkBV0QM9jNqqvkzZwm
ZnoKRJI1SbaSCqsAduCt
q5RPtNVpHmtizY3QwiJs
8tjGt9MG37zgKx8KhfYE
ByoILEa2ceHjdrP6yd1G
UMHeFx2kOCx2DVeVJIkt
aiFdKMTE9rIbpObSp1fy
Aei9bjwcWwHT2S22rYnj
QHG7FEHSPdkw8acO393N
Ip4rgim3NKanJXpfdthy
yMh4EnoYBBoqSScfW4g5
bVrXYt8JkyCuR5Og0JBN
npKpr7obY4ZYVOnIF9Pe
soEmhC8uCw73bXzMy4ui
2oi4eFSXOoNDXZiAkz3c
zshSls22jy5QBvEJDDtY
C50qtymWnisOM1TIochg
OOnMEtkUHJw21kuw6m91
aqxRK1thCfPGBGytOmzG
kyqvZE1oEzCONj7q4I3p
rvbnRzZhXv21UXRJnR51
QJyXbtzDGpSljtSt4Fxi
SGiklrhWQCdRrnXuBExW
mQFykwpVF07NRPet9LdT
zOaOhwMfCeYQr2xqkKq7
Ru2dsZdhHzhBDvV5nv1q
qB7kVD0YDKB6RJyPcpX9
MxvPmISfMJMFiAFD82Qf
0idAAQxmd6fK9TkJPihp
wp2mvp3yAHWKmdsgLcss
OUqFt0BJoM7Iz4jJpF2d
hiWTsF1jrgEhV07TInLp
u7htseWgTDRY9UYp9wy6
vnF0vsaFikqpjGkLy5oH
ncvaoRPa5MDBNvXZoiG9
gCMvsGG8eLW3u8UWvRrk
bKgFBuy4zLhBF4RBuTVt
Dz1QmbyQPqmmfjchF6u3
myRkxQtSnHQZr0kUqSJS
Ati7CBQq6LOPSVlAek5a
7EOVTREQ52qOjKAibvAr
etvEUy3CbsDiaeSPGlJH
XyFey6LugF2UZfHDFjgV
ByBaUrDz0yuvLOvECQuS
5CoA6FBz8D71FwwebEYl
5xQyEV5h7lNAsgbjBY6o
N92xlCGjyNGWp1Y9HoNL
mMhirp7mufNYVqIy8jBl
nYSK8Rk6KybpAPspHXPd
oemmqqxjF9g4ZjNk2pyL
dqetI1RYqszZPZeH7WNW
B1x1GSPdGXnefeNmxFxr
vFTVOHqgOgZR0xHHUl8P
RwFio0Cd8ZkaRIpcs7jh
Ps0tGJgPyo1gRdm9wtlB
j4hmInyIpAz1MjHYAQc1
YIjnSirWsrItgqidgS3W
LNT7DriU7wPyN6zV9G6d
YFD19x1DDBwz57DegTsy
rz72EblrUsP6wtN69GRo
irhM6N9eNu8Bq9Qo5Tlc
Cpb3Zl3FDttiW63KXQpL
4ZQ7VGbfVjwBwhcGoOe7
RgXxZ9OU0HJFQRpjvJDW
lk3PpNhcHT4vVkgF9Q3V
URiazjSe8G4zHrBBMaxM
Gh7Xp4hqf9GTnIYyMe5E
palqUjJhSGm7EZAR1b4i
HN8qrHznKAyhlywYBw3N
nV9Kla5KFWaRG4r3cCT1
qHT7nPIbVjxNYdujh5WK
CKg7BfQjwZtHk2oM1cyO
RBPMpZxNpM3ZhiXNz5D2
xZJM9ETPwABBqHirjTXA
faI4irlrshHra2hg6mHE
N0OLyZjmKpyzHRlAcC44
oEMe1Mq85Kynyla7S3Lo
Us9auTpKq33jAI51MUvC
Vbu2qKSsmCrXu1WMDFfL
WCCzzLqz2kfMy3IV0ngc
ya4k4AoSjb2nd43VGRvt
1FrWocIRfoyFj3igs8lF
dQlTXv3jttgGmHVJtuJK
zCHcfzABc5pNch7cEW4B
r8jB0mL9ESrMHhvqGxbf
qLUYdNrXNJNujy43WNLt
GaQ6adUTFHErjRYFj7ws
btv28UZlttBqlVAEpu7G
1Se9HT2tp45a5iwbAHpA
tXaOwMjaI3S1uxngaVVL
saFZXdx4kExE3Y3SEMTA
my9rhAEFcw4N1uBqa2Ts
IRupwTKFoRIpPSBwnPPw
qpxq4VIrOdESR4UZiOcw
1n12beyYTUN0zNzV0nRf
dkgrmnaeWbrxA2QQaHDq
o1f6VCap62NxJI2Wd0F7
eyYYL6mY0XUmuCdV2v9e
SPBqa552akcetnRViZD9
cqLrX89ouNlDcjC7hmYk
3vAcrwlseFDYDYzrCXXx
tkyJUeJjORVXoFKaoEmi
o1JoqBFpSPyRT6RwFTXC
IMW657539XCcn0Tvx3iJ
rW9ZUNBSHNHjR0wfbr1R
x7Ez1Br1T9VG4wEetwfY
Xj9s0ipdQDEeYG3eCkBG
xQCp4J0a7BEqEEVPJvYY
S46aXD70Ur3BiokRfeJK
kEQcqPCP9kmWxXboESOB
VjADYs7ZwJUvWNAk0Msc
5cSrhWsbizSwo31NsPKj
PHKG7ui9gU0F5fXKXtWz
8FxjchkHJ3jQQSWKfkSu
pN8e9d71IVYA1vLyQGqV
Hh3QE3o9tmNsJMEBoRK8
QBLTFWWfkGSOI3Vp3y6c
5gwll5qdcgnaF4tDvdRd
NDYpacWX4hnFsrO73OOo
GaenbdbDOUp0iClZKlTU
79UJvctLD86KC2mwxSqc
jbwmzM4oZZ7zuYo769YY
B7Ssx6qbITbIqaJJboMK
7tLwsE3FhBphBJBKP4Bk
aumHnttxOXpiX3b5ivlk
gsvWRKCd1KLYkucRdW1j
j0TSXNoMGXlIK9X6YjX1
4zvHH7QEPlgK4AaRWw6r
eXSVfE2X2nbn1wzA3bdw
exrWkKQ8v87kzzxpzdF7
wL9B42yeyA6SgfnZ0SnW
hyO53wkaJNQnK2rzndcA
8jSesmehxaHL39QUdlEo
oAQMANsGVewC4cYhdjpk
tBVMFz1LMIg8nj5acoKx
4IxsrP4UrdaHa2QlFZ38
OMg0erS6Mg2nVY9PBLGu
WLybJJlrNJ3ZKgftRyOb
s392j4FVZuxnLc8Euq2g
2AB9ceeOXHrw6dJeqImY
q8Gqy9rzsKyp9vEg13h3
UhWoiMQuE8i38vd5HZuO
CjLfC9MtQY7wou4YGl1f
bQGFeV3I1YVsyh1zjdYX
E4yS7PXLT2pTvq9aTuPc
41Vm8F6tc6mnYCc0gfCY
nmKOUzThbGpqnSkJzmr5
E5izT37qIM1PJ1IotRnw
X0rD7K2rUN47XeLXW3x2
3taWQ4GMNGQgjuD7MPwX
u7AyGdUWFG25ZaeZSyrt
mLPs4NU5ayAgrj9L089E
5mWnKfJ8OoAbhjb9XpY5
cBv75uTcpezbnWe5C7YC
DWikoIaaJQebFW2tddw2
qMyIzbkUJxyTheONxBjJ
WyWqJmTW5uniw9ofX84U
JaFGtu4y24UGSmPrIjVj
SDFz3iRvf2FG65m8brV9
0mpT6dWL4p59cdTs0n1c
jw7rIgu3VFnkuOp8mZR3
F1PPQYZfZkqbyiu7Tvl6
tXT8EPpH39oB9Qe3SI6C
DwL6cklHbnOyEOO5jNOo
vEORF3tEYRngOowzuOEY
6XY27pGEG9L9MvwvHinw
rEMyl7S9eFk554yHvCa3
pLToqRXBWIPK51roFlKs
AXfdbVdGkGqwlKn68k01
ecFbbnvrpmcLF2gL3GbC
aWJf90PECBF0qqZ1jVC3
WjMuah0gZjryj7zsZKMB
1J9koTowUYguyp4MBGmp
rnjhybC8RQSEvmYpqkGR
Qdj2QlGYXN1H8A1315QJ
amycQeWwXnrdI3duyqTa
H2YwgIStIGQlWNigfiIZ
btR0CdDnkwGt0hlCtQF0
O37vtIvVgCKVbcXbBexH
xhkbsShz4onN4CeGf7Ox
1vJfx422pUnxtjG5Laag
3IV5ib20qSYZW3Wr2LiH
zmvoTLblxTX3EpYPlHxC
U0Ceix4L3dMomXzn7OAC
JyzkRGfIi8j4EnKfoWPG
gMUXWXZZgJzLBfZ4y9FV
7ClYOAd9EoWspOWQ1MmO
1CIKB3Ei846C1rmXS8Zc
ARLDXFpaHp1VlbEMF8fk
KrQa28U3gbHs9B5oGhxS
WHc9LmQiINcUglo8cKPs
3WMYJ8TAtvlMswUPOd6t
s81Cy4B2oLrc4E5XSa5p
QA6pDUiKipuWFXZ4BMUF
E03CQbBiZ27GpJekftsF
pqGkJifdjVLuuIu0xBej
V27rk0vIwp1Q8p4DvJ1F
TPhvHNooyU6Rrmcx8GIK
81nRsYYsvVo3LCmuOnX5
uY6xGTes3UOMXkXwEfGj
T5LfaSyWP5y4L7vvLBjS
dHO7dVB1bmIA40fEgk3i
KHJxU6C0rUVtPtIR1slm
YdhTz1mwWi2z2GDzzRJB
TIzFqPkKrgCgkiv2RzCg
Z0qY4Wjpfug51zXzU51H
nWm3mJnVLAKv9RNkdThl
xk28IMKOGOPdQuXjGDB6
eEG3ndIRXnmmLilygHop
jE6u88nWi30Yos79canx
b0VuROFF04rZuOTo5Fue
yt4fSpHN7v4uZ7uNPMA9
0sENIYeLlIbBWhqTjXCp
m7qMMX3acdRtTTVNp7Qt
s8XKOJmCQr7YGk47jGMn
6o1kxMmoUgWCW8rEtnWA
kxXj1hKRFBJmX8ErM6Zp
FZBIPSbNt5hmXoC1M92l
UxeirI2PCJnQcAJVmNVJ
FaJ9L5K0u1J9JKGl2Aew
bHGX5QLvkGXSFY5OCezp
5cnbOjU1j8Fuvtuuk9d0
7Oz2IIi69WB5J14n9iWQ
XgCpDLURX3urpiYDFf3P
7xeWOS4yTMUQ0EbLkZOU
AzKM3Dp7nGr9SYPI4xmi
EOF;
}
}
+31 -38
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -30,57 +30,50 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
class My_Middleware extends \Slim\Middleware
class MyMiddleware extends \Slim\Middleware
{
public function call()
{
echo "Before";
$this->next->call();
echo "After";
}
}
class My_Application
{
public function call()
{
echo "Application";
}
public function call() {}
}
class MiddlewareTest extends PHPUnit_Framework_TestCase
{
/**
* Get and set application
*/
public function testGetAndSetApplication()
public function testSetApplication()
{
$app = new My_Application();
$mw = new My_Middleware();
$app = new stdClass();
$mw = new MyMiddleware();
$mw->setApplication($app);
$this->assertAttributeSame($app, 'app', $mw);
}
public function testGetApplication()
{
$app = new stdClass();
$mw = new MyMiddleware();
$property = new \ReflectionProperty($mw, 'app');
$property->setAccessible(true);
$property->setValue($mw, $app);
$this->assertSame($app, $mw->getApplication());
}
/**
* Get and set next middleware
*/
public function testGetAndSetNextMiddleware()
public function testSetNextMiddleware()
{
$mw1 = new My_Middleware();
$mw2 = new My_Middleware();
$mw1 = new MyMiddleware();
$mw2 = new MyMiddleware();
$mw1->setNextMiddleware($mw2);
$this->assertSame($mw2, $mw1->getNextMiddleware());
$this->assertAttributeSame($mw2, 'next', $mw1);
}
/**
* Test call
*/
public function testCall()
public function testGetNextMiddleware()
{
$this->expectOutputString('BeforeApplicationAfter');
$app = new My_Application();
$mw = new My_Middleware();
$mw->setNextMiddleware($app);
$mw->call();
$mw1 = new MyMiddleware();
$mw2 = new MyMiddleware();
$property = new \ReflectionProperty($mw1, 'next');
$property->setAccessible(true);
$property->setValue($mw1, $mw2);
$this->assertSame($mw2, $mw1->getNextMiddleware());
}
}
+375 -399
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -29,292 +29,202 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Used for passing callable via string
function testCallable() {}
class RouteTest extends PHPUnit_Framework_TestCase
{
/**
* Route should set name
*/
public function testRouteSetsName()
public function testGetPattern()
{
$route = new \Slim\Route('/foo/bar', function () {});
$route->name('foo');
$route = new \Slim\Route('/foo', function () {});
$this->assertEquals('/foo', $route->getPattern());
}
public function testGetName()
{
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($route, 'name');
$property->setAccessible(true);
$property->setValue($route, 'foo');
$this->assertEquals('foo', $route->getName());
}
/**
* Route should set pattern
*/
public function testRouteSetsPattern()
public function testSetName()
{
$route1 = new \Slim\Route('/foo/bar', function () {});
$this->assertEquals('/foo/bar', $route1->getPattern());
$route = new \Slim\Route('/foo', function () {});
$route->name('foo'); // <-- Alias for `setName()`
$this->assertAttributeEquals('foo', 'name', $route);
}
/**
* Route sets pattern with params
*/
public function testRouteSetsPatternWithParams()
public function testGetCallable()
{
$route = new \Slim\Route('/hello/:first/:last', function () {});
$this->assertEquals('/hello/:first/:last', $route->getPattern());
}
$callable = function () {
echo 'Foo';
};
$route = new \Slim\Route('/foo', $callable);
/**
* Route sets custom pattern that overrides pattern
*/
public function testRouteSetsCustomTemplate()
{
$route = new \Slim\Route('/hello/*', function () {});
$route->setPattern('/hello/:name');
$this->assertEquals('/hello/:name', $route->getPattern());
}
/**
* Route should store a reference to the callable
* anonymous function.
*/
public function testRouteSetsCallableAsFunction()
{
$callable = function () { echo "Foo!"; };
$route = new \Slim\Route('/foo/bar', $callable);
$this->assertSame($callable, $route->getCallable());
}
/**
* Route should store a reference to the callable
* regular function (for PHP 5 < 5.3)
*/
public function testRouteSetsCallableAsString()
public function testGetCallableAsClass()
{
$route = new \Slim\Route('/foo/bar', 'testCallable');
$this->assertEquals('testCallable', $route->getCallable());
$route = new \Slim\Route('/foo', '\Slim\Router:getCurrentRoute');
$callable = $route->getCallable();
$this->assertInstanceOf('\Slim\Router', $callable[0]);
$this->assertEquals('getCurrentRoute', $callable[1]);
}
/**
* Route should throw exception when creating with an invalid callable
*/
public function testRouteThrowsExecptionWithInvalidCallable()
public function testGetCallableAsStaticMethod()
{
$this->setExpectedException('InvalidArgumentException');
$route = new \Slim\Route('/foo/bar', 'fnDoesNotExist');
$route = new \Slim\Route('/bar', '\Slim\Slim::getInstance');
$callable = $route->getCallable();
$this->assertEquals('\Slim\Slim::getInstance', $callable);
}
/**
* Route should throw exception when setting an invalid callable
*/
public function testRouteThrowsExecptionWhenSettingInvalidCallable()
public function testSetCallable()
{
$route = new \Slim\Route('/foo/bar', function () {});
try
{
$route->setCallable('fnDoesNotExist');
$this->fail('Did not catch InvalidArgumentException when setting invalid callable');
} catch(\InvalidArgumentException $e) {}
$callable = function () {
echo 'Foo';
};
$route = new \Slim\Route('/foo', $callable); // <-- Called inside __construct()
$this->assertAttributeSame($callable, 'callable', $route);
}
/**
* Test gets all params
*/
public function testGetRouteParams()
public function testSetCallableWithInvalidArgument()
{
$this->setExpectedException('\InvalidArgumentException');
$route = new \Slim\Route('/foo', 'doesNotExist'); // <-- Called inside __construct()
}
public function testGetParams()
{
// Prepare route
$requestUri = '/hello/mr/anderson';
$route = new \Slim\Route('/hello/:first/:last', function () {});
$route->matches('/hello/mr/anderson'); // <-- Parses params from argument
// Parse route params
$this->assertTrue($route->matches($requestUri));
// Get params
$params = $route->getParams();
$this->assertEquals(2, count($params));
$this->assertEquals('mr', $params['first']);
$this->assertEquals('anderson', $params['last']);
$this->assertEquals(array(
'first' => 'mr',
'last' => 'anderson'
), $route->getParams());
}
/**
* Test sets all params
*/
public function testSetRouteParams()
public function testSetParams()
{
// Prepare route
$requestUri = '/hello/mr/anderson';
$route = new \Slim\Route('/hello/:first/:last', function () {});
// Parse route params
$this->assertTrue($route->matches($requestUri));
// Get params
$params = $route->getParams();
$this->assertEquals(2, count($params));
$this->assertEquals('mr', $params['first']);
$this->assertEquals('anderson', $params['last']);
// Replace params
$route->matches('/hello/mr/anderson'); // <-- Parses params from argument
$route->setParams(array(
'first' => 'john',
'first' => 'agent',
'last' => 'smith'
));
// Get new params
$params = $route->getParams();
$this->assertEquals(2, count($params));
$this->assertEquals('john', $params['first']);
$this->assertEquals('smith', $params['last']);
$this->assertAttributeEquals(array(
'first' => 'agent',
'last' => 'smith'
), 'params', $route);
}
/**
* Test gets param when exists
*/
public function testGetRouteParamWhenExists()
public function testGetParam()
{
// Prepare route
$requestUri = '/hello/mr/anderson';
$route = new \Slim\Route('/hello/:first/:last', function () {});
// Parse route params
$this->assertTrue($route->matches($requestUri));
$property = new \ReflectionProperty($route, 'params');
$property->setAccessible(true);
$property->setValue($route, array(
'first' => 'foo',
'last' => 'bar'
));
// Get param
$this->assertEquals('anderson', $route->getParam('last'));
$this->assertEquals('foo', $route->getParam('first'));
}
/**
* Test gets param when not exists
*/
public function testGetRouteParamWhenNotExists()
public function testGetParamThatDoesNotExist()
{
// Prepare route
$requestUri = '/hello/mr/anderson';
$this->setExpectedException('InvalidArgumentException');
$route = new \Slim\Route('/hello/:first/:last', function () {});
// Parse route params
$this->assertTrue($route->matches($requestUri));
$property = new \ReflectionProperty($route, 'params');
$property->setAccessible(true);
$property->setValue($route, array(
'first' => 'foo',
'last' => 'bar'
));
// Get param
try {
$param = $route->getParam('foo');
$this->fail('Did not catch expected InvalidArgumentException');
} catch ( \InvalidArgumentException $e ) {}
$route->getParam('middle');
}
/**
* Test sets param when exists
*/
public function testSetRouteParamWhenExists()
public function testSetParam()
{
// Prepare route
$requestUri = '/hello/mr/anderson';
$route = new \Slim\Route('/hello/:first/:last', function () {});
// Parse route params
$this->assertTrue($route->matches($requestUri));
// Get param
$this->assertEquals('anderson', $route->getParam('last'));
// Set param
$route->matches('/hello/mr/anderson'); // <-- Parses params from argument
$route->setParam('last', 'smith');
// Get new param
$this->assertEquals('smith', $route->getParam('last'));
$this->assertAttributeEquals(array(
'first' => 'mr',
'last' => 'smith'
), 'params', $route);
}
/**
* Test sets param when not exists
*/
public function testSetRouteParamWhenNotExists()
public function testSetParamThatDoesNotExist()
{
// Prepare route
$requestUri = '/hello/mr/anderson';
$this->setExpectedException('InvalidArgumentException');
$route = new \Slim\Route('/hello/:first/:last', function () {});
// Parse route params
$this->assertTrue($route->matches($requestUri));
// Get param
try {
$param = $route->setParam('foo', 'bar');
$this->fail('Did not catch expected InvalidArgumentException');
} catch ( \InvalidArgumentException $e ) {}
$route->matches('/hello/mr/anderson'); // <-- Parses params from argument
$route->setParam('middle', 'smith'); // <-- Should trigger InvalidArgumentException
}
/**
* If route matches a resource URI, param should be extracted.
*/
public function testRouteMatchesAndParamExtracted()
public function testMatches()
{
$resource = '/hello/Josh';
$route = new \Slim\Route('/hello/:name', function () {});
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('name' => 'Josh'), $route->getParams());
$this->assertTrue($route->matches('/hello/josh'));
}
/**
* If route matches a resource URI, multiple params should be extracted.
*/
public function testRouteMatchesAndMultipleParamsExtracted()
public function testMatchesIsFalse()
{
$route = new \Slim\Route('/foo', function () {});
$this->assertFalse($route->matches('/bar'));
}
public function testMatchesPatternWithTrailingSlash()
{
$route = new \Slim\Route('/foo/', function () {});
$this->assertTrue($route->matches('/foo/'));
$this->assertTrue($route->matches('/foo'));
}
public function testMatchesPatternWithoutTrailingSlash()
{
$route = new \Slim\Route('/foo', function () {});
$this->assertFalse($route->matches('/foo/'));
$this->assertTrue($route->matches('/foo'));
}
public function testMatchesWithConditions()
{
$resource = '/hello/Josh/and/John';
$route = new \Slim\Route('/hello/:first/and/:second', function () {});
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('first' => 'Josh', 'second' => 'John'), $route->getParams());
$route->conditions(array(
'first' => '[a-zA-Z]{3,}'
));
$this->assertTrue($route->matches('/hello/Josh/and/John'));
}
/**
* If route does not match a resource URI, params remain an empty array
*/
public function testRouteDoesNotMatchAndParamsNotExtracted()
public function testMatchesWithConditionsIsFalse()
{
$resource = '/foo/bar';
$route = new \Slim\Route('/hello/:name', function () {});
$result = $route->matches($resource);
$this->assertFalse($result);
$this->assertEquals(array(), $route->getParams());
}
/**
* Route matches URI with trailing slash
*
*/
public function testRouteMatchesWithTrailingSlash()
{
$resource1 = '/foo/bar/';
$resource2 = '/foo/bar';
$route = new \Slim\Route('/foo/:one/', function () {});
$this->assertTrue($route->matches($resource1));
$this->assertTrue($route->matches($resource2));
}
/**
* Route matches URI with conditions
*/
public function testRouteMatchesResourceWithConditions()
{
$resource = '/hello/Josh/and/John';
$route = new \Slim\Route('/hello/:first/and/:second', function () {});
$route->conditions(array('first' => '[a-zA-Z]{3,}'));
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('first' => 'Josh', 'second' => 'John'), $route->getParams());
}
$route->conditions(array(
'first' => '[a-z]{3,}'
));
/**
* Route does not match URI with conditions
*/
public function testRouteDoesNotMatchResourceWithConditions()
{
$resource = '/hello/Josh/and/John';
$route = new \Slim\Route('/hello/:first/and/:second', function () {});
$route->conditions(array('first' => '[a-z]{3,}'));
$result = $route->matches($resource);
$this->assertFalse($result);
$this->assertEquals(array(), $route->getParams());
$this->assertFalse($route->matches('/hello/Josh/and/John'));
}
/*
@@ -324,14 +234,12 @@ class RouteTest extends PHPUnit_Framework_TestCase
*
* Excludes "+" which is valid but decodes into a space character
*/
public function testRouteMatchesResourceWithValidRfc2396PathComponent()
public function testMatchesWithValidRfc2396PathComponent()
{
$symbols = ':@&=$,';
$resource = '/rfc2386/' . $symbols;
$route = new \Slim\Route('/rfc2386/:symbols', function () {});
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('symbols' => $symbols), $route->getParams());
$this->assertTrue($route->matches('/rfc2386/' . $symbols));
}
/*
@@ -339,221 +247,289 @@ class RouteTest extends PHPUnit_Framework_TestCase
*
* "Uniform Resource Identifiers (URI): Generic Syntax" http://www.ietf.org/rfc/rfc2396.txt
*/
public function testRouteMatchesResourceWithUnreservedMarks()
public function testMatchesWithUnreservedMarks()
{
$marks = "-_.!~*'()";
$resource = '/marks/' . $marks;
$route = new \Slim\Route('/marks/:marks', function () {});
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('marks' => $marks), $route->getParams());
$this->assertTrue($route->matches('/marks/' . $marks));
}
/**
* Route optional parameters
*
* Pre-conditions:
* Route pattern requires :year, optionally accepts :month and :day
*
* Post-conditions:
* All: Year is 2010
* Case A: Month and day default values are used
* Case B: Month is "05" and day default value is used
* Case C: Month is "05" and day is "13"
*/
public function testRouteOptionalParameters()
public function testMatchesOptionalParameters()
{
$pattern = '/archive/:year(/:month(/:day))';
//Case A
$routeA = new \Slim\Route($pattern, function () {});
$resourceA = '/archive/2010';
$resultA = $routeA->matches($resourceA);
$this->assertTrue($resultA);
$this->assertEquals(array('year' => '2010'), $routeA->getParams());
$route1 = new \Slim\Route($pattern, function () {});
$this->assertTrue($route1->matches('/archive/2010'));
$this->assertEquals(array('year' => '2010'), $route1->getParams());
//Case B
$routeB = new \Slim\Route($pattern, function () {});
$resourceB = '/archive/2010/05';
$resultB = $routeB->matches($resourceB);
$this->assertTrue($resultB);
$this->assertEquals(array('year' => '2010', 'month' => '05'), $routeB->getParams());
$route2 = new \Slim\Route($pattern, function () {});
$this->assertTrue($route2->matches('/archive/2010/05'));
$this->assertEquals(array('year' => '2010', 'month' => '05'), $route2->getParams());
//Case C
$routeC = new \Slim\Route($pattern, function () {});
$resourceC = '/archive/2010/05/13';
$resultC = $routeC->matches($resourceC);
$this->assertTrue($resultC);
$this->assertEquals(array('year' => '2010', 'month' => '05', 'day' => '13'), $routeC->getParams());
$route3 = new \Slim\Route($pattern, function () {});
$this->assertTrue($route3->matches('/archive/2010/05/13'));
$this->assertEquals(array('year' => '2010', 'month' => '05', 'day' => '13'), $route3->getParams());
}
/**
* Test route default conditions
*
* Pre-conditions:
* Route class has default conditions;
*
* Post-conditions:
* Case A: Route instance has default conditions;
* Case B: Route instance has newly merged conditions;
*/
public function testRouteDefaultConditions()
public function testGetConditions()
{
\Slim\Route::setDefaultConditions(array('id' => '\d+'));
$r = new \Slim\Route('/foo', function () {});
//Case A
$this->assertEquals(\Slim\Route::getDefaultConditions(), $r->getConditions());
//Case B
$r->conditions(array('name' => '[a-z]{2,5}'));
$c = $r->getConditions();
$this->assertArrayHasKey('id', $c);
$this->assertArrayHasKey('name', $c);
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($route, 'conditions');
$property->setAccessible(true);
$property->setValue($route, array('foo' => '\d{3}'));
$this->assertEquals(array('foo' => '\d{3}'), $route->getConditions());
}
/**
* Route matches URI with wildcard
*/
public function testRouteMatchesResourceWithWildcard()
public function testSetDefaultConditions()
{
\Slim\Route::setDefaultConditions(array(
'id' => '\d+'
));
$property = new \ReflectionProperty('\Slim\Route', 'defaultConditions');
$property->setAccessible(true);
$this->assertEquals(array(
'id' => '\d+'
), $property->getValue());
}
public function testGetDefaultConditions()
{
$property = new \ReflectionProperty('\Slim\Route', 'defaultConditions');
$property->setAccessible(true);
$property->setValue(array(
'id' => '\d+'
));
$this->assertEquals(array(
'id' => '\d+'
), \Slim\Route::getDefaultConditions());
}
public function testDefaultConditionsAssignedToInstance()
{
$staticProperty = new \ReflectionProperty('\Slim\Route', 'defaultConditions');
$staticProperty->setAccessible(true);
$staticProperty->setValue(array(
'id' => '\d+'
));
$route = new \Slim\Route('/foo', function () {});
$this->assertAttributeEquals(array(
'id' => '\d+'
), 'conditions', $route);
}
public function testMatchesWildcard()
{
$resource = '/hello/foo/bar/world';
$route = new \Slim\Route('/hello/:path+/world', function () {});
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('path'=>array('foo', 'bar')), $route->getParams());
$this->assertTrue($route->matches('/hello/foo/bar/world'));
$this->assertAttributeEquals(array(
'path' => array('foo', 'bar')
), 'params', $route);
}
/**
* Route matches URI with more than one wildcard
*/
public function testRouteMatchesResourceWithMultipleWildcards()
public function testMatchesMultipleWildcards()
{
$resource = '/hello/foo/bar/world/2012/03/10';
$route = new \Slim\Route('/hello/:path+/world/:date+', function () {});
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('path'=>array('foo', 'bar'), 'date'=>array('2012', '03', '10')), $route->getParams());
$this->assertTrue($route->matches('/hello/foo/bar/world/2012/03/10'));
$this->assertAttributeEquals(array(
'path' => array('foo', 'bar'),
'date' => array('2012', '03', '10')
), 'params', $route);
}
/**
* Route matches URI with wildcards and parameters
*/
public function testRouteMatchesResourceWithWildcardsAndParams()
public function testMatchesParamsAndWildcards()
{
$resource = '/hello/foo/bar/world/2012/03/10/first/second';
$route = new \Slim\Route('/hello/:path+/world/:year/:month/:day/:path2+', function () {});
$result = $route->matches($resource);
$this->assertTrue($result);
$this->assertEquals(array('path'=>array('foo', 'bar'), 'year'=>'2012', 'month'=>'03', 'day'=>'10', 'path2'=>array('first', 'second')), $route->getParams());
$this->assertTrue($route->matches('/hello/foo/bar/world/2012/03/10/first/second'));
$this->assertAttributeEquals(array(
'path' => array('foo', 'bar'),
'year' => '2012',
'month' => '03',
'day' => '10',
'path2' => array('first', 'second')
), 'params', $route);
}
public function testMatchesParamsWithOptionalWildcard()
{
$route = new \Slim\Route('/hello(/:foo(/:bar+))', function () {});
$this->assertTrue($route->matches('/hello'));
$this->assertTrue($route->matches('/hello/world'));
$this->assertTrue($route->matches('/hello/world/foo'));
$this->assertTrue($route->matches('/hello/world/foo/bar'));
}
public function testSetMiddleware()
{
$route = new \Slim\Route('/foo', function () {});
$mw = function () {
echo 'Foo';
};
$route->setMiddleware($mw);
$this->assertAttributeContains($mw, 'middleware', $route);
}
public function testSetMiddlewareMultipleTimes()
{
$route = new \Slim\Route('/foo', function () {});
$mw1 = function () {
echo 'Foo';
};
$mw2 = function () {
echo 'Bar';
};
$route->setMiddleware($mw1);
$route->setMiddleware($mw2);
$this->assertAttributeContains($mw1, 'middleware', $route);
$this->assertAttributeContains($mw2, 'middleware', $route);
}
public function testSetMiddlewareWithArray()
{
$route = new \Slim\Route('/foo', function () {});
$mw1 = function () {
echo 'Foo';
};
$mw2 = function () {
echo 'Bar';
};
$route->setMiddleware(array($mw1, $mw2));
$this->assertAttributeContains($mw1, 'middleware', $route);
$this->assertAttributeContains($mw2, 'middleware', $route);
}
public function testSetMiddlewareWithInvalidArgument()
{
$this->setExpectedException('InvalidArgumentException');
$route = new \Slim\Route('/foo', function () {});
$route->setMiddleware('doesNotExist'); // <-- Should throw InvalidArgumentException
}
public function testSetMiddlewareWithArrayWithInvalidArgument()
{
$this->setExpectedException('InvalidArgumentException');
$route = new \Slim\Route('/foo', function () {});
$route->setMiddleware(array('doesNotExist'));
}
public function testGetMiddleware()
{
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($route, 'middleware');
$property->setAccessible(true);
$property->setValue($route, array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $route->getMiddleware());
}
public function testSetHttpMethods()
{
$route = new \Slim\Route('/foo', function () {});
$route->setHttpMethods('GET', 'POST');
$this->assertAttributeEquals(array('GET', 'POST'), 'methods', $route);
}
public function testGetHttpMethods()
{
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($route, 'methods');
$property->setAccessible(true);
$property->setValue($route, array('GET', 'POST'));
$this->assertEquals(array('GET', 'POST'), $route->getHttpMethods());
}
public function testAppendHttpMethods()
{
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($route, 'methods');
$property->setAccessible(true);
$property->setValue($route, array('GET', 'POST'));
$route->appendHttpMethods('PUT');
$this->assertAttributeEquals(array('GET', 'POST', 'PUT'), 'methods', $route);
}
public function testAppendHttpMethodsWithVia()
{
$route = new \Slim\Route('/foo', function () {});
$route->via('PUT');
$this->assertAttributeContains('PUT', 'methods', $route);
}
public function testSupportsHttpMethod()
{
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($route, 'methods');
$property->setAccessible(true);
$property->setValue($route, array('POST'));
$this->assertTrue($route->supportsHttpMethod('POST'));
$this->assertFalse($route->supportsHttpMethod('PUT'));
}
/**
* Route matches URI with optional wildcard and parameter
* Test dispatch with params
*/
public function testRouteMatchesResourceWithOptionalWildcardsAndParams()
public function testDispatch()
{
$resourceA = '/hello/world/foo/bar';
$routeA = new \Slim\Route('/hello(/:world(/:path+))', function () {});
$this->assertTrue($routeA->matches($resourceA));
$this->assertEquals(array('world'=>'world', 'path'=>array('foo', 'bar')), $routeA->getParams());
$resourceB = '/hello/world';
$routeB = new \Slim\Route('/hello(/:world(/:path))', function () {});
$this->assertTrue($routeB->matches($resourceB));
$this->assertEquals(array('world'=>'world'), $routeB->getParams());
$this->expectOutputString('Hello josh');
$route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; });
$route->matches('/hello/josh'); //<-- Extracts params from resource URI
$route->dispatch();
}
/**
* Route does not match URI with wildcard
* Test dispatch with middleware
*/
public function testRouteDoesNotMatchResourceWithWildcard()
public function testDispatchWithMiddleware()
{
$resource = '/hello';
$route = new \Slim\Route('/hello/:path+', function () {});
$result = $route->matches($resource);
$this->assertFalse($result);
$this->assertEquals(array(), $route->getParams());
$this->expectOutputString('First! Second! Hello josh');
$route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; });
$route->setMiddleware(function () {
echo "First! ";
});
$route->setMiddleware(function () {
echo "Second! ";
});
$route->matches('/hello/josh'); //<-- Extracts params from resource URI
$route->dispatch();
}
/**
* Test route sets and gets middleware
*
* Pre-conditions:
* Route instantiated
*
* Post-conditions:
* Case A: Middleware set as callable, not array
* Case B: Middleware set after other middleware already set
* Case C: Middleware set as array of callables
* Case D: Middleware set as a callable array
* Case E: Middleware is invalid; throws InvalidArgumentException
* Case F: Middleware is an array with one invalid callable; throws InvalidArgumentException
* Test middleware with arguments
*/
public function testRouteMiddleware()
public function testRouteMiddlwareArguments()
{
$callable1 = function () {};
$callable2 = function () {};
//Case A
$r1 = new \Slim\Route('/foo', function () {});
$r1->setMiddleware($callable1);
$mw = $r1->getMiddleware();
$this->assertInternalType('array', $mw);
$this->assertEquals(1, count($mw));
//Case B
$r1->setMiddleware($callable2);
$mw = $r1->getMiddleware();
$this->assertEquals(2, count($mw));
//Case C
$r2 = new \Slim\Route('/foo', function () {});
$r2->setMiddleware(array($callable1, $callable2));
$mw = $r2->getMiddleware();
$this->assertInternalType('array', $mw);
$this->assertEquals(2, count($mw));
//Case D
$r3 = new \Slim\Route('/foo', function () {});
$r3->setMiddleware(array($this, 'callableTestFunction'));
$mw = $r3->getMiddleware();
$this->assertInternalType('array', $mw);
$this->assertEquals(1, count($mw));
//Case E
try {
$r3->setMiddleware('sdjfsoi788');
$this->fail('Did not catch InvalidArgumentException when setting invalid route middleware');
} catch ( \InvalidArgumentException $e ) {}
//Case F
try {
$r3->setMiddleware(array($callable1, $callable2, 'sdjfsoi788'));
$this->fail('Did not catch InvalidArgumentException when setting an array with one invalid route middleware');
} catch ( \InvalidArgumentException $e ) {}
}
public function callableTestFunction() {}
/**
* Test that a Route manages the HTTP methods that it supports
*
* Case A: Route initially supports no HTTP methods
* Case B: Route can set its supported HTTP methods
* Case C: Route can append supported HTTP methods
* Case D: Route can test if it supports an HTTP method
* Case E: Route can lazily declare supported HTTP methods with `via`
*/
public function testHttpMethods()
{
//Case A
$r = new \Slim\Route('/foo', function () {});
$this->assertEmpty($r->getHttpMethods());
//Case B
$r->setHttpMethods('GET');
$this->assertEquals(array('GET'), $r->getHttpMethods());
//Case C
$r->appendHttpMethods('POST', 'PUT');
$this->assertEquals(array('GET', 'POST', 'PUT'), $r->getHttpMethods());
//Case D
$this->assertTrue($r->supportsHttpMethod('GET'));
$this->assertFalse($r->supportsHttpMethod('DELETE'));
//Case E
$viaResult = $r->via('DELETE');
$this->assertTrue($viaResult instanceof \Slim\Route);
$this->assertTrue($r->supportsHttpMethod('DELETE'));
$this->expectOutputString('foobar');
$route = new \Slim\Route('/foo', function () { echo "bar"; });
$route->setName('foo');
$route->setMiddleware(function ($route) {
echo $route->getName();
});
$route->matches('/foo'); //<-- Extracts params from resource URI
$route->dispatch();
}
}
+160 -512
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -32,571 +32,219 @@
class RouterTest extends PHPUnit_Framework_TestCase
{
protected $env;
protected $req;
protected $res;
public function setUp()
/**
* Constructor should initialize routes as empty array
*/
public function testConstruct()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$this->env = \Slim\Environment::getInstance();
$this->req = new \Slim\Http\Request($this->env);
$this->res = new \Slim\Http\Response();
$router = new \Slim\Router();
$this->assertAttributeEquals(array(), 'routes', $router);
}
/**
* Router::urlFor should return a full route pattern
* even if no params data is provided.
* Map should set and return instance of \Slim\Route
*/
public function testUrlForNamedRouteWithoutParams()
public function testMap()
{
$router = new \Slim\Router();
$route = $router->map('/foo/bar', function () {})->via('GET');
$route = new \Slim\Route('/foo', function() {});
$router->map($route);
$this->assertAttributeContains($route, 'routes', $router);
}
/**
* Named route should be added and indexed by name
*/
public function testAddNamedRoute()
{
$router = new \Slim\Router();
$route = new \Slim\Route('/foo', function () {});
$router->addNamedRoute('foo', $route);
$this->assertEquals('/foo/bar', $router->urlFor('foo'));
$property = new \ReflectionProperty($router, 'namedRoutes');
$property->setAccessible(true);
$rV = $property->getValue($router);
$this->assertSame($route, $rV['foo']);
}
/**
* Router::urlFor should return a full route pattern if
* param data is provided.
* Named route should have unique name
*/
public function testUrlForNamedRouteWithParams()
public function testAddNamedRouteWithDuplicateKey()
{
$this->setExpectedException('RuntimeException');
$router = new \Slim\Router();
$route = $router->map('/foo/:one/and/:two', function ($one, $two) {})->via('GET');
$route = new \Slim\Route('/foo', function () {});
$router->addNamedRoute('foo', $route);
$router->addNamedRoute('foo', $route);
$this->assertEquals('/foo/Josh/and/John', $router->urlFor('foo', array('one' => 'Josh', 'two' => 'John')));
}
/**
* Router::urlFor should throw an exception if Route with name
* does not exist.
* @expectedException \RuntimeException
* Router should return named route by name, or null if not found
*/
public function testUrlForNamedRouteThatDoesNotExist()
public function testGetNamedRoute()
{
$router = new \Slim\Router();
$route = $router->map('/foo/bar', function () {})->via('GET');
$router->addNamedRoute('bar', $route);
$router->urlFor('foo');
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($router, 'namedRoutes');
$property->setAccessible(true);
$property->setValue($router, array('foo' => $route));
$this->assertSame($route, $router->getNamedRoute('foo'));
$this->assertNull($router->getNamedRoute('bar'));
}
/**
* Router::addNamedRoute should throw an exception if named Route
* with same name already exists.
* Router should determine named routes and cache results
*/
public function testNamedRouteWithExistingName()
public function testGetNamedRoutes()
{
$this->setExpectedException('\RuntimeException');
$router = new \Slim\Router();
$route1 = $router->map('/foo/bar', function () {})->via('GET');
$route2 = $router->map('/foo/bar/2', function () {})->via('GET');
$router->addNamedRoute('bar', $route1);
$router->addNamedRoute('bar', $route2);
$route1 = new \Slim\Route('/foo', function () {});
$route2 = new \Slim\Route('/bar', function () {});
// Init router routes to array
$propertyRouterRoutes = new \ReflectionProperty($router, 'routes');
$propertyRouterRoutes->setAccessible(true);
$propertyRouterRoutes->setValue($router, array($route1, $route2));
// Init router named routes to null
$propertyRouterNamedRoutes = new \ReflectionProperty($router, 'namedRoutes');
$propertyRouterNamedRoutes->setAccessible(true);
$propertyRouterNamedRoutes->setValue($router, null);
// Init route name
$propertyRouteName = new \ReflectionProperty($route2, 'name');
$propertyRouteName->setAccessible(true);
$propertyRouteName->setValue($route2, 'bar');
$namedRoutes = $router->getNamedRoutes();
$this->assertCount(1, $namedRoutes);
$this->assertSame($route2, $namedRoutes['bar']);
}
/**
* Test if named route exists
*
* Pre-conditions:
* Slim app instantiated;
* Named route created;
*
* Post-conditions:
* Named route found to exist;
* Non-existant route found not to exist;
* Router should detect presence of a named route by name
*/
public function testHasNamedRoute()
{
$router = new \Slim\Router();
$route = $router->map('/foo', function () {})->via('GET');
$router->addNamedRoute('foo', $route);
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($router, 'namedRoutes');
$property->setAccessible(true);
$property->setValue($router, array('foo' => $route));
$this->assertTrue($router->hasNamedRoute('foo'));
$this->assertFalse($router->hasNamedRoute('bar'));
}
/**
* Test Router gets named route
*
* Pre-conditions;
* Slim app instantiated;
* Named route created;
*
* Post-conditions:
* Named route fetched by named;
* NULL is returned if named route does not exist;
*/
public function testGetNamedRoute()
{
$router = new \Slim\Router();
$route1 = $router->map('/foo', function () {})->via('GET');
$router->addNamedRoute('foo', $route1);
$this->assertSame($route1, $router->getNamedRoute('foo'));
$this->assertNull($router->getNamedRoute('bar'));
}
/**
* Test external iterator for Router's named routes
*
* Pre-conditions:
* Slim app instantiated;
* Named routes created;
*
* Post-conditions:
* Array iterator returned for named routes;
*/
public function testGetNamedRoutes()
{
$router = new \Slim\Router();
$route1 = $router->map('/foo', function () {})->via('GET');
$route2 = $router->map('/bar', function () {})->via('POST');
$router->addNamedRoute('foo', $route1);
$router->addNamedRoute('bar', $route2);
$namedRoutesIterator = $router->getNamedRoutes();
$this->assertInstanceOf('ArrayIterator', $namedRoutesIterator);
$this->assertEquals(2, $namedRoutesIterator->count());
}
/**
* Router considers HEAD requests as GET requests
*/
public function testRouterConsidersHeadAsGet()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'HEAD',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$this->env = \Slim\Environment::getInstance();
$this->req = new \Slim\Http\Request($this->env);
$this->res = new \Slim\Http\Response();
$router = new \Slim\Router();
$route = $router->map('/bar', function () {})->via('GET', 'HEAD');
$numberOfMatchingRoutes = count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri()));
$this->assertEquals(1, $numberOfMatchingRoutes);
}
/**
* Router::urlFor
*/
public function testRouterUrlFor()
{
$router = new \Slim\Router();
$route1 = $router->map('/foo/bar', function () {})->via('GET');
$route2 = $router->map('/foo/:one/:two', function () {})->via('GET');
$route3 = $router->map('/foo/:one(/:two)', function () {})->via('GET');
$route4 = $router->map('/foo/:one/(:two/)', function () {})->via('GET');
$route5 = $router->map('/foo/:one/(:two/(:three/))', function () {})->via('GET');
$route6 = $router->map('/foo/:path+/bar', function (){})->via('GET');
$route7 = $router->map('/foo/:path+/:path2+/bar', function (){})->via('GET');
$route8 = $router->map('/foo/:path+', function (){})->via('GET');
$route9 = $router->map('/foo/:var/:var2', function (){})->via('GET');
$route1->setName('route1');
$route2->setName('route2');
$route3->setName('route3');
$route4->setName('route4');
$route5->setName('route5');
$route6->setName('route6');
$route7->setName('route7');
$route8->setName('route8');
$route9->setName('route9');
//Route
$this->assertEquals('/foo/bar', $router->urlFor('route1'));
//Route with params
$this->assertEquals('/foo/foo/bar', $router->urlFor('route2', array('one' => 'foo', 'two' => 'bar')));
$this->assertEquals('/foo/foo/:two', $router->urlFor('route2', array('one' => 'foo')));
$this->assertEquals('/foo/:one/bar', $router->urlFor('route2', array('two' => 'bar')));
//Route with params and optional segments
$this->assertEquals('/foo/foo/bar', $router->urlFor('route3', array('one' => 'foo', 'two' => 'bar')));
$this->assertEquals('/foo/foo', $router->urlFor('route3', array('one' => 'foo')));
$this->assertEquals('/foo/:one/bar', $router->urlFor('route3', array('two' => 'bar')));
$this->assertEquals('/foo/:one', $router->urlFor('route3'));
//Route with params and optional segments
$this->assertEquals('/foo/foo/bar/', $router->urlFor('route4', array('one' => 'foo', 'two' => 'bar')));
$this->assertEquals('/foo/foo/', $router->urlFor('route4', array('one' => 'foo')));
$this->assertEquals('/foo/:one/bar/', $router->urlFor('route4', array('two' => 'bar')));
$this->assertEquals('/foo/:one/', $router->urlFor('route4'));
//Route with params and optional segments
$this->assertEquals('/foo/foo/bar/what/', $router->urlFor('route5', array('one' => 'foo', 'two' => 'bar', 'three' => 'what')));
$this->assertEquals('/foo/foo/', $router->urlFor('route5', array('one' => 'foo')));
$this->assertEquals('/foo/:one/bar/', $router->urlFor('route5', array('two' => 'bar')));
$this->assertEquals('/foo/:one/bar/what/', $router->urlFor('route5', array('two' => 'bar', 'three' => 'what')));
$this->assertEquals('/foo/:one/', $router->urlFor('route5'));
//Route with wildcard params
$this->assertEquals('/foo/bar/bar', $router->urlFor('route6', array('path'=>'bar')));
$this->assertEquals('/foo/foo/bar/bar', $router->urlFor('route7', array('path'=>'foo', 'path2'=>'bar')));
$this->assertEquals('/foo/bar', $router->urlFor('route8', array('path'=>'bar')));
//Route with similar param names, test greedy matching
$this->assertEquals('/foo/1/2', $router->urlFor('route9', array('var2'=>'2', 'var'=>'1')));
$this->assertEquals('/foo/1/2', $router->urlFor('route9', array('var'=>'1', 'var2'=>'2')));
}
/**
* Test that router returns no matches when neither HTTP method nor URI match.
*/
public function testRouterMatchesRoutesNone()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$this->env = \Slim\Environment::getInstance();
$this->req = new \Slim\Http\Request($this->env);
$this->res = new \Slim\Http\Response();
$router = new \Slim\Router();
$router->map('/bar', function () {})->via('POST');
$router->map('/foo', function () {})->via('POST');
$router->map('/foo', function () {})->via('PUT');
$router->map('/foo/bar/xyz', function () {})->via('DELETE');
$this->assertEquals(0, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri())));
}
/**
* Test that router returns no matches when HTTP method matches but URI does not.
*/
public function testRouterMatchesRoutesNoneWhenMethodMatchesUriDoesNot()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$this->env = \Slim\Environment::getInstance();
$this->req = new \Slim\Http\Request($this->env);
$this->res = new \Slim\Http\Response();
$router = new \Slim\Router();
$router->map('/fooNOMATCH', function () {})->via('GET');
$router->map('/foo', function () {})->via('POST');
$router->map('/foo', function () {})->via('PUT');
$router->map('/foo/bar/xyz', function () {})->via('DELETE');
$this->assertEquals(0, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri())));
}
/**
* Test that router returns no matches when HTTP method does not match but URI does.
*/
public function testRouterMatchesRoutesNoneWhenMethodDoesNotMatchUriMatches()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$this->env = \Slim\Environment::getInstance();
$this->req = new \Slim\Http\Request($this->env);
$this->res = new \Slim\Http\Response();
$router = new \Slim\Router();
$router->map('/foo', function () {})->via('OPTIONS');
$router->map('/foo', function () {})->via('POST');
$router->map('/foo', function () {})->via('PUT');
$router->map('/foo/bar/xyz', function () {})->via('DELETE');
$this->assertEquals(0, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri())));
}
/**
* Test that router returns matched routes based on HTTP method and URI.
*/
public function testRouterMatchesRoutes()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$this->env = \Slim\Environment::getInstance();
$this->req = new \Slim\Http\Request($this->env);
$this->res = new \Slim\Http\Response();
$router = new \Slim\Router();
$router->map('/foo', function () {})->via('GET');
$router->map('/foo', function () {})->via('POST');
$router->map('/foo', function () {})->via('PUT');
$router->map('/foo/bar/xyz', function () {})->via('DELETE');
$this->assertEquals(1, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri())));
}
/**
* Test get current route
* Router should return current route if set during iteration
*/
public function testGetCurrentRoute()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo' //<-- Virtual
));
$app = new \Slim\Slim();
$route1 = $app->get('/bar', function () {
echo "Bar";
});
$route2 = $app->get('/foo', function () {
echo "Foo";
});
$app->call();
$this->assertSame($route2, $app->router()->getCurrentRoute());
}
/**
* Test calling get current route before routing doesn't cause errors
*/
public function testGetCurrentRouteBeforeRoutingDoesntError()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo' //<-- Virtual
));
$app = new \Slim\Slim();
$route1 = $app->get('/bar', function () {
echo "Bar";
});
$route2 = $app->get('/foo', function () {
echo "Foo";
});
$app->router()->getCurrentRoute();
$app->call();
}
/**
* Test get current route before routing returns null
*/
public function testGetCurrentRouteBeforeRoutingReturnsNull()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo' //<-- Virtual
));
$app = new \Slim\Slim();
$route1 = $app->get('/bar', function () {
echo "Bar";
});
$route2 = $app->get('/foo', function () {
echo "Foo";
});
$this->assertSame(null, $app->router()->getCurrentRoute());
}
/**
* Test get current route during slim.before.dispatch hook
*/
public function testGetCurrentRouteDuringBeforeDispatchHook()
{
$route = null;
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo' //<-- Virtual
));
$app = new \Slim\Slim();
$app->hook('slim.before.dispatch', function() use(&$route, $app) {
$route = $app->router()->getCurrentRoute();
});
$route1 = $app->get('/bar', function () {
echo "Bar";
});
$route2 = $app->get('/foo', function () {
echo "Foo";
});
$app->call();
$this->assertSame($route2, $route);
}
/**
* Test get current route during routing
*/
public function testGetCurrentRouteDuringRouting()
{
$route = null;
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo' //<-- Virtual
));
$app = new \Slim\Slim();
$route1 = $app->get('/bar', function () {
echo "Bar";
});
$route2 = $app->get('/foo', function () use (&$route, $app) {
echo "Foo";
$route = $app->router()->getCurrentRoute();
});
$app->call();
$this->assertSame($route2, $route);
}
/**
* Test get current route after routing
*/
public function testGetCurrentRouteAfterRouting()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo' //<-- Virtual
));
$app = new \Slim\Slim();
$route1 = $app->get('/bar', function () {
echo "Bar";
});
$route2 = $app->get('/foo', function () {
echo "Foo";
});
$app->call();
$this->assertSame($route2, $app->router()->getCurrentRoute());
}
public function testDispatch()
{
$this->expectOutputString('Hello josh');
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/hello/josh', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$env = \Slim\Environment::getInstance();
$req = new \Slim\Http\Request($env);
$router = new \Slim\Router();
$route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; });
$route->matches($req->getResourceUri()); //<-- Extracts params from resource URI
$router->dispatch($route);
$route = new \Slim\Route('/foo', function () {});
$property = new \ReflectionProperty($router, 'currentRoute');
$property->setAccessible(true);
$property->setValue($router, $route);
$this->assertSame($route, $router->getCurrentRoute());
}
public function testDispatchWithMiddlware()
/**
* Router should return first matching route if current route not set yet by iteration
*/
public function testGetCurrentRouteIfMatchedRoutes()
{
$this->expectOutputString('First! Second! Hello josh');
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/hello/josh', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$env = \Slim\Environment::getInstance();
$req = new \Slim\Http\Request($env);
$router = new \Slim\Router();
$route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; });
$route->setMiddleware(function () {
echo "First! ";
});
$route->setMiddleware(function () {
echo "Second! ";
});
$route->matches($req->getResourceUri()); //<-- Extracts params from resource URI
$router->dispatch($route);
$route = new \Slim\Route('/foo', function () {});
$propertyMatchedRoutes = new \ReflectionProperty($router, 'matchedRoutes');
$propertyMatchedRoutes->setAccessible(true);
$propertyMatchedRoutes->setValue($router, array($route));
$propertyCurrentRoute = new \ReflectionProperty($router, 'currentRoute');
$propertyCurrentRoute->setAccessible(true);
$propertyCurrentRoute->setValue($router, null);
$this->assertSame($route, $router->getCurrentRoute());
}
public function testRouteMiddlwareArguments()
/**
* Router should return `null` if current route not set yet and there are no matching routes
*/
public function testGetCurrentRouteIfNoMatchedRoutes()
{
$this->expectOutputString('foobar');
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/foo' //<-- Virtual
));
$env = \Slim\Environment::getInstance();
$req = new \Slim\Http\Request($env);
$router = new \Slim\Router();
$route = new \Slim\Route('/foo', function () { echo "bar"; });
$route->setName('foo');
$route->setMiddleware(function ($route) {
echo $route->getName();
});
$route->matches($req->getResourceUri()); //<-- Extracts params from resource URI
$router->dispatch($route);
$propertyMatchedRoutes = new \ReflectionProperty($router, 'matchedRoutes');
$propertyMatchedRoutes->setAccessible(true);
$propertyMatchedRoutes->setValue($router, array());
$propertyCurrentRoute = new \ReflectionProperty($router, 'currentRoute');
$propertyCurrentRoute->setAccessible(true);
$propertyCurrentRoute->setValue($router, null);
$this->assertNull($router->getCurrentRoute());
}
public function testDispatchWithoutCallable()
public function testGetMatchedRoutes()
{
$this->setExpectedException('InvalidArgumentException');
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'REMOTE_ADDR' => '127.0.0.1',
'SCRIPT_NAME' => '', //<-- Physical
'PATH_INFO' => '/hello/josh', //<-- Virtual
'QUERY_STRING' => 'one=1&two=2&three=3',
'SERVER_NAME' => 'slim',
'SERVER_PORT' => 80,
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => fopen('php://stderr', 'w'),
'HTTP_HOST' => 'slim'
));
$env = \Slim\Environment::getInstance();
$req = new \Slim\Http\Request($env);
$router = new \Slim\Router();
$route = new \Slim\Route('/hello/:name', 'foo'); // <-- Fail fast
$route1 = new \Slim\Route('/foo', function () {});
$route1 = $route1->via('GET');
$route2 = new \Slim\Route('/foo', function () {});
$route2 = $route2->via('POST');
$route3 = new \Slim\Route('/bar', function () {});
$route3 = $route3->via('PUT');
$routes = new \ReflectionProperty($router, 'routes');
$routes->setAccessible(true);
$routes->setValue($router, array($route1, $route2, $route3));
$matchedRoutes = $router->getMatchedRoutes('GET', '/foo');
$this->assertSame($route1, $matchedRoutes[0]);
}
// Test url for named route
public function testUrlFor()
{
$router = new \Slim\Router();
$route1 = new \Slim\Route('/hello/:first/:last', function () {});
$route1 = $route1->via('GET')->name('hello');
$route2 = new \Slim\Route('/path/(:foo\.:bar)', function () {});
$route2 = $route2->via('GET')->name('regexRoute');
$routes = new \ReflectionProperty($router, 'namedRoutes');
$routes->setAccessible(true);
$routes->setValue($router, array(
'hello' => $route1,
'regexRoute' => $route2
));
$this->assertEquals('/hello/Josh/Lockhart', $router->urlFor('hello', array('first' => 'Josh', 'last' => 'Lockhart')));
$this->assertEquals('/path/Hello.Josh', $router->urlFor('regexRoute', array('foo' => 'Hello', 'bar' => 'Josh')));
}
public function testUrlForIfNoSuchRoute()
{
$this->setExpectedException('RuntimeException');
$router = new \Slim\Router();
$router->urlFor('foo', array('abc' => '123'));
}
}
+131 -85
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -33,7 +33,7 @@
//Mock custom view
class CustomView extends \Slim\View
{
public function render($template) { echo "Custom view"; }
public function render($template, $data = null) { echo "Custom view"; }
}
//Echo Logger
@@ -371,6 +371,27 @@ class SlimTest extends PHPUnit_Framework_TestCase
$this->assertSame($callable, $route->getCallable());
}
/**
* Test PATCH route
*/
public function testPatchRoute()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'PATCH',
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
));
$s = new \Slim\Slim();
$mw1 = function () { echo "foo"; };
$mw2 = function () { echo "bar"; };
$callable = function () { echo "xyz"; };
$route = $s->patch('/bar', $mw1, $mw2, $callable);
$s->call();
$this->assertEquals('foobarxyz', $s->response()->body());
$this->assertEquals('/bar', $route->getPattern());
$this->assertSame($callable, $route->getCallable());
}
/**
* Test DELETE route
*/
@@ -413,6 +434,51 @@ class SlimTest extends PHPUnit_Framework_TestCase
$this->assertSame($callable, $route->getCallable());
}
/**
* Test route groups
*/
public function testRouteGroups()
{
\Slim\Environment::mock(array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar/baz', //<-- Virtual'
));
$s = new \Slim\Slim();
$mw1 = function () { echo "foo"; };
$mw2 = function () { echo "bar"; };
$callable = function () { echo "xyz"; };
$s->group('/bar', $mw1, function () use ($s, $mw2, $callable) {
$s->get('/baz', $mw2, $callable);
});
$s->call();
$this->assertEquals('foobarxyz', $s->response()->body());
}
/*
* Test ANY route
*/
public function testAnyRoute()
{
$mw1 = function () { echo "foo"; };
$mw2 = function () { echo "bar"; };
$callable = function () { echo "xyz"; };
$methods = array('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS');
foreach ($methods as $i => $method) {
\Slim\Environment::mock(array(
'REQUEST_METHOD' => $method,
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
));
$s = new \Slim\Slim();
$route = $s->any('/bar', $mw1, $mw2, $callable);
$s->call();
$this->assertEquals('foobarxyz', $s->response()->body());
$this->assertEquals('/bar', $route->getPattern());
$this->assertSame($callable, $route->getCallable());
}
}
/**
* Test if route does NOT expect trailing slash and URL has one
*/
@@ -485,6 +551,16 @@ class SlimTest extends PHPUnit_Framework_TestCase
* RENDERING
************************************************/
/**
* Test template path is passed to view
*/
public function testViewGetsTemplatesPath()
{
$path = dirname(__FILE__) . '/templates';
$s = new \Slim\Slim(array('templates.path' => $path));
$this->assertEquals($s->view->getTemplatesDirectory(), $path);
}
/**
* Test render with template and data
*/
@@ -545,7 +621,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
'IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 17:00:52 -0400',
'HTTP_IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 21:00:52 GMT',
));
$s = new \Slim\Slim();
$s->get('/bar', function () use ($s) {
@@ -563,7 +639,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
'IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 17:00:52 -0400',
'IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 21:00:52 GMT',
));
$s = new \Slim\Slim();
$s->get('/bar', function () use ($s) {
@@ -587,6 +663,25 @@ class SlimTest extends PHPUnit_Framework_TestCase
$s->call();
}
/**
* Test Last Modified header format
*/
public function testLastModifiedHeaderFormat()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
));
$s = new \Slim\Slim();
$s->get('/bar', function () use ($s) {
$s->lastModified(1286139652);
});
$s->call();
list($status, $header, $body) = $s->response()->finalize();
$this->assertTrue(isset($header['Last-Modified']));
$this->assertEquals('Sun, 03 Oct 2010 21:00:52 GMT', $header['Last-Modified']);
}
/**
* Test ETag matches
*/
@@ -595,7 +690,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
'IF_NONE_MATCH' => '"abc123"',
'HTTP_IF_NONE_MATCH' => '"abc123"',
));
$s = new \Slim\Slim();
$s->get('/bar', function () use ($s) {
@@ -650,7 +745,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
));
$expectedDate = gmdate('D, d M Y', strtotime('5 days')); //Just the day, month, and year
$expectedDate = gmdate('D, d M Y H:i:s T', strtotime('5 days'));
$s = new \Slim\Slim();
$s->get('/bar', function () use ($s) {
$s->expires('5 days');
@@ -658,7 +753,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
$s->call();
list($status, $header, $body) = $s->response()->finalize();
$this->assertTrue(isset($header['Expires']));
$this->assertEquals(0, strpos($header['Expires'], $expectedDate));
$this->assertEquals($header['Expires'], $expectedDate);
}
/**
@@ -671,7 +766,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
'PATH_INFO' => '/bar', //<-- Virtual
));
$fiveDaysFromNow = time() + (60 * 60 * 24 * 5);
$expectedDate = gmdate('D, d M Y', $fiveDaysFromNow); //Just the day, month, and year
$expectedDate = gmdate('D, d M Y H:i:s T', $fiveDaysFromNow);
$s = new \Slim\Slim();
$s->get('/bar', function () use ($s, $fiveDaysFromNow) {
$s->expires($fiveDaysFromNow);
@@ -679,7 +774,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
$s->call();
list($status, $header, $body) = $s->response()->finalize();
$this->assertTrue(isset($header['Expires']));
$this->assertEquals(0, strpos($header['Expires'], $expectedDate));
$this->assertEquals($header['Expires'], $expectedDate);
}
/************************************************
@@ -706,11 +801,11 @@ class SlimTest extends PHPUnit_Framework_TestCase
$s->setCookie('foo1', 'bar1', '2 days');
});
$s->call();
list($status, $header, $body) = $s->response()->finalize();
$cookies = explode("\n", $header['Set-Cookie']);
$this->assertEquals(2, count($cookies));
$this->assertEquals(1, preg_match('@foo=bar@', $cookies[0]));
$this->assertEquals(1, preg_match('@foo1=bar1@', $cookies[1]));
$cookie1 = $s->response->cookies->get('foo');
$cookie2 = $s->response->cookies->get('foo1');
$this->assertEquals(2, count($s->response->cookies));
$this->assertEquals('bar', $cookie1['value']);
$this->assertEquals('bar1', $cookie2['value']);
}
/**
@@ -730,7 +825,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
'QUERY_STRING' => 'one=foo&two=bar',
'SERVER_NAME' => 'slimframework.com',
'SERVER_PORT' => 80,
'COOKIE' => 'foo=bar; foo2=bar2',
'HTTP_COOKIE' => 'foo=bar; foo2=bar2',
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => @fopen('php://stderr', 'w')
@@ -773,60 +868,10 @@ class SlimTest extends PHPUnit_Framework_TestCase
$s->deleteCookie('foo');
});
$s->call();
list($status, $header, $body) = $s->response()->finalize();
$cookies = explode("\n", $header['Set-Cookie']);
$this->assertEquals(1, count($cookies));
$this->assertEquals(1, preg_match('@^foo=;@', $cookies[0]));
}
/**
* Test set encrypted cookie
*
* This method ensures that the `Set-Cookie:` HTTP request
* header is set. The implementation is tested in a separate file.
*/
public function testSetEncryptedCookie()
{
$s = new \Slim\Slim();
$s->setEncryptedCookie('foo', 'bar');
$r = $s->response();
$this->assertEquals(1, preg_match("@^foo=.+%7C.+%7C.+@", $r['Set-Cookie'])); //<-- %7C is a url-encoded pipe
}
/**
* Test get encrypted cookie
*
* This only tests that this method runs without error. The implementation of
* fetching the encrypted cookie is tested separately.
*/
public function testGetEncryptedCookieAndDeletingIt()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
));
$s = new \Slim\Slim();
$r = $s->response();
$this->assertFalse($s->getEncryptedCookie('foo'));
$this->assertEquals(1, preg_match("@foo=;.*@", $r['Set-Cookie']));
}
/**
* Test get encrypted cookie WITHOUT deleting it
*
* This only tests that this method runs without error. The implementation of
* fetching the encrypted cookie is tested separately.
*/
public function testGetEncryptedCookieWithoutDeletingIt()
{
\Slim\Environment::mock(array(
'SCRIPT_NAME' => '/foo', //<-- Physical
'PATH_INFO' => '/bar', //<-- Virtual
));
$s = new \Slim\Slim();
$r = $s->response();
$this->assertFalse($s->getEncryptedCookie('foo', false));
$this->assertEquals(0, preg_match("@foo=;.*@", $r['Set-Cookie']));
$cookie = $s->response->cookies->get('foo');
$this->assertEquals(1, count($s->response->cookies));
$this->assertEquals('', $cookie['value']);
$this->assertLessThan(time(), $cookie['expires']);
}
/************************************************
@@ -1195,7 +1240,9 @@ class SlimTest extends PHPUnit_Framework_TestCase
*/
public function testSlimError()
{
$s = new \Slim\Slim();
$s = new \Slim\Slim(array(
"log.enabled" => false
));
$s->get('/bar', function () use ($s) {
$s->error();
});
@@ -1215,13 +1262,13 @@ class SlimTest extends PHPUnit_Framework_TestCase
public function testDefaultHandlerLogsTheErrorWhenDebugIsFalse()
{
$s = new \Slim\Slim(array('debug' => false));
$s->container->singleton('log', function ($c) {
return new EchoErrorLogger();
});
$s->get('/bar', function () use ($s) {
throw new \InvalidArgumentException('my specific error message');
});
$env = $s->environment();
$env['slim.log'] = new EchoErrorLogger(); // <-- inject the fake logger
ob_start();
$s->run();
$output = ob_get_clean();
@@ -1276,7 +1323,8 @@ class SlimTest extends PHPUnit_Framework_TestCase
public function testErrorWithMultipleApps()
{
$s1 = new \Slim\Slim(array(
'debug' => false
'debug' => false,
'log.enabled' => false
));
$s2 = new \Slim\Slim();
$s1->get('/bar', function () use ($s1) {
@@ -1323,22 +1371,20 @@ class SlimTest extends PHPUnit_Framework_TestCase
{
$defaultErrorReporting = error_reporting();
// Assert Slim ignores E_NOTICE errors
// Test 1
error_reporting(E_ALL ^ E_NOTICE); // <-- Report all errors EXCEPT notices
try {
$this->assertTrue(\Slim\Slim::handleErrors(E_NOTICE, 'test error', 'Slim.php', 119));
\Slim\Slim::handleErrors(E_NOTICE, 'test error', 'Slim.php', 119);
} catch (\ErrorException $e) {
$this->fail('Slim::handleErrors reported a disabled error level.');
}
// Assert Slim reports E_STRICT errors
// Test 2
error_reporting(E_ALL | E_STRICT); // <-- Report all errors, including E_STRICT
try {
\Slim\Slim::handleErrors(E_STRICT, 'test error', 'Slim.php', 119);
$this->fail('Slim::handleErrors didn\'t report a enabled error level');
} catch (\ErrorException $e) {
$this->assertEquals('test error', $e->getMessage());
}
} catch (\ErrorException $e) {}
error_reporting($defaultErrorReporting);
}
@@ -1357,8 +1403,8 @@ class SlimTest extends PHPUnit_Framework_TestCase
* Slim should throw a Slim_Exception_Stop if error callback is not callable
*/
public function testErrorHandlerIfNotCallable() {
$this->setExpectedException('\Slim\Exception\Stop');
$s = new \Slim\Slim();
$this->setExpectedException('\Slim\Exception\Stop');
$s = new \Slim\Slim(array("log.enabled" => false));
$errCallback = 'foo';
$s->error($errCallback);
}
@@ -1377,7 +1423,7 @@ class SlimTest extends PHPUnit_Framework_TestCase
* Slim should throw a Slim_Exception_Stop if NotFound callback is not callable
*/
public function testNotFoundHandlerIfNotCallable() {
$this->setExpectedException('\Slim\Exception\Stop');
$this->setExpectedException('\Slim\Exception\Stop');
$s = new \Slim\Slim();
$notFoundCallback = 'foo';
$s->notFound($notFoundCallback);
@@ -1473,13 +1519,13 @@ class SlimTest extends PHPUnit_Framework_TestCase
$this->assertEquals(array(array()), $app->getHooks('test.hook.one'));
}
/**
/**
* Test late static binding
*
* Pre-conditions:
* Slim app is extended by Derived class and instantiated;
* Derived class overrides the 'getDefaultSettings' function and adds an extra default config value
* Test that the new config value exists
* Test that the new config value exists
*
* Post-conditions:
* Config value exists and is equal to expected value
+132 -129
View File
@@ -6,7 +6,7 @@
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.2.0
* @version 2.4.0
*
* MIT LICENSE
*
@@ -32,164 +32,167 @@
class ViewTest extends PHPUnit_Framework_TestCase
{
public function setUp()
public function testGetDataAll()
{
$this->view = new \Slim\View();
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
$prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar')));
$this->assertSame(array('foo' => 'bar'), $view->getData());
}
public function generateTestData()
public function testGetDataKeyExists()
{
return array('a' => 1, 'b' => 2, 'c' => 3);
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
$prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar')));
$this->assertEquals('bar', $view->getData('foo'));
}
/**
* Test initial View data is an empty array
*
* Pre-conditions:
* None
*
* Post-conditions:
* The View object's data attribute is an empty array
*/
public function testViewIsConstructedWithDataArray()
public function testGetDataKeyNotExists()
{
$this->assertEquals(array(), $this->view->getData());
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
$prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar')));
$this->assertNull($view->getData('abc'));
}
/**
* Test View sets and gets data
*
* Pre-conditions:
* Case A: Set view data key/value
* Case B: Set view data as array
* Case C: Set view data with one argument that is not an array
*
* Post-conditions:
* Case A: Data key/value are set
* Case B: Data is set to array
* Case C: An InvalidArgumentException is thrown
*/
public function testViewSetAndGetData()
public function testSetDataKeyValue()
{
//Case A
$this->view->setData('one', 1);
$this->assertEquals(1, $this->view->getData('one'));
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
$view->setData('foo', 'bar');
//Case B
$data = array('foo' => 'bar', 'a' => 'A');
$this->view->setData($data);
$this->assertSame($data, $this->view->getData());
//Case C
try {
$this->view->setData('foo');
$this->fail('Setting View data with non-array single argument did not throw exception');
} catch ( \InvalidArgumentException $e ) {}
$this->assertEquals(array('foo' => 'bar'), $prop->getValue($view)->all());
}
/**
* Test View appends data
*
* Pre-conditions:
* Case A: Append data to View several times
* Case B: Append view data which is not an array
*
* Post-conditions:
* Case A: The View data contains all appended data
* Case B: An InvalidArgumentException is thrown
*/
public function testViewAppendsData()
public function testSetDataKeyValueAsClosure()
{
//Case A
$this->view->appendData(array('a' => 'A'));
$this->view->appendData(array('b' => 'B'));
$this->assertEquals(array('a' => 'A', 'b' => 'B'), $this->view->getData());
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
//Case B
try {
$this->view->appendData('not an array');
$this->fail('Appending View data with non-array argument did not throw exception');
} catch ( \InvalidArgumentException $e ) {}
$view->setData('fooClosure', function () {
return 'foo';
});
$value = $prop->getValue($view)->get('fooClosure');
$this->assertInstanceOf('Closure', $value);
$this->assertEquals('foo', $value());
}
/**
* Test View templates directory
*
* Pre-conditions:
* View templates directory is set to an existing directory
*
* Post-conditions:
* The templates directory is set correctly.
*/
public function testSetsTemplatesDirectory()
public function testSetDataArray()
{
$templatesDirectory = dirname(__FILE__) . '/templates';
$this->view->setTemplatesDirectory($templatesDirectory);
$this->assertEquals($templatesDirectory, $this->view->getTemplatesDirectory());
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
$view->setData(array('foo' => 'bar'));
$this->assertEquals(array('foo' => 'bar'), $prop->getValue($view)->all());
}
/**
* Test View templates directory may have a trailing slash when set
*
* Pre-conditions:
* View templates directory is set to an existing directory with a trailing slash
*
* Post-conditions:
* The View templates directory is set correctly without a trailing slash
*/
public function testTemplatesDirectoryWithTrailingSlash()
public function testSetDataInvalidArgument()
{
$this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates/');
$this->assertEquals(dirname(__FILE__) . '/templates', $this->view->getTemplatesDirectory());
$this->setExpectedException('InvalidArgumentException');
$view = new \Slim\View();
$view->setData('foo');
}
/**
* Test View renders template
*
* Pre-conditions:
* View templates directory is set to an existing directory.
* View data is set without errors
* Case A: View renders an existing template
* Case B: View renders a non-existing template
*
* Post-conditions:
* Case A: The rendered template is returned as a string
* Case B: A RuntimeException is thrown
*/
public function testRendersTemplateWithData()
public function testAppendData()
{
$this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates');
$this->view->setData(array('foo' => 'bar'));
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
$view->appendData(array('foo' => 'bar'));
//Case A
$output = $this->view->render('test.php');
$this->assertEquals('test output bar', $output);
//Case B
try {
$output = $this->view->render('foo.php');
$this->fail('Rendering non-existent template did not throw exception');
} catch ( \RuntimeException $e ) {}
$this->assertEquals(array('foo' => 'bar'), $prop->getValue($view)->all());
}
/**
* Test View displays template
*
* Pre-conditions:
* View templates directory is set to an existing directory.
* View data is set without errors
* View is displayed
*
* Post-conditions:
* The output buffer contains the rendered template
*/
public function testDisplaysTemplateWithData()
public function testLocalData()
{
$view = new \Slim\View();
$prop1 = new \ReflectionProperty($view, 'data');
$prop1->setAccessible(true);
$prop1->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar')));
$prop2 = new \ReflectionProperty($view, 'templatesDirectory');
$prop2->setAccessible(true);
$prop2->setValue($view, dirname(__FILE__) . '/templates');
$output = $view->fetch('test.php', array('foo' => 'baz'));
$this->assertEquals('test output baz', $output);
}
public function testAppendDataOverwrite()
{
$view = new \Slim\View();
$prop = new \ReflectionProperty($view, 'data');
$prop->setAccessible(true);
$prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar')));
$view->appendData(array('foo' => '123'));
$this->assertEquals(array('foo' => '123'), $prop->getValue($view)->all());
}
public function testAppendDataInvalidArgument()
{
$this->setExpectedException('InvalidArgumentException');
$view = new \Slim\View();
$view->appendData('foo');
}
public function testGetTemplatesDirectory()
{
$view = new \Slim\View();
$property = new \ReflectionProperty($view, 'templatesDirectory');
$property->setAccessible(true);
$property->setValue($view, 'templates');
$this->assertEquals('templates', $view->getTemplatesDirectory());
}
public function testSetTemplatesDirectory()
{
$view = new \Slim\View();
$view->setTemplatesDirectory('templates/'); // <-- Should strip trailing slash
$this->assertAttributeEquals('templates', 'templatesDirectory', $view);
}
public function testDisplay()
{
$this->expectOutputString('test output bar');
$this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates');
$this->view->setData(array('foo' => 'bar'));
$this->view->display('test.php');
$view = new \Slim\View();
$prop1 = new \ReflectionProperty($view, 'data');
$prop1->setAccessible(true);
$prop1->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar')));
$prop2 = new \ReflectionProperty($view, 'templatesDirectory');
$prop2->setAccessible(true);
$prop2->setValue($view, dirname(__FILE__) . '/templates');
$view->display('test.php');
}
public function testDisplayTemplateThatDoesNotExist()
{
$this->setExpectedException('\RuntimeException');
$view = new \Slim\View();
$prop2 = new \ReflectionProperty($view, 'templatesDirectory');
$prop2->setAccessible(true);
$prop2->setValue($view, dirname(__FILE__) . '/templates');
$view->display('foo.php');
}
}
+107 -145
View File
@@ -19,6 +19,8 @@ use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
@@ -54,16 +56,17 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*/
class Application
{
private $commands;
private $commands = array();
private $wantHelps = false;
private $runningCommand;
private $name;
private $version;
private $catchExceptions;
private $autoExit;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
private $helperSet;
private $dispatcher;
private $terminalDimensions;
/**
* Constructor.
@@ -77,9 +80,6 @@ class Application
{
$this->name = $name;
$this->version = $version;
$this->catchExceptions = true;
$this->autoExit = true;
$this->commands = array();
$this->helperSet = $this->getDefaultHelperSet();
$this->definition = $this->getDefaultInputDefinition();
@@ -404,6 +404,10 @@ class Application
return;
}
if (null === $command->getDefinition()) {
throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
@@ -491,51 +495,31 @@ class Application
public function findNamespace($namespace)
{
$allNamespaces = $this->getNamespaces();
$found = '';
foreach (explode(':', $namespace) as $i => $part) {
// select sub-namespaces matching the current namespace we found
$namespaces = array();
foreach ($allNamespaces as $n) {
if ('' === $found || 0 === strpos($n, $found)) {
$namespaces[$n] = explode(':', $n);
}
}
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
$namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
$abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $namespaces)))));
if (empty($namespaces)) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
if (!isset($abbrevs[$part])) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
if (1 <= $i) {
$part = $found.':'.$part;
if ($alternatives = $this->findAlternatives($namespace, $allNamespaces, array())) {
if (1 == count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
if ($alternatives = $this->findAlternativeNamespace($part, $abbrevs)) {
if (1 == count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new \InvalidArgumentException($message);
$message .= implode("\n ", $alternatives);
}
// there are multiple matches, but $part is an exact match of one of them so we select it
if (in_array($part, $abbrevs[$part])) {
$abbrevs[$part] = array($part);
}
if (count($abbrevs[$part]) > 1) {
throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$part])));
}
$found .= $found ? ':' . $abbrevs[$part][0] : $abbrevs[$part][0];
throw new \InvalidArgumentException($message);
}
return $found;
$exact = in_array($namespace, $namespaces, true);
if (count($namespaces) > 1 && !$exact) {
throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
}
return $exact ? $namespace : reset($namespaces);
}
/**
@@ -554,58 +538,19 @@ class Application
*/
public function find($name)
{
// namespace
$namespace = '';
$searchName = $name;
if (false !== $pos = strrpos($name, ':')) {
$namespace = $this->findNamespace(substr($name, 0, $pos));
$searchName = $namespace.substr($name, $pos);
}
$allCommands = array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
// name
$commands = array();
foreach ($this->commands as $command) {
$extractedNamespace = $this->extractNamespace($command->getName());
if ($extractedNamespace === $namespace
|| !empty($namespace) && 0 === strpos($extractedNamespace, $namespace)
) {
$commands[] = $command->getName();
if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) {
if (false !== $pos = strrpos($name, ':')) {
// check if a namespace exists and contains commands
$this->findNamespace(substr($name, 0, $pos));
}
}
$abbrevs = static::getAbbreviations(array_unique($commands));
if (isset($abbrevs[$searchName]) && 1 == count($abbrevs[$searchName])) {
return $this->get($abbrevs[$searchName][0]);
}
if (isset($abbrevs[$searchName]) && in_array($searchName, $abbrevs[$searchName])) {
return $this->get($searchName);
}
if (isset($abbrevs[$searchName]) && count($abbrevs[$searchName]) > 1) {
$suggestions = $this->getAbbreviationSuggestions($abbrevs[$searchName]);
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
}
// aliases
$aliases = array();
foreach ($this->commands as $command) {
foreach ($command->getAliases() as $alias) {
$extractedNamespace = $this->extractNamespace($alias);
if ($extractedNamespace === $namespace
|| !empty($namespace) && 0 === strpos($extractedNamespace, $namespace)
) {
$aliases[] = $alias;
}
}
}
$aliases = static::getAbbreviations(array_unique($aliases));
if (!isset($aliases[$searchName])) {
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternativeCommands($searchName, $abbrevs)) {
if ($alternatives = $this->findAlternatives($name, $allCommands, array())) {
if (1 == count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
@@ -617,11 +562,14 @@ class Application
throw new \InvalidArgumentException($message);
}
if (count($aliases[$searchName]) > 1) {
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($aliases[$searchName])));
$exact = in_array($name, $commands, true);
if (count($commands) > 1 && !$exact) {
$suggestions = $this->getAbbreviationSuggestions(array_values($commands));
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
}
return $this->get($aliases[$searchName][0]);
return $this->get($exact ? $name : reset($commands));
}
/**
@@ -684,8 +632,10 @@ class Application
public function asText($namespace = null, $raw = false)
{
$descriptor = new TextDescriptor();
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw);
$descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true));
return $descriptor->describe($this, array('namespace' => $namespace, 'raw_text' => $raw));
return $output->fetch();
}
/**
@@ -702,7 +652,14 @@ class Application
{
$descriptor = new XmlDescriptor();
return $descriptor->describe($this, array('namespace' => $namespace, 'as_dom' => $asDom));
if ($asDom) {
return $descriptor->getApplicationDocument($this, $namespace);
}
$output = new BufferedOutput();
$descriptor->describe($output, $this, array('namespace' => $namespace));
return $output->fetch();
}
/**
@@ -818,6 +775,10 @@ class Application
*/
public function getTerminalDimensions()
{
if ($this->terminalDimensions) {
return $this->terminalDimensions;
}
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
// extract [w, H] from "wxh (WxH)"
if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
@@ -843,6 +804,23 @@ class Application
return array(null, null);
}
/**
* Sets terminal dimensions.
*
* Can be useful to force terminal dimensions for functional tests.
*
* @param integer $width The width
* @param integer $height The height
*
* @return Application The current application
*/
public function setTerminalDimensions($width, $height)
{
$this->terminalDimensions = array($width, $height);
return $this;
}
/**
* Configures the input and output instances based on the user arguments and options.
*
@@ -893,6 +871,12 @@ class Application
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
foreach ($command->getHelperSet() as $helper) {
if ($helper instanceof InputAwareInterface) {
$helper->setInput($input);
}
}
if (null === $this->dispatcher) {
return $command->run($input, $output);
}
@@ -1054,72 +1038,50 @@ class Application
}
/**
* Finds alternative commands of $name
*
* @param string $name The full name of the command
* @param array $abbrevs The abbreviations
*
* @return array A sorted array of similar commands
*/
private function findAlternativeCommands($name, $abbrevs)
{
$callback = function($item) {
return $item->getName();
};
return $this->findAlternatives($name, $this->commands, $abbrevs, $callback);
}
/**
* Finds alternative namespace of $name
*
* @param string $name The full name of the namespace
* @param array $abbrevs The abbreviations
*
* @return array A sorted array of similar namespace
*/
private function findAlternativeNamespace($name, $abbrevs)
{
return $this->findAlternatives($name, $this->getNamespaces(), $abbrevs);
}
/**
* Finds alternative of $name among $collection,
* if nothing is found in $collection, try in $abbrevs
* Finds alternative of $name among $collection
*
* @param string $name The string
* @param array|Traversable $collection The collection
* @param array $abbrevs The abbreviations
* @param Closure|string|array $callback The callable to transform collection item before comparison
*
* @return array A sorted array of similar string
*/
private function findAlternatives($name, $collection, $abbrevs, $callback = null)
private function findAlternatives($name, $collection)
{
$threshold = 1e3;
$alternatives = array();
$collectionParts = array();
foreach ($collection as $item) {
if (null !== $callback) {
$item = call_user_func($callback, $item);
}
$lev = levenshtein($name, $item);
if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = $lev;
}
$collectionParts[$item] = explode(':', $item);
}
if (!$alternatives) {
foreach ($abbrevs as $key => $values) {
$lev = levenshtein($name, $key);
if ($lev <= strlen($name) / 3 || false !== strpos($key, $name)) {
foreach ($values as $value) {
$alternatives[$value] = $lev;
}
foreach (explode(':', $name) as $i => $subname) {
foreach ($collectionParts as $collectionName => $parts) {
$exists = isset($alternatives[$collectionName]);
if (!isset($parts[$i]) && $exists) {
$alternatives[$collectionName] += $threshold;
continue;
} elseif (!isset($parts[$i])) {
continue;
}
$lev = levenshtein($subname, $parts[$i]);
if ($lev <= strlen($subname) / 3 || false !== strpos($parts[$i], $subname)) {
$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
} elseif ($exists) {
$alternatives[$collectionName] += $threshold;
}
}
}
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2*$threshold; });
asort($alternatives);
return array_keys($alternatives);
@@ -1,6 +1,13 @@
CHANGELOG
=========
2.4.0
-----
* added a way to force terminal dimensions
* added a convenient method to detect verbosity level
* [BC BREAK] made descriptors use output instead of returning a string
2.3.0
-----
@@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
@@ -32,13 +33,13 @@ class Command
{
private $application;
private $name;
private $aliases;
private $aliases = array();
private $definition;
private $help;
private $description;
private $ignoreValidationErrors;
private $applicationDefinitionMerged;
private $applicationDefinitionMergedWithArgs;
private $ignoreValidationErrors = false;
private $applicationDefinitionMerged = false;
private $applicationDefinitionMergedWithArgs = false;
private $code;
private $synopsis;
private $helperSet;
@@ -46,7 +47,7 @@ class Command
/**
* Constructor.
*
* @param string $name The name of the command
* @param string|null $name The name of the command; passing null means it must be set in configure()
*
* @throws \LogicException When the command name is empty
*
@@ -55,10 +56,6 @@ class Command
public function __construct($name = null)
{
$this->definition = new InputDefinition();
$this->ignoreValidationErrors = false;
$this->applicationDefinitionMerged = false;
$this->applicationDefinitionMergedWithArgs = false;
$this->aliases = array();
if (null !== $name) {
$this->setName($name);
@@ -401,7 +398,7 @@ class Command
*
* @return Command The current instance
*
* @throws \InvalidArgumentException When command name given is empty
* @throws \InvalidArgumentException When the name is invalid
*
* @api
*/
@@ -511,6 +508,8 @@ class Command
*
* @return Command The current instance
*
* @throws \InvalidArgumentException When an alias is invalid
*
* @api
*/
public function setAliases($aliases)
@@ -576,8 +575,10 @@ class Command
public function asText()
{
$descriptor = new TextDescriptor();
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
$descriptor->describe($output, $this, array('raw_output' => true));
return $descriptor->describe($this);
return $output->fetch();
}
/**
@@ -593,12 +594,28 @@ class Command
{
$descriptor = new XmlDescriptor();
return $descriptor->describe($this, array('as_dom' => $asDom));
if ($asDom) {
return $descriptor->getCommandDocument($this);
}
$output = new BufferedOutput();
$descriptor->describe($output, $this);
return $output->fetch();
}
/**
* Validates a command name.
*
* It must be non-empty and parts can optionally be separated by ":".
*
* @param string $name
*
* @throws \InvalidArgumentException When the name is invalid
*/
private function validateName($name)
{
if (!preg_match('/^[^\:]+(\:[^\:]+)*$/', $name)) {
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
@@ -38,7 +38,7 @@ class HelpCommand extends Command
->setDefinition(array(
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output help in other formats'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output help in other formats', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
))
->setDescription('Displays help for a command')
@@ -81,7 +81,11 @@ EOF
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, $input->getOption('format'), $input->getOption('raw'));
$helper->describe($output, $this->command, array(
'format' => $input->getOption('format'),
'raw' => $input->getOption('raw'),
));
$this->command = null;
}
}
@@ -73,7 +73,11 @@ EOF
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), $input->getOption('format'), $input->getOption('raw'), $input->getArgument('namespace'));
$helper->describe($output, $this->getApplication(), array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
));
}
/**
@@ -85,7 +89,7 @@ EOF
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output list in other formats'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output list in other formats', 'txt'),
));
}
}
@@ -16,28 +16,55 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
abstract class Descriptor implements DescriptorInterface
{
public function describe($object, array $options = array())
/**
* @var OutputInterface
*/
private $output;
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = array())
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
return $this->describeInputArgument($object, $options);
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
return $this->describeInputOption($object, $options);
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
return $this->describeInputDefinition($object, $options);
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
return $this->describeCommand($object, $options);
$this->describeCommand($object, $options);
break;
case $object instanceof Application:
return $this->describeApplication($object, $options);
$this->describeApplication($object, $options);
break;
default:
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
}
}
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
/**
* Writes content to output.
*
* @param string $content
* @param boolean $decorated
*/
protected function write($content, $decorated = false)
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
/**
@@ -11,6 +11,8 @@
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Descriptor interface.
*
@@ -21,10 +23,9 @@ interface DescriptorInterface
/**
* Describes an InputArgument instance.
*
* @param object $object
* @param array $options
*
* @return string|mixed
* @param OutputInterface $output
* @param object $object
* @param array $options
*/
public function describe($object, array $options = array());
public function describe(OutputInterface $output, $object, array $options = array());
}
@@ -29,13 +29,7 @@ class JsonDescriptor extends Descriptor
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
{
return $this->output(array(
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => $argument->getDescription(),
'default' => $argument->getDefault(),
), $options);
$this->writeData($this->getInputArgumentData($argument), $options);
}
/**
@@ -43,15 +37,7 @@ class JsonDescriptor extends Descriptor
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
return $this->output(array(
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => $option->getDescription(),
'default' => $option->getDefault(),
), $options);
$this->writeData($this->getInputOptionData($option), $options);
}
/**
@@ -59,17 +45,7 @@ class JsonDescriptor extends Descriptor
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$inputArguments = array();
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->describeInputArgument($argument, array('as_array' => true));
}
$inputOptions = array();
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->describeInputOption($option, array('as_array' => true));
}
return $this->output(array('arguments' => $inputArguments, 'options' => $inputOptions), $options);
$this->writeData($this->getInputDefinitionData($definition), $options);
}
/**
@@ -77,17 +53,7 @@ class JsonDescriptor extends Descriptor
*/
protected function describeCommand(Command $command, array $options = array())
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
return $this->output(array(
'name' => $command->getName(),
'usage' => $command->getSynopsis(),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'aliases' => $command->getAliases(),
'definition' => $this->describeInputDefinition($command->getNativeDefinition(), array('as_array' => true)),
), $options);
$this->writeData($this->getCommandData($command), $options);
}
/**
@@ -100,30 +66,100 @@ class JsonDescriptor extends Descriptor
$commands = array();
foreach ($description->getCommands() as $command) {
$commands[] = $this->describeCommand($command, array('as_array' => true));
$commands[] = $this->getCommandData($command);
}
$data = $describedNamespace
? array('commands' => $commands, 'namespace' => $describedNamespace)
: array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces()));
return $this->output($data, $options);
$this->writeData($data, $options);
}
/**
* Outputs data as array or string according to options.
* Writes data as json.
*
* @param array $data
* @param array $options
*
* @return array|string
*/
private function output(array $data, array $options)
private function writeData(array $data, array $options)
{
if (isset($options['as_array']) && $options['as_array']) {
return $data;
$this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
}
/**
* @param InputArgument $argument
*
* @return array
*/
private function getInputArgumentData(InputArgument $argument)
{
return array(
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => $argument->getDescription(),
'default' => $argument->getDefault(),
);
}
/**
* @param InputOption $option
*
* @return array
*/
private function getInputOptionData(InputOption $option)
{
return array(
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => $option->getDescription(),
'default' => $option->getDefault(),
);
}
/**
* @param InputDefinition $definition
*
* @return array
*/
private function getInputDefinitionData(InputDefinition $definition)
{
$inputArguments = array();
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
return json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0);
$inputOptions = array();
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
}
return array('arguments' => $inputArguments, 'options' => $inputOptions);
}
/**
* @param Command $command
*
* @return array
*/
private function getCommandData(Command $command)
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
return array(
'name' => $command->getName(),
'usage' => $command->getSynopsis(),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'aliases' => $command->getAliases(),
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
);
}
}
@@ -29,12 +29,14 @@ class MarkdownDescriptor extends Descriptor
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
{
return '**'.$argument->getName().':**'."\n\n"
$this->write(
'**'.$argument->getName().':**'."\n\n"
.'* Name: '.($argument->getName() ?: '<none>')."\n"
.'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'* Description: '.($argument->getDescription() ?: '<none>')."\n"
.'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`';
.'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
);
}
/**
@@ -42,14 +44,16 @@ class MarkdownDescriptor extends Descriptor
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
return '**'.$option->getName().':**'."\n\n"
$this->write(
'**'.$option->getName().':**'."\n\n"
.'* Name: `--'.$option->getName().'`'."\n"
.'* Shortcut: '.($option->getShortcut() ? '`-'.implode('|-', explode('|', $option->getShortcut())).'`' : '<none>')."\n"
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
.'* Description: '.($option->getDescription() ?: '<none>')."\n"
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`';
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
);
}
/**
@@ -57,23 +61,25 @@ class MarkdownDescriptor extends Descriptor
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$blocks = array();
if (count($definition->getArguments()) > 0) {
$blocks[] = '### Arguments:';
if ($showArguments = count($definition->getArguments()) > 0) {
$this->write('### Arguments:');
foreach ($definition->getArguments() as $argument) {
$blocks[] = $this->describeInputArgument($argument);
$this->write("\n\n");
$this->write($this->describeInputArgument($argument));
}
}
if (count($definition->getOptions()) > 0) {
$blocks[] = '### Options:';
if ($showArguments) {
$this->write("\n\n");
}
$this->write('### Options:');
foreach ($definition->getOptions() as $option) {
$blocks[] = $this->describeInputOption($option);
$this->write("\n\n");
$this->write($this->describeInputOption($option));
}
}
return implode("\n\n", $blocks);
}
/**
@@ -84,21 +90,23 @@ class MarkdownDescriptor extends Descriptor
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
$markdown = $command->getName()."\n"
$this->write(
$command->getName()."\n"
.str_repeat('-', strlen($command->getName()))."\n\n"
.'* Description: '.($command->getDescription() ?: '<none>')."\n"
.'* Usage: `'.$command->getSynopsis().'`'."\n"
.'* Aliases: '.(count($command->getAliases()) ? '`'.implode('`, `', $command->getAliases()).'`' : '<none>');
.'* Aliases: '.(count($command->getAliases()) ? '`'.implode('`, `', $command->getAliases()).'`' : '<none>')
);
if ($help = $command->getProcessedHelp()) {
$markdown .= "\n\n".$help;
$this->write("\n\n");
$this->write($help);
}
if ($definitionMarkdown = $this->describeInputDefinition($command->getNativeDefinition())) {
$markdown .= "\n\n".$definitionMarkdown;
if ($definition = $command->getNativeDefinition()) {
$this->write("\n\n");
$this->describeInputDefinition($command->getNativeDefinition());
}
return $markdown;
}
/**
@@ -108,22 +116,24 @@ class MarkdownDescriptor extends Descriptor
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
$blocks = array($application->getName()."\n".str_repeat('=', strlen($application->getName())));
$this->write($application->getName()."\n".str_repeat('=', strlen($application->getName())));
foreach ($description->getNamespaces() as $namespace) {
if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$blocks[] = '**'.$namespace['id'].':**';
$this->write("\n\n");
$this->write('**'.$namespace['id'].':**');
}
$blocks[] = implode("\n", array_map(function ($commandName) {
$this->write("\n\n");
$this->write(implode("\n", array_map(function ($commandName) {
return '* '.$commandName;
} , $namespace['commands']));
} , $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {
$blocks[] = $this->describeCommand($command);
$this->write("\n\n");
$this->write($this->describeCommand($command));
}
return implode("\n\n", $blocks);
}
}
@@ -36,10 +36,12 @@ class TextDescriptor extends Descriptor
}
$nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($argument->getName());
$output = str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $argument->getDescription());
$output = sprintf(" <info>%-${nameWidth}s</info> %s%s", $argument->getName(), $output, $default);
return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
$this->writeText(sprintf(" <info>%-${nameWidth}s</info> %s%s",
$argument->getName(),
str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $argument->getDescription()),
$default
), $options);
}
/**
@@ -56,15 +58,13 @@ class TextDescriptor extends Descriptor
$nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($option->getName());
$nameWithShortcutWidth = $nameWidth - strlen($option->getName()) - 2;
$output = sprintf(" <info>%s</info> %-${nameWithShortcutWidth}s%s%s%s",
$this->writeText(sprintf(" <info>%s</info> %-${nameWithShortcutWidth}s%s%s%s",
'--'.$option->getName(),
$option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '',
str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $option->getDescription()),
$default,
$option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
);
return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
), $options);
}
/**
@@ -85,27 +85,27 @@ class TextDescriptor extends Descriptor
}
++$nameWidth;
$messages = array();
if ($definition->getArguments()) {
$messages[] = '<comment>Arguments:</comment>';
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$messages[] = $this->describeInputArgument($argument, array('name_width' => $nameWidth));
$this->describeInputArgument($argument, array_merge($options, array('name_width' => $nameWidth)));
$this->writeText("\n");
}
$messages[] = '';
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$messages[] = '<comment>Options:</comment>';
$this->writeText('<comment>Options:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getOptions() as $option) {
$messages[] = $this->describeInputOption($option, array('name_width' => $nameWidth));
$this->describeInputOption($option, array_merge($options, array('name_width' => $nameWidth)));
$this->writeText("\n");
}
$messages[] = '';
}
$output = implode("\n", $messages);
return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
}
/**
@@ -115,22 +115,30 @@ class TextDescriptor extends Descriptor
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
$messages = array('<comment>Usage:</comment>', ' '.$command->getSynopsis(), '');
if ($command->getAliases()) {
$messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $command->getAliases()).'</info>';
$this->writeText('<comment>Usage:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.$command->getSynopsis(), $options);
$this->writeText("\n");
if (count($command->getAliases()) > 0) {
$this->writeText("\n");
$this->writeText('<comment>Aliases:</comment> <info>'.implode(', ', $command->getAliases()).'</info>', $options);
}
$messages[] = $this->describeInputDefinition($command->getNativeDefinition());
if ($definition = $command->getNativeDefinition()) {
$this->writeText("\n");
$this->describeInputDefinition($definition, $options);
}
$this->writeText("\n");
if ($help = $command->getProcessedHelp()) {
$messages[] = '<comment>Help:</comment>';
$messages[] = ' '.str_replace("\n", "\n ", $help)."\n";
$this->writeText('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
$output = implode("\n", $messages);
return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
}
/**
@@ -140,41 +148,52 @@ class TextDescriptor extends Descriptor
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
$messages = array();
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getCommands());
foreach ($description->getCommands() as $command) {
$messages[] = sprintf("%-${width}s %s", $command->getName(), $command->getDescription());
$this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
$width = $this->getColumnWidth($description->getCommands());
$messages[] = $application->getHelp();
$messages[] = '';
$this->writeText($application->getHelp(), $options);
$this->writeText("\n\n");
if ($describedNamespace) {
$messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $describedNamespace);
$this->writeText(sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $describedNamespace), $options);
} else {
$messages[] = '<comment>Available commands:</comment>';
$this->writeText('<comment>Available commands:</comment>', $options);
}
// add commands by namespace
foreach ($description->getNamespaces() as $namespace) {
if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$messages[] = '<comment>'.$namespace['id'].'</comment>';
$this->writeText("\n");
$this->writeText('<comment>'.$namespace['id'].'</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$messages[] = sprintf(" <info>%-${width}s</info> %s", $name, $description->getCommand($name)->getDescription());
$this->writeText("\n");
$this->writeText(sprintf(" <info>%-${width}s</info> %s", $name, $description->getCommand($name)->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
$output = implode("\n", $messages);
return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output;
/**
* {@inheritdoc}
*/
private function writeText($content, array $options = array())
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
isset($options['raw_output']) ? !$options['raw_output'] : true
);
}
/**
@@ -24,10 +24,185 @@ use Symfony\Component\Console\Input\InputOption;
*/
class XmlDescriptor extends Descriptor
{
/**
* @param InputDefinition $definition
*
* @return \DOMDocument
*/
public function getInputDefinitionDocument(InputDefinition $definition)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($definition->getArguments() as $argument) {
$this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
}
return $dom;
}
/**
* @param Command $command
*
* @return \DOMDocument
*/
public function getCommandDocument(Command $command)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
$commandXML->setAttribute('id', $command->getName());
$commandXML->setAttribute('name', $command->getName());
$commandXML->appendChild($usageXML = $dom->createElement('usage'));
$usageXML->appendChild($dom->createTextNode(sprintf($command->getSynopsis(), '')));
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
$commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
foreach ($command->getAliases() as $alias) {
$aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
$aliasXML->appendChild($dom->createTextNode($alias));
}
$definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition());
$this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
return $dom;
}
/**
* @param Application $application
* @param string|null $namespace
*
* @return \DOMDocument
*/
public function getApplicationDocument(Application $application, $namespace = null)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
if ($application->getName() !== 'UNKNOWN') {
$rootXml->setAttribute('name', $application->getName());
if ($application->getVersion() !== 'UNKNOWN') {
$rootXml->setAttribute('version', $application->getVersion());
}
}
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
$description = new ApplicationDescription($application, $namespace);
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
}
foreach ($description->getCommands() as $command) {
$this->appendDocument($commandsXML, $this->getCommandDocument($command));
}
if (!$namespace) {
$rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
foreach ($description->getNamespaces() as $namespaceDescription) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);
foreach ($namespaceDescription['commands'] as $name) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($name));
}
}
}
return $dom;
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = array())
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = array())
{
$this->writeDocument($this->getInputOptionDocument($option));
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$this->writeDocument($this->getCommandDocument($command));
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
}
/**
* Appends document children to parent node.
*
* @param \DOMNode $parentNode
* @param \DOMNode $importedParent
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
}
}
/**
* Writes DOM document.
*
* @param \DOMDocument $dom
*
* @return \DOMDocument|string
*/
private function writeDocument(\DOMDocument $dom)
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
/**
* @param InputArgument $argument
*
* @return \DOMDocument
*/
private function getInputArgumentDocument(InputArgument $argument)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
@@ -45,13 +220,15 @@ class XmlDescriptor extends Descriptor
$defaultXML->appendChild($dom->createTextNode($default));
}
return $this->output($dom, $options);
return $dom;
}
/**
* {@inheritdoc}
* @param InputOption $option
*
* @return \DOMDocument
*/
protected function describeInputOption(InputOption $option, array $options = array())
private function getInputOptionDocument(InputOption $option)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
@@ -82,131 +259,6 @@ class XmlDescriptor extends Descriptor
}
}
return $this->output($dom, $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = array())
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($definition->getArguments() as $argument) {
$this->appendDocument($argumentsXML, $this->describeInputArgument($argument, array('as_dom' => true)));
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->describeInputOption($option, array('as_dom' => true)));
}
return $this->output($dom, $options);
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = array())
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
$commandXML->setAttribute('id', $command->getName());
$commandXML->setAttribute('name', $command->getName());
$commandXML->appendChild($usageXML = $dom->createElement('usage'));
$usageXML->appendChild($dom->createTextNode(sprintf($command->getSynopsis(), '')));
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
$commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
foreach ($command->getAliases() as $alias) {
$aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
$aliasXML->appendChild($dom->createTextNode($alias));
}
$definitionXML = $this->describeInputDefinition($command->getNativeDefinition(), array('as_dom' => true));
$this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
return $this->output($dom, $options);
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = array())
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
if ($describedNamespace) {
$commandsXML->setAttribute('namespace', $describedNamespace);
}
foreach ($description->getCommands() as $command) {
$this->appendDocument($commandsXML, $this->describeCommand($command, array('as_dom' => true)));
}
if (!$describedNamespace) {
$rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
foreach ($description->getNamespaces() as $namespace) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $namespace['id']);
foreach ($namespace['commands'] as $name) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($name));
}
}
}
return $this->output($dom, $options);
}
/**
* Appends document children to parent node.
*
* @param \DOMNode $parentNode
* @param \DOMNode $importedParent
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
}
}
/**
* Outputs document as DOMDocument or string according to options.
*
* @param \DOMDocument $dom
* @param array $options
*
* @return \DOMDocument|string
*/
private function output(\DOMDocument $dom, array $options)
{
if (isset($options['as_dom']) && $options['as_dom']) {
return $dom;
}
$dom->formatOutput = true;
return $dom->saveXML();
return $dom;
}
}
@@ -11,8 +11,6 @@
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
/**
* Allows to do things before the command is executed.
*
@@ -30,7 +30,7 @@ class ConsoleExceptionEvent extends ConsoleEvent
parent::__construct($command, $input, $output);
$this->setException($exception);
$this->exitCode = $exitCode;
$this->exitCode = (int) $exitCode;
}
/**
@@ -43,7 +43,7 @@ class ConsoleTerminateEvent extends ConsoleEvent
*/
public function setExitCode($exitCode)
{
$this->exitCode = $exitCode;
$this->exitCode = (int) $exitCode;
}
/**
@@ -46,23 +46,29 @@ class DescriptorHelper extends Helper
/**
* Describes an object if supported.
*
* Available options are:
* * format: string, the output format name
* * raw_text: boolean, sets output type as raw
*
* @param OutputInterface $output
* @param object $object
* @param string $format
* @param boolean $raw
* @param array $options
*
* @throws \InvalidArgumentException
*/
public function describe(OutputInterface $output, $object, $format = null, $raw = false, $namespace = null)
public function describe(OutputInterface $output, $object, array $options = array())
{
$options = array('raw_text' => $raw, 'format' => $format ?: 'txt', 'namespace' => $namespace);
$type = !$raw && 'txt' === $options['format'] ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW;
$options = array_merge(array(
'raw_text' => false,
'format' => 'txt',
), $options);
if (!isset($this->descriptors[$options['format']])) {
throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
}
$descriptor = $this->descriptors[$options['format']];
$output->writeln($descriptor->describe($object, $options), $type);
$descriptor->describe($output, $object, $options);
}
/**
@@ -19,7 +19,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterStyle;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DialogHelper extends Helper
class DialogHelper extends InputAwareHelper
{
private $inputStream;
private static $shell;
@@ -98,6 +98,10 @@ class DialogHelper extends Helper
*/
public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null)
{
if ($this->input && !$this->input->isInteractive()) {
return $default;
}
$output->write($question);
$inputStream = $this->inputStream ?: STDIN;
@@ -18,9 +18,9 @@ use Symfony\Component\Console\Command\Command;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelperSet
class HelperSet implements \IteratorAggregate
{
private $helpers;
private $helpers = array();
private $command;
/**
@@ -30,7 +30,6 @@ class HelperSet
*/
public function __construct(array $helpers = array())
{
$this->helpers = array();
foreach ($helpers as $alias => $helper) {
$this->set($helper, is_int($alias) ? null : $alias);
}
@@ -101,4 +100,9 @@ class HelperSet
{
return $this->command;
}
public function getIterator()
{
return new \ArrayIterator($this->helpers);
}
}
@@ -227,22 +227,7 @@ class ProgressHelper extends Helper
*/
public function advance($step = 1, $redraw = false)
{
if (null === $this->startTime) {
throw new \LogicException('You must start the progress bar before calling advance().');
}
if (0 === $this->current) {
$redraw = true;
}
$prevPeriod = intval($this->current / $this->redrawFreq);
$this->current += $step;
$currPeriod = intval($this->current / $this->redrawFreq);
if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) {
$this->display();
}
$this->setCurrent($this->current + $step, $redraw);
}
/**
@@ -272,7 +257,7 @@ class ProgressHelper extends Helper
$prevPeriod = intval($this->current / $this->redrawFreq);
$this->current = $current;
$currPeriod = intval($this->current / $this->redrawFreq);
if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) {
$this->display();
@@ -299,6 +284,18 @@ class ProgressHelper extends Helper
$this->overwrite($this->output, $message);
}
/**
* Removes the progress bar from the current line.
*
* This is useful if you wish to write some output
* while a progress bar is running.
* Call display() to show the progress bar again.
*/
public function clear()
{
$this->overwrite($this->output, '');
}
/**
* Finishes the progress output.
*/
@@ -23,6 +23,7 @@ class TableHelper extends Helper
{
const LAYOUT_DEFAULT = 0;
const LAYOUT_BORDERLESS = 1;
const LAYOUT_COMPACT = 2;
/**
* Table headers.
@@ -45,6 +46,7 @@ class TableHelper extends Helper
private $crossingChar;
private $cellHeaderFormat;
private $cellRowFormat;
private $cellRowContentFormat;
private $borderFormat;
private $padType;
@@ -89,7 +91,22 @@ class TableHelper extends Helper
->setVerticalBorderChar(' ')
->setCrossingChar(' ')
->setCellHeaderFormat('<info>%s</info>')
->setCellRowFormat('<comment>%s</comment>')
->setCellRowFormat('%s')
->setCellRowContentFormat(' %s ')
->setBorderFormat('%s')
->setPadType(STR_PAD_RIGHT)
;
break;
case self::LAYOUT_COMPACT:
$this
->setPaddingChar(' ')
->setHorizontalBorderChar('')
->setVerticalBorderChar(' ')
->setCrossingChar('')
->setCellHeaderFormat('<info>%s</info>')
->setCellRowFormat('%s')
->setCellRowContentFormat('%s')
->setBorderFormat('%s')
->setPadType(STR_PAD_RIGHT)
;
@@ -102,7 +119,8 @@ class TableHelper extends Helper
->setVerticalBorderChar('|')
->setCrossingChar('+')
->setCellHeaderFormat('<info>%s</info>')
->setCellRowFormat('<comment>%s</comment>')
->setCellRowFormat('%s')
->setCellRowContentFormat(' %s ')
->setBorderFormat('%s')
->setPadType(STR_PAD_RIGHT)
;
@@ -162,6 +180,10 @@ class TableHelper extends Helper
*/
public function setPaddingChar($paddingChar)
{
if (!$paddingChar) {
throw new \LogicException('The padding char must not be empty');
}
$this->paddingChar = $paddingChar;
return $this;
@@ -237,6 +259,20 @@ class TableHelper extends Helper
return $this;
}
/**
* Sets row cell content format.
*
* @param string $cellRowContentFormat
*
* @return TableHelper
*/
public function setCellRowContentFormat($cellRowContentFormat)
{
$this->cellRowContentFormat = $cellRowContentFormat;
return $this;
}
/**
* Sets table border format.
*
@@ -309,11 +345,13 @@ class TableHelper extends Helper
return;
}
if (!$this->horizontalBorderChar && !$this->crossingChar) {
return;
}
$markup = $this->crossingChar;
for ($column = 0; $column < $count; $column++) {
$markup .= str_repeat($this->horizontalBorderChar, $this->getColumnWidth($column))
.$this->crossingChar
;
$markup .= str_repeat($this->horizontalBorderChar, $this->getColumnWidth($column)).$this->crossingChar;
}
$this->output->writeln(sprintf($this->borderFormat, $markup));
@@ -366,15 +404,9 @@ class TableHelper extends Helper
$width += strlen($cell) - mb_strlen($cell, $encoding);
}
$this->output->write(sprintf(
$cellFormat,
str_pad(
$this->paddingChar.$cell.$this->paddingChar,
$width,
$this->paddingChar,
$this->padType
)
));
$content = sprintf($this->cellRowContentFormat, $cell);
$this->output->write(sprintf($cellFormat, str_pad($content, $width, $this->paddingChar, $this->padType)));
}
/**
@@ -416,7 +448,7 @@ class TableHelper extends Helper
$lengths[] = $this->getCellWidth($row, $column);
}
return $this->columnWidths[$column] = max($lengths) + 2;
return $this->columnWidths[$column] = max($lengths) + strlen($this->cellRowContentFormat) - 2;
}
/**
@@ -24,9 +24,12 @@ namespace Symfony\Component\Console\Input;
*/
abstract class Input implements InputInterface
{
/**
* @var InputDefinition
*/
protected $definition;
protected $options;
protected $arguments;
protected $options = array();
protected $arguments = array();
protected $interactive = true;
/**
@@ -37,8 +40,6 @@ abstract class Input implements InputInterface
public function __construct(InputDefinition $definition = null)
{
if (null === $definition) {
$this->arguments = array();
$this->options = array();
$this->definition = new InputDefinition();
} else {
$this->bind($definition);
@@ -18,6 +18,7 @@ if (!defined('JSON_UNESCAPED_UNICODE')) {
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Output\BufferedOutput;
/**
* A InputDefinition represents a set of valid command line arguments and options.
@@ -426,8 +427,10 @@ class InputDefinition
public function asText()
{
$descriptor = new TextDescriptor();
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
$descriptor->describe($output, $this, array('raw_output' => true));
return $descriptor->describe($this);
return $output->fetch();
}
/**
@@ -443,6 +446,13 @@ class InputDefinition
{
$descriptor = new XmlDescriptor();
return $descriptor->describe($this, array('as_dom' => $asDom));
if ($asDom) {
return $descriptor->getInputDefinitionDocument($this);
}
$output = new BufferedOutput();
$descriptor->describe($output, $this);
return $output->fetch();
}
}
@@ -46,7 +46,7 @@ abstract class Output implements OutputInterface
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null)
{
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
$this->formatter = null === $formatter ? new OutputFormatter() : $formatter;
$this->formatter = $formatter ?: new OutputFormatter();
$this->formatter->setDecorated($decorated);
}
@@ -98,6 +98,26 @@ abstract class Output implements OutputInterface
return $this->verbosity;
}
public function isQuiet()
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
public function isVerbose()
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
public function isVeryVerbose()
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
public function isDebug()
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
+1 -2
View File
@@ -32,7 +32,7 @@ class Shell
private $history;
private $output;
private $hasReadline;
private $processIsolation;
private $processIsolation = false;
/**
* Constructor.
@@ -48,7 +48,6 @@ class Shell
$this->application = $application;
$this->history = getenv('HOME').'/.history_'.$application->getName();
$this->output = new ConsoleOutput();
$this->processIsolation = false;
}
/**
@@ -32,6 +32,7 @@ class ApplicationTester
private $application;
private $input;
private $output;
private $statusCode;
/**
* Constructor.
@@ -72,7 +73,7 @@ class ApplicationTester
$this->output->setVerbosity($options['verbosity']);
}
return $this->application->run($this->input, $this->output);
return $this->statusCode = $this->application->run($this->input, $this->output);
}
/**
@@ -114,4 +115,14 @@ class ApplicationTester
{
return $this->output;
}
/**
* Gets the status code returned by the last execution of the application.
*
* @return integer The status code
*/
public function getStatusCode()
{
return $this->statusCode;
}
}
@@ -27,6 +27,7 @@ class CommandTester
private $command;
private $input;
private $output;
private $statusCode;
/**
* Constructor.
@@ -54,6 +55,15 @@ class CommandTester
*/
public function execute(array $input, array $options = array())
{
// set the command name automatically if the application requires
// this argument and no command name was passed
if (!isset($input['command'])
&& (null !== $application = $this->command->getApplication())
&& $application->getDefinition()->hasArgument('command')
) {
$input['command'] = $this->command->getName();
}
$this->input = new ArrayInput($input);
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
@@ -67,7 +77,7 @@ class CommandTester
$this->output->setVerbosity($options['verbosity']);
}
return $this->command->run($this->input, $this->output);
return $this->statusCode = $this->command->run($this->input, $this->output);
}
/**
@@ -109,4 +119,14 @@ class CommandTester
{
return $this->output;
}
/**
* Gets the status code returned by the last execution of the application.
*
* @return integer The status code
*/
public function getStatusCode()
{
return $this->statusCode;
}
}
@@ -14,6 +14,7 @@ namespace Symfony\Component\Console\Tests;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
@@ -22,6 +23,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Tester\ApplicationTester;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
@@ -40,6 +42,9 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
require_once self::$fixturesPath.'/Foo2Command.php';
require_once self::$fixturesPath.'/Foo3Command.php';
require_once self::$fixturesPath.'/Foo4Command.php';
require_once self::$fixturesPath.'/Foo5Command.php';
require_once self::$fixturesPath.'/FoobarCommand.php';
require_once self::$fixturesPath.'/BarBucCommand.php';
}
protected function normalizeLineBreaks($text)
@@ -124,6 +129,16 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array($foo, $foo1), array($commands['foo:bar'], $commands['foo:bar1']), '->addCommands() registers an array of commands');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Command class "Foo5Command" is not correctly initialized. You probably forgot to call the parent constructor.
*/
public function testAddCommandWithEmptyConstructor()
{
$application = new Application();
$application->add(new \Foo5Command());
}
public function testHasGet()
{
$application = new Application();
@@ -193,6 +208,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
public function testFindAmbiguousNamespace()
{
$application = new Application();
$application->add(new \BarBucCommand());
$application->add(new \FooCommand());
$application->add(new \Foo2Command());
$application->findNamespace('f');
@@ -208,6 +224,20 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$application->findNamespace('bar');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Command "foo1" is not defined
*/
public function testFindUniqueNameButNamespaceName()
{
$application = new Application();
$application->add(new \FooCommand());
$application->add(new \Foo1Command());
$application->add(new \Foo2Command());
$application->find($commandName = 'foo1');
}
public function testFind()
{
$application = new Application();
@@ -240,7 +270,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
return array(
array('f', 'Command "f" is not defined.'),
array('a', 'Command "a" is ambiguous (afoobar, afoobar1 and 1 more).'),
array('foo:b', 'Command "foo:b" is ambiguous (foo:bar, foo:bar1).')
array('foo:b', 'Command "foo:b" is ambiguous (foo:bar, foo:bar1 and 1 more).')
);
}
@@ -254,6 +284,23 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('Foo4Command', $application->find('foo3:bar:toh'), '->find() returns a command even if its namespace equals another command name');
}
public function testFindCommandWithAmbiguousNamespacesButUniqueName()
{
$application = new Application();
$application->add(new \FooCommand());
$application->add(new \FoobarCommand());
$this->assertInstanceOf('FoobarCommand', $application->find('f:f'));
}
public function testFindCommandWithMissingNamespace()
{
$application = new Application();
$application->add(new \Foo4Command());
$this->assertInstanceOf('Foo4Command', $application->find('f::t'));
}
/**
* @dataProvider provideInvalidCommandNamesSingle
* @expectedException \InvalidArgumentException
@@ -262,15 +309,15 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
public function testFindAlternativeExceptionMessageSingle($name)
{
$application = new Application();
$application->add(new \FooCommand());
$application->add(new \Foo3Command());
$application->find($name);
}
public function provideInvalidCommandNamesSingle()
{
return array(
array('foo:baR'),
array('foO:bar')
array('foo3:baR'),
array('foO3:bar')
);
}
@@ -288,6 +335,8 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
} catch (\Exception $e) {
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
$this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
$this->assertRegExp('/foo1:bar/', $e->getMessage());
$this->assertRegExp('/foo:bar/', $e->getMessage());
}
// Namespace + plural
@@ -297,6 +346,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
} catch (\Exception $e) {
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
$this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
$this->assertRegExp('/foo1/', $e->getMessage());
}
$application->add(new \Foo3Command());
@@ -329,26 +379,17 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(sprintf('Command "%s" is not defined.', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without alternatives');
}
// Test if "bar1" command throw an "\InvalidArgumentException" and does not contain
// "foo:bar" as alternative because "bar1" is too far from "foo:bar"
try {
$application->find($commandName = 'foo');
$application->find($commandName = 'bar1');
$this->fail('->find() throws an \InvalidArgumentException if command does not exist');
} catch (\Exception $e) {
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
$this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
$this->assertRegExp('/foo:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar"');
$this->assertRegExp('/foo1:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo1:bar"');
$this->assertRegExp('/afoobar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "afoobar1"');
$this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar1"');
}
// Test if "foo1" command throw an "\InvalidArgumentException" and does not contain
// "foo:bar" as alternative because "foo1" is too far from "foo:bar"
try {
$application->find($commandName = 'foo1');
$this->fail('->find() throws an \InvalidArgumentException if command does not exist');
} catch (\Exception $e) {
$this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist');
$this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives');
$this->assertFalse(strpos($e->getMessage(), 'foo:bar'), '->find() throws an \InvalidArgumentException if command does not exist, without "foo:bar" alternative');
$this->assertNotRegExp('/foo:bar(?>!1)/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without "foo:bar" alternative');
}
}
@@ -561,6 +602,29 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if -n is passed');
}
/**
* Issue #9285
*
* If the "verbose" option is just before an argument in ArgvInput,
* an argument value should not be treated as verbosity value.
* This test will fail with "Not enough arguments." if broken
*/
public function testVerboseValueNotBreakArguments()
{
$application = new Application();
$application->setAutoExit(false);
$application->setCatchExceptions(false);
$application->add(new \FooCommand());
$output = new StreamOutput(fopen('php://memory', 'w', false));
$input = new ArgvInput(array('cli.php', '-v', 'foo:bar'));
$application->run($input, $output);
$input = new ArgvInput(array('cli.php', '--verbose', 'foo:bar'));
$application->run($input, $output);
}
public function testRunReturnsIntegerExitCode()
{
$exception = new \Exception('', 4);
@@ -734,10 +798,6 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
public function testRunWithDispatcher()
{
if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
$this->markTestSkipped('The "EventDispatcher" component is not available');
}
$application = new Application();
$application->setAutoExit(false);
$application->setDispatcher($this->getDispatcher());
@@ -757,10 +817,6 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
*/
public function testRunWithExceptionAndDispatcher()
{
if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
$this->markTestSkipped('The "EventDispatcher" component is not available');
}
$application = new Application();
$application->setDispatcher($this->getDispatcher());
$application->setAutoExit(false);
@@ -776,10 +832,6 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
public function testRunDispatchesAllEventsWithException()
{
if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
$this->markTestSkipped('The "EventDispatcher" component is not available');
}
$application = new Application();
$application->setDispatcher($this->getDispatcher());
$application->setAutoExit(false);
@@ -795,9 +847,24 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertContains('before.foo.after.caught.', $tester->getDisplay());
}
public function testTerminalDimensions()
{
$application = new Application();
$originalDimensions = $application->getTerminalDimensions();
$this->assertCount(2, $originalDimensions);
$width = 80;
if ($originalDimensions[0] == $width) {
$width = 100;
}
$application->setTerminalDimensions($width, 80);
$this->assertSame(array($width, 80), $application->getTerminalDimensions());
}
protected function getDispatcher()
{
$dispatcher = new EventDispatcher;
$dispatcher = new EventDispatcher();
$dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) {
$event->getOutput()->write('before.');
});
@@ -16,31 +16,32 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\BufferedOutput;
abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase
{
/** @dataProvider getDescribeInputArgumentTestData */
public function testDescribeInputArgument(InputArgument $argument, $expectedDescription)
{
$this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($argument)));
$this->assertDescription($expectedDescription, $argument);
}
/** @dataProvider getDescribeInputOptionTestData */
public function testDescribeInputOption(InputOption $option, $expectedDescription)
{
$this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($option)));
$this->assertDescription($expectedDescription, $option);
}
/** @dataProvider getDescribeInputDefinitionTestData */
public function testDescribeInputDefinition(InputDefinition $definition, $expectedDescription)
{
$this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($definition)));
$this->assertDescription($expectedDescription, $definition);
}
/** @dataProvider getDescribeCommandTestData */
public function testDescribeCommand(Command $command, $expectedDescription)
{
$this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($command)));
$this->assertDescription($expectedDescription, $command);
}
/** @dataProvider getDescribeApplicationTestData */
@@ -53,7 +54,7 @@ abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase
$command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp()));
}
$this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $this->getDescriptor()->describe($application))));
$this->assertDescription($expectedDescription, $application);
}
public function getDescribeInputArgumentTestData()
@@ -94,4 +95,11 @@ abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase
return $data;
}
private function assertDescription($expectedDescription, $describedObject)
{
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
$this->getDescriptor()->describe($output, $describedObject, array('raw_output' => true));
$this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $output->fetch())));
}
}
@@ -1 +1 @@
{"commands":[{"name":"help","usage":"help [--xml] [--format=\"...\"] [--raw] [command_name]","description":"Displays help for a command","help":"The <info>help<\/info> command displays help for a given command:\n\n <info>php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the <comment>--format<\/comment> option:\n\n <info>php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the <info>list<\/info> command.","aliases":[],"definition":{"arguments":{"command_name":{"name":"command_name","is_required":false,"is_array":false,"description":"The command name","default":"help"}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output help as XML","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output help in other formats","default":null},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command help","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"list","usage":"list [--xml] [--raw] [--format=\"...\"] [namespace]","description":"Lists commands","help":"The <info>list<\/info> command lists all commands:\n\n <info>php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n <info>php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the <comment>--format<\/comment> option:\n\n <info>php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n <info>php app\/console list --raw<\/info>","aliases":[],"definition":{"arguments":{"namespace":{"name":"namespace","is_required":false,"is_array":false,"description":"The namespace name","default":null}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output list as XML","default":false},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command list","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output list in other formats","default":null}}}}],"namespaces":[{"id":"_global","commands":["help","list"]}]}
{"commands":[{"name":"help","usage":"help [--xml] [--format=\"...\"] [--raw] [command_name]","description":"Displays help for a command","help":"The <info>help<\/info> command displays help for a given command:\n\n <info>php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the <comment>--format<\/comment> option:\n\n <info>php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the <info>list<\/info> command.","aliases":[],"definition":{"arguments":{"command_name":{"name":"command_name","is_required":false,"is_array":false,"description":"The command name","default":"help"}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output help as XML","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output help in other formats","default":"txt"},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command help","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"list","usage":"list [--xml] [--raw] [--format=\"...\"] [namespace]","description":"Lists commands","help":"The <info>list<\/info> command lists all commands:\n\n <info>php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n <info>php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the <comment>--format<\/comment> option:\n\n <info>php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n <info>php app\/console list --raw<\/info>","aliases":[],"definition":{"arguments":{"namespace":{"name":"namespace","is_required":false,"is_array":false,"description":"The namespace name","default":null}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output list as XML","default":false},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command list","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output list in other formats","default":"txt"}}}}],"namespaces":[{"id":"_global","commands":["help","list"]}]}
@@ -51,7 +51,7 @@ To display the list of available commands, please use the <info>list</info> comm
* Is value required: yes
* Is multiple: no
* Description: To output help in other formats
* Default: `NULL`
* Default: `'txt'`
**raw:**
@@ -196,4 +196,4 @@ It's also possible to get raw list of commands (useful for embedding command run
* Is value required: yes
* Is multiple: no
* Description: To output list in other formats
* Default: `NULL`
* Default: `'txt'`
@@ -28,7 +28,9 @@
</option>
<option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
<description>To output help in other formats</description>
<defaults/>
<defaults>
<default>txt</default>
</defaults>
</option>
<option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
<description>To output raw command help</description>
@@ -90,7 +92,9 @@
</option>
<option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
<description>To output list in other formats</description>
<defaults/>
<defaults>
<default>txt</default>
</defaults>
</option>
</options>
</command>
File diff suppressed because one or more lines are too long
@@ -58,7 +58,7 @@ To display the list of available commands, please use the <info>list</info> comm
* Is value required: yes
* Is multiple: no
* Description: To output help in other formats
* Default: `NULL`
* Default: `'txt'`
**raw:**
@@ -203,7 +203,7 @@ It's also possible to get raw list of commands (useful for embedding command run
* Is value required: yes
* Is multiple: no
* Description: To output list in other formats
* Default: `NULL`
* Default: `'txt'`
descriptor:command1
-------------------
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<symfony>
<symfony name="My Symfony application" version="v1.0">
<commands>
<command id="help" name="help">
<usage>help [--xml] [--format="..."] [--raw] [command_name]</usage>
@@ -28,7 +28,9 @@
</option>
<option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
<description>To output help in other formats</description>
<defaults/>
<defaults>
<default>txt</default>
</defaults>
</option>
<option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
<description>To output raw command help</description>
@@ -90,7 +92,9 @@
</option>
<option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
<description>To output list in other formats</description>
<defaults/>
<defaults>
<default>txt</default>
</defaults>
</option>
</options>
</command>
@@ -17,4 +17,4 @@
<info>help </info> Displays help for a command
<info>list </info> Lists commands
<comment>foo</comment>
<info>foo:bar </info> The foo:bar command
<info>foo:bar </info> The foo:bar command
@@ -13,4 +13,4 @@
<info>--no-interaction</info> <info>-n</info> Do not ask any interactive question.
<comment>Available commands for the "foo" namespace:</comment>
<info>foo:bar </info> The foo:bar command
<info>foo:bar </info> The foo:bar command
@@ -28,7 +28,9 @@
</option>
<option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
<description>To output help in other formats</description>
<defaults/>
<defaults>
<default>txt</default>
</defaults>
</option>
<option name="--raw" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
<description>To output raw command help</description>
@@ -90,7 +92,9 @@
</option>
<option name="--format" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
<description>To output list in other formats</description>
<defaults/>
<defaults>
<default>txt</default>
</defaults>
</option>
</options>
</command>
@@ -7,7 +7,7 @@ Arguments:
Options:
--xml To output help as XML
--format To output help in other formats
--format To output help in other formats (default: "txt")
--raw To output raw command help
--help (-h) Display this help message.
--quiet (-q) Do not output any message.
@@ -27,4 +27,3 @@ Help:
php app/console help --format=xml list
To display the list of available commands, please use the list command.
@@ -7,7 +7,7 @@ Arguments:
Options:
--xml To output list as XML
--raw To output raw command list
--format To output list in other formats
--format To output list in other formats (default: "txt")
Help:
The list command lists all commands:
@@ -25,4 +25,3 @@ Help:
It's also possible to get raw list of commands (useful for embedding command runner):
php app/console list --raw
@@ -59,7 +59,7 @@ class OutputFormatterStyleStackTest extends \PHPUnit_Framework_TestCase
}
/**
* @expectedException InvalidArgumentException
* @expectedException \InvalidArgumentException
*/
public function testInvalidPop()
{
@@ -11,6 +11,7 @@
namespace Symfony\Component\Console\Tests\Helper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
@@ -156,6 +157,18 @@ class DialogHelperTest extends \PHPUnit_Framework_TestCase
}
}
public function testNoInteration()
{
$dialog = new DialogHelper();
$input = new ArrayInput(array());
$input->setInteractive(false);
$dialog->setInput($input);
$this->assertEquals('not yet', $dialog->ask($this->getOutputStream(), 'Do you have a job?', 'not yet'));
}
protected function getInputStream($input)
{
$stream = fopen('php://memory', 'r+', false);
@@ -111,6 +111,23 @@ class HelperSetTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($cmd, $helperset->getCommand(), '->getCommand() retrieves stored command');
}
/**
* @covers \Symfony\Component\Console\Helper\HelperSet::getIterator
*/
public function testIteration()
{
$helperset = new HelperSet();
$helperset->set($this->getGenericMockHelper('fake_helper_01', $helperset));
$helperset->set($this->getGenericMockHelper('fake_helper_02', $helperset));
$helpers = array('fake_helper_01', 'fake_helper_02');
$i = 0;
foreach ($helperset as $helper) {
$this->assertEquals($helpers[$i++], $helper->getName());
}
}
/**
* Create a generic mock for the helper interface. Optionally check for a call to setHelperSet with a specific
* helperset instance.
@@ -166,6 +166,20 @@ class ProgressHelperTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($this->generateOutput(' 3 [■■■>------------------------]'), stream_get_contents($output->getStream()));
}
public function testClear()
{
$progress = new ProgressHelper();
$progress->start($output = $this->getOutputStream(), 50);
$progress->setCurrent(25);
$progress->clear();
rewind($output->getStream());
$this->assertEquals(
$this->generateOutput(' 25/50 [==============>-------------] 50%') . $this->generateOutput(''),
stream_get_contents($output->getStream())
);
}
public function testPercentNotHundredBeforeComplete()
{
$progress = new ProgressHelper();
@@ -81,15 +81,17 @@ class TableHelperTest extends \PHPUnit_Framework_TestCase
public function testRenderProvider()
{
$books = array(
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
);
return array(
array(
array('ISBN', 'Title', 'Author'),
array(
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
),
$books,
TableHelper::LAYOUT_DEFAULT,
<<<TABLE
+---------------+--------------------------+------------------+
@@ -105,14 +107,32 @@ TABLE
),
array(
array('ISBN', 'Title', 'Author'),
array(
array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
),
$books,
TableHelper::LAYOUT_COMPACT,
<<<TABLE
ISBN Title Author
99921-58-10-7 Divine Comedy Dante Alighieri
9971-5-0210-0 A Tale of Two Cities Charles Dickens
960-425-059-0 The Lord of the Rings J. R. R. Tolkien
80-902734-1-6 And Then There Were None Agatha Christie
TABLE
),
array(
array('ISBN', 'Title', 'Author'),
$books,
TableHelper::LAYOUT_BORDERLESS,
" =============== ========================== ================== \n ISBN Title Author \n =============== ========================== ================== \n 99921-58-10-7 Divine Comedy Dante Alighieri \n 9971-5-0210-0 A Tale of Two Cities Charles Dickens \n 960-425-059-0 The Lord of the Rings J. R. R. Tolkien \n 80-902734-1-6 And Then There Were None Agatha Christie \n =============== ========================== ================== \n"
<<<TABLE
=============== ========================== ==================
ISBN Title Author
=============== ========================== ==================
99921-58-10-7 Divine Comedy Dante Alighieri
9971-5-0210-0 A Tale of Two Cities Charles Dickens
960-425-059-0 The Lord of the Rings J. R. R. Tolkien
80-902734-1-6 And Then There Were None Agatha Christie
=============== ========================== ==================
TABLE
),
array(
array('ISBN', 'Title'),
@@ -35,6 +35,35 @@ class OutputTest extends \PHPUnit_Framework_TestCase
$output = new TestOutput();
$output->setVerbosity(Output::VERBOSITY_QUIET);
$this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '->setVerbosity() sets the verbosity');
$this->assertTrue($output->isQuiet());
$this->assertFalse($output->isVerbose());
$this->assertFalse($output->isVeryVerbose());
$this->assertFalse($output->isDebug());
$output->setVerbosity(Output::VERBOSITY_NORMAL);
$this->assertFalse($output->isQuiet());
$this->assertFalse($output->isVerbose());
$this->assertFalse($output->isVeryVerbose());
$this->assertFalse($output->isDebug());
$output->setVerbosity(Output::VERBOSITY_VERBOSE);
$this->assertFalse($output->isQuiet());
$this->assertTrue($output->isVerbose());
$this->assertFalse($output->isVeryVerbose());
$this->assertFalse($output->isDebug());
$output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
$this->assertFalse($output->isQuiet());
$this->assertTrue($output->isVerbose());
$this->assertTrue($output->isVeryVerbose());
$this->assertFalse($output->isDebug());
$output->setVerbosity(Output::VERBOSITY_DEBUG);
$this->assertFalse($output->isQuiet());
$this->assertTrue($output->isVerbose());
$this->assertTrue($output->isVeryVerbose());
$this->assertTrue($output->isDebug());
}
public function testWriteWithVerbosityQuiet()
@@ -61,4 +61,9 @@ class ApplicationTesterTest extends \PHPUnit_Framework_TestCase
{
$this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution');
}
public function testGetStatusCode()
{
$this->assertSame(0, $this->tester->getStatusCode(), '->getStatusCode() returns the status code');
}
}
@@ -11,6 +11,7 @@
namespace Symfony\Component\Console\Tests\Tester;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Tester\CommandTester;
@@ -59,4 +60,25 @@ class CommandTesterTest extends \PHPUnit_Framework_TestCase
{
$this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution');
}
public function testGetStatusCode()
{
$this->assertSame(0, $this->tester->getStatusCode(), '->getStatusCode() returns the status code');
}
public function testCommandFromApplication()
{
$application = new Application();
$application->setAutoExit(false);
$command = new Command('foo');
$command->setCode(function ($input, $output) { $output->writeln('foo'); });
$application->add($command);
$tester = new CommandTester($application->find('foo'));
// check that there is no need to pass the command name here
$this->assertEquals(0, $tester->execute(array()));
}
}
@@ -31,7 +31,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
"dev-master": "2.4-dev"
}
}
}
@@ -56,7 +56,7 @@ test: Simple Mapping
brief: |
You can add a keyed list (also known as a dictionary or
hash) to your document by placing each member of the
list on a new line, with a colon seperating the key
list on a new line, with a colon separating the key
from its value. In YAML, this type of list is called
a mapping.
yaml: |
@@ -222,7 +222,7 @@ yaml: |
# Following node labeled SS
- &SS Sammy Sosa
rbi:
- *SS # Subsequent occurance
- *SS # Subsequent occurrence
- Ken Griffey
php: |
array(

Some files were not shown because too many files have changed in this diff Show More