Commit: 514f87e244d94cde94d3bbd09a6228b7af5f7212

Author: chawbacca | Date: 2011-05-26 18:12:03 +0000
Merge from lithium.git
diff --git a/app/config/bootstrap.php b/app/config/bootstrap.php index eaca28f..b9e90e8 100644 --- a/app/config/bootstrap.php +++ b/app/config/bootstrap.php @@ -2,50 +2,41 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ -/** - * This is the primary bootstrap file of your application, and is loaded immediately after the front - * controller (`index.php`) is invoked. The code below allows you to tell Lithium where to find - * your application and support libraries (including the framework itself). It also includes - * references to other feature-specific bootstrap files that you can turn on and off to configure - * the services needed for your application. - */ - -/** - * This is the path to the class libraries used by your application, and must contain a copy of the - * Lithium core. By default, this directory is named `libraries`, and resides in the same - * directory as your application. If you use the same libraries in multiple applications, you can - * set this to a shared path on your server. - */ -define('LITHIUM_LIBRARY_PATH', dirname(dirname(__DIR__)) . '/libraries'); /** - * This is the path to your application's directory. It contains all the sub-folders for your - * application's classes and files. You don't need to change this unless your webroot folder is - * stored outside of your app folder. + * This is the primary bootstrap file of your application, and is loaded immediately after the front + * controller (`webroot/index.php`) is invoked. It includes references to other feature-specific + * bootstrap files that you can turn on and off to configure the services needed for your + * application. + * + * Besides global configuration of external application resources, these files also include + * configuration for various classes to interact with one another, usually through _filters_. + * Filters are Lithium's system of creating interactions between classes without tight coupling. See + * the `Filters` class for more information. + * + * If you have other services that must be configured globally for the entire application, create a + * new bootstrap file and `require` it here. + * + * @see lithium\util\collection\Filters */ -define('LITHIUM_APP_PATH', dirname(__DIR__)); /** - * Locate and load Lithium core library files. Throws a fatal error if the core can't be found. - * If your Lithium core directory is named something other than 'lithium', change the string below. + * The libraries file contains the loading instructions for all plugins, frameworks and other class + * libraries used in the application, including the Lithium core, and the application itself. These + * instructions include library names, paths to files, and any applicable class-loading rules. This + * file also statically loads common classes to improve bootstrap performance. */ -if (!include LITHIUM_LIBRARY_PATH . '/lithium/core/Libraries.php') { - $message = "Lithium core could not be found. Check the value of LITHIUM_LIBRARY_PATH in "; - $message .= __FILE__ . ". It should point to the directory containing your "; - $message .= "/libraries directory."; - throw new ErrorException($message); -} +require __DIR__ . '/bootstrap/libraries.php'; /** - * This file contains the loading instructions for all class libraries used in the application, - * including the Lithium core, and the application itself. These instructions include library names, - * paths to files, and any applicable class-loading rules. Also includes any statically-loaded - * classes to improve bootstrap performance. + * The error configuration allows you to use the filter system along with the advanced matching + * rules of the `ErrorHandler` class to provide a high level of control over managing exceptions in + * your application, with no impact on framework or application code. */ -require __DIR__ . '/bootstrap/libraries.php'; +// require __DIR__ . '/bootstrap/errors.php'; /** * This file contains configurations for connecting to external caching resources, as well as diff --git a/app/config/bootstrap/action.php b/app/config/bootstrap/action.php index 41b4d5e..d1f7bfc 100644 --- a/app/config/bootstrap/action.php +++ b/app/config/bootstrap/action.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/app/config/bootstrap/cache.php b/app/config/bootstrap/cache.php index 3e124be..45b8524 100644 --- a/app/config/bootstrap/cache.php +++ b/app/config/bootstrap/cache.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -23,31 +23,38 @@ if (PHP_SAPI === 'cli') { * If APC is not available and the cache directory is not writeable, bail out. This block should be * removed post-install, and the cache should be configured with the adapter you plan to use. */ -if (!($apcEnabled = Apc::enabled()) && !is_writable(LITHIUM_APP_PATH . '/resources/tmp/cache')) { +$cachePath = Libraries::get(true, 'resources') . '/tmp/cache'; + +if (!($apcEnabled = Apc::enabled()) && !is_writable($cachePath)) { return; } +/** + * This configures the default cache, based on whether ot not APC user caching is enabled. If it is + * not, file caching will be used. Most of this code is for getting you up and running only, and + * should be replaced with a hard-coded configuration, based on the cache(s) you plan to use. + */ +$default = array('adapter' => 'File', 'strategies' => array('Serializer')); + if ($apcEnabled) { - $default = array( - 'adapter' => 'lithium\storage\cache\adapter\Apc', - ); -} else { - $default = array( - 'adapter' => 'lithium\storage\cache\adapter\File', - 'strategies' => array('Serializer') - ); + $default = array('adapter' => 'Apc'); } Cache::config(compact('default')); +/** + * Caches paths for auto-loaded and service-located classes. + */ Dispatcher::applyFilter('run', function($self, $params, $chain) { - if ($cache = Cache::read('default', 'core.libraries')) { + $key = md5(LITHIUM_APP_PATH) . '.core.libraries'; + + if ($cache = Cache::read('default', $key)) { $cache = (array) $cache + Libraries::cache(); Libraries::cache($cache); } $result = $chain->next($self, $params, $chain); if ($cache != Libraries::cache()) { - Cache::write('default', 'core.libraries', Libraries::cache(), '+1 day'); + Cache::write('default', $key, Libraries::cache(), '+1 day'); } return $result; }); diff --git a/app/config/bootstrap/connections.php b/app/config/bootstrap/connections.php index d8a1826..bec243d 100644 --- a/app/config/bootstrap/connections.php +++ b/app/config/bootstrap/connections.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/app/config/bootstrap/console.php b/app/config/bootstrap/console.php index e47e2c0..19d07fe 100644 --- a/app/config/bootstrap/console.php +++ b/app/config/bootstrap/console.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/app/config/bootstrap/errors.php b/app/config/bootstrap/errors.php new file mode 100644 index 0000000..8354478 --- /dev/null +++ b/app/config/bootstrap/errors.php @@ -0,0 +1,28 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +use lithium\core\ErrorHandler; +use lithium\action\Response; +use lithium\net\http\Media; + +ErrorHandler::apply('lithium\action\Dispatcher::run', array(), function($info, $params) { + $response = new Response(array( + 'request' => $params['request'], + 'status' => $info['exception']->getCode() + )); + + Media::render($response, compact('info', 'params'), array( + 'controller' => '_errors', + 'template' => 'development', + 'layout' => 'error', + 'request' => $params['request'] + )); + return $response; +}); + +?> \ No newline at end of file diff --git a/app/config/bootstrap/g11n.php b/app/config/bootstrap/g11n.php index ab9581f..c229a17 100644 --- a/app/config/bootstrap/g11n.php +++ b/app/config/bootstrap/g11n.php @@ -2,12 +2,16 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ -namespace lithium; - +/** + * This bootstrap file contains class configuration for all aspects of globalizing your application, + * including localization of text, validation rules, setting timezones and character inflections, + * and identifying a user's locale. + */ +use lithium\core\Libraries; use lithium\core\Environment; use lithium\g11n\Locale; use lithium\g11n\Catalog; @@ -31,13 +35,15 @@ date_default_timezone_set('UTC'); * globalization related settings. * * The environment settings are: - * - `'locale'` The effective locale. - * - `'locales'` Application locales available mapped to names. The available locales are used + * + * - `'locale'` The effective locale. + * - `'locales'` Application locales available mapped to names. The available locales are used * to negotiate he effective locale, the names can be used i.e. when displaying * a menu for choosing the locale to users. */ $locale = 'en'; $locales = array('en' => 'English'); + Environment::set('production', compact('locale', 'locales')); Environment::set('development', compact('locale', 'locales')); Environment::set('test', array('locale' => 'en', 'locales' => array('en' => 'English'))); @@ -46,26 +52,26 @@ Environment::set('test', array('locale' => 'en', 'locales' => array('en' => 'Eng * Globalization (g11n) catalog configuration. The catalog allows for obtaining and * writing globalized data. Each configuration can be adjusted through the following settings: * - * - `'adapter' The name of a supported adapter. The builtin adapters are _memory_ (a - * simple adapter good for runtime data and testing), _gettext_, _cldr_ (for - * interfacing with Unicode's common locale data repository) and _code_ (used mainly for + * - `'adapter'` _string_: The name of a supported adapter. The builtin adapters are `Memory` (a + * simple adapter good for runtime data and testing), `Php`, `Gettext`, `Cldr` (for + * interfacing with Unicode's common locale data repository) and `Code` (used mainly for * extracting message templates from source code). * - * - `'path'` All adapters with the exception of the _memory_ adapter require a directory + * - `'path'` All adapters with the exception of the `Memory` adapter require a directory * which holds the data. * * - `'scope'` If you plan on using scoping i.e. for accessing plugin data separately you - * need to specify a scope for each configuration, except for those using the _memory_, - * _php_ or _gettext_ adapter which handle this internally. + * need to specify a scope for each configuration, except for those using the `Memory`, + * `Php` or `Gettext` adapter which handle this internally. */ Catalog::config(array( 'runtime' => array( 'adapter' => 'Memory' ), -// 'app' => array( -// 'adapter' => 'Gettext', -// 'path' => LITHIUM_APP_PATH . '/resources/g11n' -// ), + // 'app' => array( + // 'adapter' => 'Gettext', + // 'path' => Libraries::get(true, 'resources') . '/g11n' + // ), 'lithium' => array( 'adapter' => 'Php', 'path' => LITHIUM_LIBRARY_PATH . '/lithium/g11n/resources/php' @@ -77,7 +83,7 @@ Catalog::config(array( */ // Inflector::rules('transliteration', Catalog::read(true, 'inflection.transliteration', 'en')); -/* +/** * Inflector configuration examples. If your application has custom singular or plural rules, or * extra non-ASCII characters to transliterate, you can configure that by uncommenting the lines * below. @@ -128,6 +134,7 @@ ActionDispatcher::applyFilter('_callable', function($self, $params, $chain) { Environment::set(Environment::get(), array('locale' => $request->locale)); return $controller; }); + ConsoleDispatcher::applyFilter('_callable', function($self, $params, $chain) { $request = $params['request']; $command = $chain->next($self, $params, $chain); diff --git a/app/config/bootstrap/libraries.php b/app/config/bootstrap/libraries.php index cdb3b3f..273b561 100644 --- a/app/config/bootstrap/libraries.php +++ b/app/config/bootstrap/libraries.php @@ -2,10 +2,76 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ +/** + * The libraries file is where you configure the various plugins, frameworks, and other libraries + * to be used by your application, including your application itself. This file also defines some + * global constants used to tell Lithium where to find your application and support libraries + * (including Lithium itself). It uses the `Libraries` class to add configurations for the groups of + * classes used in your app. + * + * In Lithium, a _library_ is any collection of classes located in a single base directory, which + * all share the same class-to-file naming convention, and usually a common class or namespace + * prefix. While all collections of classes are considered libraries, there are two special types of + * libraries: + * + * - **Applications**: Applications are libraries which follow the organizational conventions that + * Lithium defines for applications (see `Libraries::locate()` and `Libraries::paths()`), and + * which also include a web-accessible document root (i.e. the `webroot/` folder), and can + * dispatch HTTP requests (i.e. through `webroot/index.php`). + * + * - **Plugins**: Plugins are libraries which generally follow the same organizational conventions + * as applications, but are designed to be used within the context of another application. They + * _may_ include a public document root for supporting assets, but this requires a symlink from + * `libraries/<plugin-name>/webroot` to `<app-name>/webroot/<plugin-name>` (recommended for + * production), or a media filter to load plugin resources (see `/config/bootstrap/media.php`). + * + * Note that a library can be designed as both an application and a plugin, but this requires some + * special considerations in the bootstrap process, such as removing any `require` statements, and + * conditionally defining the constants below. + * + * By default, libraries are stored in the base `/libraries` directory, or in the + * application-specific `<app-name>/libraries` directory. Libraries can be loaded from either place + * without additional configuration, but note that if the same library is in both directories, the + * application-specific `libraries` directory will override the global one. + * + * The one exception to this is the _primary_ library, which is an application configured with + * `'default' => true` (see below); this library uses the `LITHIUM_APP_PATH` constant (also defined + * below) as its path. Note, however, that any library can be overridden with an arbitrary path by + * passing the `'path'` key to its configuration. See `Libraries::add()` for more options. + * + * @see lithium\core\Libraries + */ + +/** + * This is the path to your application's directory. It contains all the sub-folders for your + * application's classes and files. You don't need to change this unless your webroot folder is + * stored outside of your app folder. + */ +define('LITHIUM_APP_PATH', dirname(dirname(__DIR__))); + +/** + * This is the path to the class libraries used by your application, and must contain a copy of the + * Lithium core. By default, this directory is named `libraries`, and resides in the same + * directory as your application. If you use the same libraries in multiple applications, you can + * set this to a shared path on your server. + */ +define('LITHIUM_LIBRARY_PATH', dirname(LITHIUM_APP_PATH) . '/libraries'); + +/** + * Locate and load Lithium core library files. Throws a fatal error if the core can't be found. + * If your Lithium core directory is named something other than `lithium`, change the string below. + */ +if (!include LITHIUM_LIBRARY_PATH . '/lithium/core/Libraries.php') { + $message = "Lithium core could not be found. Check the value of LITHIUM_LIBRARY_PATH in "; + $message .= __FILE__ . ". It should point to the directory containing your "; + $message .= "/libraries directory."; + throw new ErrorException($message); +} + use lithium\core\Libraries; /** @@ -52,7 +118,7 @@ Libraries::add('lithium'); Libraries::add('app', array('default' => true)); /** - * Add some plugins + * Add some plugins: */ // Libraries::add('li3_docs'); diff --git a/app/config/bootstrap/media.php b/app/config/bootstrap/media.php index c4a42c6..a350b47 100644 --- a/app/config/bootstrap/media.php +++ b/app/config/bootstrap/media.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/app/config/bootstrap/session.php b/app/config/bootstrap/session.php index b57d0b4..d8b5849 100644 --- a/app/config/bootstrap/session.php +++ b/app/config/bootstrap/session.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -13,22 +13,23 @@ use lithium\storage\Session; Session::config(array( - 'cookie' => array('adapter' => 'Cookie'), + // 'cookie' => array('adapter' => 'Cookie'), 'default' => array('adapter' => 'Php') )); /** - * Uncomment this to enable forms-based authentication. The configuration below will attempt to - * authenticate users against a `User` model. In a controller, run + * Uncomment the lines below to enable forms-based authentication. This configuration will attempt + * to authenticate users against a `Users` model. In a controller, run * `Auth::check('default', $this->request)` to authenticate a user. This will check the POST data of * the request (`lithium\action\Request::$data`) to see if the fields match the `'fields'` key of - * the configuration below. If successful, it will write the data returned from `User::first()` to - * the session using the default session configuration. Once the session data is written, you can - * call `Auth::check('default')` to check authentication status or retrieve the user's data from the - * session. Call `Auth::clear('default')` to remove the user's authentication details from the - * session. This effectively logs a user out of the system. To modify the form input that the - * adapter accepts, or how the configured model is queried, or how the data is stored in the - * session, see the `Form` adapter API or the `Auth` API, respectively. + * the configuration below. If successful, it will write the data returned from `Users::first()` to + * the session using the default session configuration. + * + * Once the session data is written, you can call `Auth::check('default')` to check authentication + * status or retrieve the user's data from the session. Call `Auth::clear('default')` to remove the + * user's authentication details from the session. This effectively logs a user out of the system. + * To modify the form input that the adapter accepts, or how the configured model is queried, or how + * the data is stored in the session, see the `Form` adapter API or the `Auth` API, respectively. * * @see lithium\security\auth\adapter\Form * @see lithium\action\Request::$data @@ -39,7 +40,7 @@ Session::config(array( // Auth::config(array( // 'default' => array( // 'adapter' => 'Form', -// 'model' => 'User', +// 'model' => 'Users', // 'fields' => array('username', 'password') // ) // )); diff --git a/app/config/routes.php b/app/config/routes.php index 502d421..3c45e27 100644 --- a/app/config/routes.php +++ b/app/config/routes.php @@ -2,27 +2,44 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ +/** + * The routes file is where you define your URL structure, which is an important part of the + * [information architecture](http://en.wikipedia.org/wiki/Information_architecture) of your + * application. Here, you can use _routes_ to match up URL pattern strings to a set of parameters, + * usually including a controller and action to dispatch matching requests to. For more information, + * see the `Router` and `Route` classes. + * + * @see lithium\net\http\Router + * @see lithium\net\http\Route + */ use lithium\net\http\Router; use lithium\core\Environment; /** - * Here, we are connecting '/' (base path) to controller called 'Pages', - * its action called 'view', and we pass a param to select the view file - * to use (in this case, /app/views/pages/home.html.php)... + * Here, we are connecting `'/'` (the base path) to controller called `'Pages'`, + * its action called `view()`, and we pass a param to select the view file + * to use (in this case, `/views/pages/home.html.php`; see `app\controllers\PagesController` + * for details). + * + * @see app\controllers\PagesController */ -Router::connect('/', array('Pages::view', 'args' => array('home'))); +Router::connect('/', 'Pages::view'); /** - * ...and connect the rest of 'Pages' controller's urls. + * Connect the rest of `PagesController`'s URLs. This will route URLs like `/pages/about` to + * `PagesController`, rendering `/views/pages/about.html.php` as a static page. */ Router::connect('/pages/{:args}', 'Pages::view'); /** - * Connect the testing routes. + * Add the testing routes. These routes are only connected in non-production environments, and allow + * browser-based access to the test suite for running unit and integration tests for the Lithium + * core, as well as your own application and any other loaded plugins or frameworks. Browse to + * [http://path/to/app/test](/test) to run tests. */ if (!Environment::is('production')) { Router::connect('/test/{:args}', array('controller' => 'lithium\test\Controller')); @@ -30,10 +47,38 @@ if (!Environment::is('production')) { } /** - * Finally, connect the default routes. + * ### Database object routes + * + * The routes below are used primarily for accessing database objects, where `{:id}` corresponds to + * the primary key of the database object, and can be accessed in the controller as + * `$this->request->id`. + * + * If you're using a relational database, such as MySQL, SQLite or Postgres, where the primary key + * is an integer, uncomment the routes below to enable URLs like `/posts/edit/1138`, + * `/posts/view/1138.json`, etc. + */ +// Router::connect('/{:controller}/{:action}/{:id:\d+}.{:type}', array('id' => null)); +// Router::connect('/{:controller}/{:action}/{:id:\d+}'); + +/** + * If you're using a document-oriented database, such as CouchDB or MongoDB, or another type of + * database which uses 24-character hexidecimal values as primary keys, uncomment the routes below. + */ +// Router::connect('/{:controller}/{:action}/{:id:[0-9a-f]{24}}.{:type}', array('id' => null)); +// Router::connect('/{:controller}/{:action}/{:id:[0-9a-f]{24}}'); + +/** + * Finally, connect the default route. This route acts as a catch-all, intercepting requests in the + * following forms: + * + * - `/foo/bar`: Routes to `FooController::bar()` with no parameters passed. + * - `/foo/bar/param1/param2`: Routes to `FooController::bar('param1, 'param2')`. + * - `/foo`: Routes to `FooController::index()`, since `'index'` is assumed to be the action if none + * is otherwise specified. + * + * In almost all cases, custom routes should be added above this one, since route-matching works in + * a top-down fashion. */ -Router::connect('/{:controller}/{:action}/{:id:[0-9]+}.{:type}', array('id' => null)); -Router::connect('/{:controller}/{:action}/{:id:[0-9]+}'); Router::connect('/{:controller}/{:action}/{:args}'); ?> \ No newline at end of file diff --git a/app/controllers/HelloWorldController.php b/app/controllers/HelloWorldController.php index 5f43d1c..8dad480 100644 --- a/app/controllers/HelloWorldController.php +++ b/app/controllers/HelloWorldController.php @@ -5,7 +5,7 @@ namespace app\controllers; class HelloWorldController extends \lithium\action\Controller { public function index() { - $this->render(array('layout' => false)); + return $this->render(array('layout' => false)); } public function to_string() { @@ -13,7 +13,7 @@ class HelloWorldController extends \lithium\action\Controller { } public function to_json() { - $this->render(array('json' => 'Hello World')); + return $this->render(array('json' => 'Hello World')); } } diff --git a/app/controllers/PagesController.php b/app/controllers/PagesController.php index f4a5cf0..30e6292 100644 --- a/app/controllers/PagesController.php +++ b/app/controllers/PagesController.php @@ -1,16 +1,33 @@ <?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ namespace app\controllers; +/** + * This controller is used for serving static pages by name, which are located in the `/views/pages` + * folder. + * + * A Lithium application's default routing provides for automatically routing and rendering + * static pages using this controller. The default route (`/`) will render the `home` template, as + * specified in the `view()` action. + * + * Additionally, any other static templates in `/views/pages` can be called by name in the URL. For + * example, browsing to `/pages/about` will render `/views/pages/about.html.php`, if it exists. + * + * Templates can be nested within directories as well, which will automatically be accounted for. + * For example, browsing to `/pages/about/company` will render + * `/views/pages/about/company.html.php`. + */ class PagesController extends \lithium\action\Controller { public function view() { - $path = func_get_args(); - - if (empty($path)) { - $path = array('home'); - } - $this->render(array('template' => join('/', $path))); + $path = func_get_args() ?: array('home'); + return $this->render(array('template' => join('/', $path))); } } diff --git a/app/index.php b/app/index.php index 4718294..9958e44 100644 --- a/app/index.php +++ b/app/index.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/app/resources/g11n/empty b/app/resources/g11n/empty old mode 100755 new mode 100644 diff --git a/app/resources/tmp/cache/templates/empty b/app/resources/tmp/cache/templates/empty old mode 100755 new mode 100644 diff --git a/app/resources/tmp/logs/empty b/app/resources/tmp/logs/empty old mode 100755 new mode 100644 diff --git a/app/resources/tmp/tests/empty b/app/resources/tmp/tests/empty old mode 100755 new mode 100644 diff --git a/app/views/_errors/development.html.php b/app/views/_errors/development.html.php new file mode 100644 index 0000000..18654f6 --- /dev/null +++ b/app/views/_errors/development.html.php @@ -0,0 +1,108 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +use lithium\analysis\Debugger; +use lithium\analysis\Inspector; + +$exception = $info['exception']; +$replace = array('&lt;?php', '?&gt;', '<code>', '</code>', "\n"); +$context = 5; + +/** + * Set Lithium-esque colors for syntax highlighing. + */ +ini_set('highlight.string', '#4DDB4A'); +ini_set('highlight.comment', '#D42AAE'); +ini_set('highlight.keyword', '#D42AAE'); +ini_set('highlight.default', '#3C96FF'); +ini_set('highlight.htm', '#FFFFFF'); + +$stack = Debugger::trace(array('format' => 'array', 'trace' => $exception->getTrace())); + +array_unshift($stack, array( + 'functionRef' => '[exception]', + 'file' => $exception->getFile(), + 'line' => $exception->getLine() +)); + +?> +<h3>Exception</h3> + +<div class="lithium-exception-class"> + <?=get_class($exception);?> + + <?php if ($code = $exception->getCode()): ?> + <span class="code">(code <?=$code; ?>)</span> + <?php endif ?> +</div> + +<div class="lithium-exception-message"><?=$exception->getMessage(); ?></div> + +<h3 id="source">Source</h3> + +<div id="sourceCode"></div> + +<h3>Stack Trace</h3> + +<div class="lithium-stack-trace"> + <ol> + <?php foreach ($stack as $id => $frame): ?> + <?php + $location = "{$frame['file']}: {$frame['line']}"; + $lines = range($frame['line'] - $context, $frame['line'] + $context); + $code = Inspector::lines($frame['file'], $lines); + ?> + <li> + <tt><a href="#source" id="<?=$id; ?>" class="display-source-excerpt"> + <?=$frame['functionRef']; ?> + </a></tt> + <div id="sourceCode<?=$id; ?>" style="display: none;"> + + <div class="lithium-exception-location"> + <?=$location; ?> + </div> + + <div class="lithium-code-dump"> + <pre><code><?php + foreach ($code as $num => $content): + $numPad = str_pad($num, 3, ' '); + $content = str_ireplace(array('<?php', '?>'), '', $content); + $content = highlight_string("<?php {$numPad}{$content} ?>", true); + $content = str_replace($replace, '', $content); + + if ($frame['line'] === $num): + ?><span class="code-highlight"><?php + endif;?><?php echo "{$content}\n"; ?><?php + if ($frame['line'] === $num): + ?></span><?php + endif; + + endforeach; + ?></code></pre> + </div> + </div> + </li> + <?php endforeach; ?> + </ol> +</div> + +<script type="text/javascript"> + window.onload = function() { + var $ = function() { return document.getElementById.apply(document, arguments); }; + var links = document.getElementsByTagName('a'); + + for (i = 0; i < links.length; i++) { + if (links[i].className.indexOf('display-source-excerpt') >= 0) { + links[i].onclick = function() { + $('sourceCode').innerHTML = $('sourceCode' + this.id).innerHTML; + } + } + } + $('sourceCode').innerHTML = $('sourceCode0').innerHTML; + } +</script> \ No newline at end of file diff --git a/app/views/layouts/default.html.php b/app/views/layouts/default.html.php index 6f76bd2..f807940 100644 --- a/app/views/layouts/default.html.php +++ b/app/views/layouts/default.html.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ ?> diff --git a/app/views/layouts/default.xml.php b/app/views/layouts/default.xml.php index 4a92323..82f6a86 100644 --- a/app/views/layouts/default.xml.php +++ b/app/views/layouts/default.xml.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ ?> diff --git a/app/views/layouts/error.html.php b/app/views/layouts/error.html.php new file mode 100644 index 0000000..2f26d97 --- /dev/null +++ b/app/views/layouts/error.html.php @@ -0,0 +1,46 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +/** + * This layout is used to render error pages in both development and production. It is recommended + * that you maintain a separate, simplified layout for rendering errors that does not involve any + * complex logic or dynamic data, which could potentially trigger recursive errors. + */ +?> +<!doctype html> +<html> +<head> + <?php echo $this->html->charset(); ?> + <title>Unhandled exception</title> + <?php echo $this->html->style(array('debug', 'lithium')); ?> + <?php echo $this->scripts(); ?> + <?php echo $this->html->link('Icon', null, array('type' => 'icon')); ?> +</head> +<body class="app"> + <div id="container"> + <div id="header"> + <h1>An unhandled exception was thrown</h1> + <h3>Configuration</h3> + <p> + This layout can be changed by modifying + <code><?php + echo realpath(LITHIUM_APP_PATH . '/views/layouts/error.html.php'); + ?></code> + </p><p> + To modify your error-handling configuration, see + <code><?php + echo realpath(LITHIUM_APP_PATH . '/config/bootstrap/errors.php'); + ?></code> + </p> + </div> + <div id="content"> + <?php echo $this->content(); ?> + </div> + </div> +</body> +</html> \ No newline at end of file diff --git a/app/views/pages/home.html.php b/app/views/pages/home.html.php index 64e15f5..40b6efb 100644 --- a/app/views/pages/home.html.php +++ b/app/views/pages/home.html.php @@ -2,10 +2,11 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ +use lithium\core\Libraries; use lithium\data\Connections; $checkName = null; @@ -37,11 +38,11 @@ $notify = function($status, $message, $solution = null) use (&$checkName, &$chec $sanityChecks = array( 'resourcesWritable' => function() use ($notify) { - if (is_writable($path = realpath(LITHIUM_APP_PATH . '/resources'))) { + if (is_writable($path = realpath(Libraries::get(true, 'resources')))) { return $notify(true, 'Resources directory is writable.'); } return $notify(false, array( - "Your resource path (<code>$path</code>) is not writeable. " . + "Your resource path (<code>{$path}</code>) is not writeable. " . "To fix this on *nix and Mac OSX, run the following from the command line:", "<code>chmod -R 0777 {$path}</code>" )); diff --git a/app/webroot/css/debug.css b/app/webroot/css/debug.css index a9ecb71..e47351b 100644 --- a/app/webroot/css/debug.css +++ b/app/webroot/css/debug.css @@ -1,7 +1,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ * { @@ -403,6 +403,16 @@ pre.lithium-debug { position: relative; } +div.lithium-exception-class, div.lithium-exception-location { + font-weight: bold; +} + +div.lithium-exception-message { + color: #000; + background: #f0f0f0; + padding: 1em; +} + div.lithium-stack-trace { background: #fff; border: 4px dotted #ffcc00; @@ -440,7 +450,6 @@ div.lithium-code-dump pre, div.lithium-code-dump pre code { div.lithium-code-dump span.code-highlight { background-color: #ff0; - padding: 4px; } /*--- Code Coverage Analysis ---*/ diff --git a/app/webroot/css/lithium.css b/app/webroot/css/lithium.css index be4deac..7b643a6 100644 --- a/app/webroot/css/lithium.css +++ b/app/webroot/css/lithium.css @@ -38,7 +38,7 @@ h6 { font-size: 1em; } p { margin-bottom: 1em; } strong { font-weight: bold; } em { font-style: italic; } -a { text-decoration: none; color: #666; border-bottom: 1px dashed #00bbff; } +a { text-decoration: none; color: #666; } a, h1 a, h2 a { text-decoration: none; } a:hover { color: #00bbff; } a:visited:hover { color: #ff59ff; } @@ -266,7 +266,7 @@ code { color: #666; } table, form, pre > code { - margin-top: 12px; + margin-top: 0px; margin-bottom: 12px; } table, form, pre > code, .shadow { diff --git a/app/webroot/index.php b/app/webroot/index.php index 3b79fa0..dc298b9 100644 --- a/app/webroot/index.php +++ b/app/webroot/index.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,6 +10,10 @@ * Welcome to Lithium! This front-controller file is the gateway to your application. It is * responsible for intercepting requests, and handing them off to the `Dispatcher` for processing. * + * @see lithium\action\Dispatcher +*/ + +/** * If you're sharing a single Lithium core install or other libraries among multiple * applications, you may need to manually set things like `LITHIUM_LIBRARY_PATH`. You can do that in * `config/bootstrap.php`, which is loaded below: @@ -23,15 +27,16 @@ require dirname(__DIR__) . '/config/bootstrap.php'; * information. * * The `Request` is then used by the `Dispatcher` (in conjunction with the `Router`) to determine - * the correct controller to dispatch to, and the correct response type to render. The response - * information is then encapsulated in a `Response` object, which is returned from the controller - * to the `Dispatcher`, and finally echoed below. Echoing a `Response` object causes its headers to - * be written, and its response body to be written in a buffer loop. + * the correct `Controller` object to dispatch to, and the correct response type to render. The + * response information is then encapsulated in a `Response` object, which is returned from the + * controller to the `Dispatcher`, and finally echoed below. Echoing a `Response` object causes its + * headers to be written, and its response body to be written in a buffer loop. * * @see lithium\action\Request * @see lithium\action\Response * @see lithium\action\Dispatcher * @see lithium\net\http\Router + * @see lithium\action\Controller */ echo lithium\action\Dispatcher::run(new lithium\action\Request()); diff --git a/libraries/lithium/action/Controller.php b/libraries/lithium/action/Controller.php index d9fc261..7d6ecc2 100644 --- a/libraries/lithium/action/Controller.php +++ b/libraries/lithium/action/Controller.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -63,31 +63,36 @@ class Controller extends \lithium\core\Object { /** * Lists the rendering control options for responses generated by this controller. * - * The `'type'` key is the content type that will be rendered by default, unless another is - * explicitly specified (defaults to `'html'`). + * - The `'type'` key is the content type that will be rendered by default, unless another is + * explicitly specified (defaults to `'html'`). + * - The `'data'` key contains an associative array of variables to be sent to the view, + * including any variables created in `set()`, or if an action returns any variables (as an + * associative array). + * - When an action is invoked, it will by default attempt to render a response, set the + * `'auto'` key to `false` to prevent this behavior. + * - If you manually call `render()` within an action, the `'hasRendered'` key stores this + * state, so that responses are not rendered multiple times, either manually or automatically. + * - The `'layout'` key specifies the name of the layout to be used (defaults to `'default'`). + * Typically, layout files are looked up as + * `<app-path>/views/layouts/<layout-name>.<type>.php`. Based on the default settings, the + * actual path would be `app/views/layouts/default.html.php`. + * - Though typically introspected from the action that is executed, the `'template'` key can be + * manually specified. This sets the template to be rendered, and is looked up (by default) as + * `<app-path>/views/<controller>/<action>.<type>.php`, i.e.: + * `app/views/posts/index.html.php`. + * - To enable automatic content-type negotiation (i.e. determining the content type of the + * response based on the value of the + * [HTTP Accept header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)), set the + * `'negotiate'` flag to `true`. Otherwise, the response will only be based on the `type` + * parameter of the request object (defaulting to `'html'` if no type is present in the + * `Request` parameters). * - * The `'data'` key contains an associative array of variables to be sent to the view, including - * any variables created in `set()`, or if an action returns any variables (as an associative - * array). - * - * When an action is invoked, it will by default attempt to render a response, set the `'auto'` - * key to `false` to prevent this behavior. - * - * If you manually call `render()` within an action, the `'hasRendered'` key stores this state, - * so that responses are not rendered multiple times, either manually or automatically. - * - * The `'layout'` key specifies the name of the layout to be used (defaults to `'default'`). - * Typically, layout files are looked up as `<app-path>/views/layouts/<layout-name>.<type>.php`. - * Based on the default settings, the actual path would be `app/views/layouts/default.html.php`. - * - * Though typically introspected from the action that is executed, the `'template'` key can be - * manually specified. This sets the template to be rendered, and is looked up (by default) as - * `<app-path>/views/<controller>/<action>.<type>.php`, i.e.: `app/views/posts/index.html.php`. - * - * To change the inner-workings of these settings (template paths, default render settings for - * individual content types), see the `lithium\net\http\Media` class. + * Keep in mind that most of these settings may be passed to `Controller::render()` as well. To + * change how these settings operate (i.e. template paths, default render settings for + * individual media types), see the `Media` class. * * @var array + * @see lithium\action\Controller::render() * @see lithium\net\http\Media::type() * @see lithium\net\http\Media::render() */ @@ -97,7 +102,8 @@ class Controller extends \lithium\core\Object { 'auto' => true, 'layout' => 'default', 'template' => null, - 'hasRendered' => false + 'hasRendered' => false, + 'negotiate' => false ); /** @@ -129,13 +135,16 @@ class Controller extends \lithium\core\Object { protected function _init() { parent::_init(); $this->request = $this->request ?: $this->_config['request']; - $media = $this->_classes['media']; + $this->response = $this->_instance('response', $this->_config['response']); - if ($this->request && !$this->_render['type']) { - $this->_render['type'] = $this->request->accepts() ?: 'html'; + if (!$this->request || $this->_render['type']) { + return; } - $config = $this->_config['response'] + array('request' => $this->request); - $this->response = $this->_instance('response', $config); + if ($this->_render['negotiate']) { + $this->_render['type'] = $this->request->accepts(); + return; + } + $this->_render['type'] = $this->request->type ?: 'html'; } /** @@ -165,7 +174,7 @@ class Controller extends \lithium\core\Object { throw new DispatchException('Attempted to invoke a private method.'); } if (!method_exists($self, $action)) { - throw new DispatchException("Action '{$action}' not found."); + throw new DispatchException("Action `{$action}` not found."); } $render['template'] = $render['template'] ?: $action; @@ -174,7 +183,9 @@ class Controller extends \lithium\core\Object { $self->render(array('text' => $result)); return $self->response; } - $self->set($result); + if (is_array($result)) { + $self->set($result); + } } if (!$render['hasRendered'] && $render['auto']) { @@ -196,8 +207,9 @@ class Controller extends \lithium\core\Object { /** * Uses results (typically coming from a controller action) to generate content and headers for - * a Response object. + * a `Response` object. * + * @see lithium\action\Controller::$_render * @param array $options An array of options, as follows: * - `'data'`: An associative array of variables to be assigned to the template. These * are merged on top of any variables set in `Controller::set()`. @@ -208,28 +220,34 @@ class Controller extends \lithium\core\Object { * controller, i.e. given a `PostsController` object, if template is set to `'view'`, * the template path would be `views/posts/view.html.php`. Defaults to the name of the * action being rendered. + * + * The options specified here are merged with the values in the `Controller::$_render` + * property. You may refer to it for other options accepted by this method. * @return void */ public function render(array $options = array()) { $media = $this->_classes['media']; $class = get_class($this); $name = preg_replace('/Controller$/', '', substr($class, strrpos($class, '\\') + 1)); + $key = key($options); - $defaults = array('status' => null, 'location' => false, 'data' => null, 'head' => false); - $options += $defaults + array('controller' => Inflector::underscore($name)); - - if ($options['data']) { + if (isset($options['data'])) { $this->set($options['data']); unset($options['data']); } - $options = $options + $this->_render; - $type = key($options); - $types = array_flip($media::types()); + $defaults = array( + 'status' => null, + 'location' => false, + 'data' => null, + 'head' => false, + 'controller' => Inflector::underscore($name), + ); + $options += $this->_render + $defaults; - if (isset($types[$type])) { - $options['type'] = $type; - $this->set(current($options)); - unset($options[$type]); + if ($key && $media::type($key)) { + $options['type'] = $key; + $this->set($options[$key]); + unset($options[$key]); } $this->_render['hasRendered'] = true; @@ -246,16 +264,28 @@ class Controller extends \lithium\core\Object { } /** - * Creates a redirect response. + * Creates a redirect response by calling `render()` and providing a `'location'` parameter. * - * @param mixed $url - * @param array $options - * @return void + * @see lithium\net\http\Router::match() + * @see lithium\action\Controller::$response + * @param mixed $url The location to redirect to, provided as a string relative to the root of + * the application, a fully-qualified URL, or an array of routing parameters to be + * resolved to a URL. Post-processed by `Router::match()`. + * @param array $options Options when performing the redirect. Available options include: + * - `'status'` _integer_: The HTTP status code associated with the redirect. + * Defaults to `302`. + * - `'head'` _boolean_: Determines whether only headers are returned with the + * response. Defaults to `true`, in which case only headers and no body are + * returned. Set to `false` to render a body as well. + * - `'exit'` _boolean_: Exit immediately after rendering. Defaults to `false`. + * Because `redirect()` does not exit by default, you should always prefix calls + * with a `return` statement, so that the action is always immedately exited. + * @return object Returns the instance of the `Response` object associated with this controller. * @filter This method can be filtered. */ public function redirect($url, array $options = array()) { $router = $this->_classes['router']; - $defaults = array('location' => null, 'status' => 302, 'head' => true, 'exit' => true); + $defaults = array('location' => null, 'status' => 302, 'head' => true, 'exit' => false); $options += $defaults; $params = compact('url', 'options'); diff --git a/libraries/lithium/action/DispatchException.php b/libraries/lithium/action/DispatchException.php index e17fa47..337bc99 100644 --- a/libraries/lithium/action/DispatchException.php +++ b/libraries/lithium/action/DispatchException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/action/Dispatcher.php b/libraries/lithium/action/Dispatcher.php index 0504cce..96cfdd6 100644 --- a/libraries/lithium/action/Dispatcher.php +++ b/libraries/lithium/action/Dispatcher.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -101,6 +101,7 @@ class Dispatcher extends \lithium\core\StaticObject { * `Dispatcher::_callable()`, which is either a string or an instance of * `lithium\action\Response`. * @todo Add exception-handling/error page rendering + * @filter */ public static function run($request, array $options = array()) { $router = static::$_classes['router']; @@ -116,7 +117,7 @@ class Dispatcher extends \lithium\core\StaticObject { $params = $self::applyRules($result->params); if (!$params) { - throw new DispatchException('Could not route request'); + throw new DispatchException('Could not route request.'); } $callable = $self::invokeMethod('_callable', array($result, $params, $options)); return $self::invokeMethod('_call', array($callable, $result, $params)); @@ -132,18 +133,21 @@ class Dispatcher extends \lithium\core\StaticObject { * @param array $params An array of route parameters to which rules will be applied. * @return array Returns the `$params` array with formatting rules applied to array values. */ - public static function applyRules($params) { + public static function applyRules(&$params) { $result = array(); + $values = array(); if (!$params) { return false; } + if (isset($params['controller']) && is_string($params['controller'])) { $controller = $params['controller']; if (strpos($controller, '.') !== false) { list($library, $controller) = explode('.', $controller); $controller = $library . '.' . Inflector::camelize($controller); + $params += compact('library'); } elseif (strpos($controller, '\\') === false) { $controller = Inflector::camelize($controller); @@ -151,24 +155,24 @@ class Dispatcher extends \lithium\core\StaticObject { $controller = "{$params['library']}.{$controller}"; } } - $params['controller'] = $controller; + $values = compact('controller'); } + $values += $params; foreach (static::$_rules as $rule => $value) { foreach ($value as $k => $v) { - if (!empty($params[$rule])) { - $result[$k] = String::insert($v, $params); + if (isset($values[$rule])) { + $result[$k] = String::insert($v, $values); } - $match = preg_replace('/\{:\w+\}/', '@', $v); $match = preg_replace('/@/', '.+', preg_quote($match, '/')); - if (preg_match('/' . $match . '/i', $params[$k])) { + if (preg_match('/' . $match . '/i', $values[$k])) { return false; } } } - return $result + array_diff_key($params, $result); + return $result + $values; } /** @@ -181,6 +185,7 @@ class Dispatcher extends \lithium\core\StaticObject { * @param array $params The parameter array generated by routing the request. * @param array $options Not currently implemented. * @return object Returns a callable object which the request will be routed to. + * @filter */ protected static function _callable($request, $params, $options) { $params = compact('request', 'params', 'options'); @@ -192,18 +197,32 @@ class Dispatcher extends \lithium\core\StaticObject { try { return Libraries::instance('controllers', $controller, $options); } catch (ClassNotFoundException $e) { - throw new DispatchException("Controller '{$controller}' not found", null, $e); + throw new DispatchException("Controller `{$controller}` not found.", null, $e); } }); } + /** + * Invokes the callable object returned by `_callable()`, and returns the results, usually a + * `Response` object instance. + * + * @see lithium\action + * @param object $callable Typically a closure or instance of `lithium\action\Controller`. + * @param object $request An instance of `lithium\action\Request`. + * @param array $params An array of parameters to pass to `$callable`, along with `$request`. + * @return mixed Returns the return value of `$callable`, usually an instance of + * `lithium\action\Response`. + * @throws lithium\action\DispatchException Throws an exception if `$callable` is not a + * `Closure`, or does not declare the PHP magic `__invoke()` method. + * @filter + */ protected static function _call($callable, $request, $params) { $params = compact('callable', 'request', 'params'); return static::_filter(__FUNCTION__, $params, function($self, $params) { if (is_callable($callable = $params['callable'])) { return $callable($params['request'], $params['params']); } - throw new DispatchException('Result not callable'); + throw new DispatchException('Result not callable.'); }); } } diff --git a/libraries/lithium/action/Request.php b/libraries/lithium/action/Request.php index a8471cd..458a7c9 100644 --- a/libraries/lithium/action/Request.php +++ b/libraries/lithium/action/Request.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -25,7 +25,7 @@ use lithium\util\Validator; * @see lithium\net\http\Route * @see lithium\action\Request::__get() */ -class Request extends \lithium\net\http\Message { +class Request extends \lithium\net\http\Request { /** * Current url of request. @@ -263,7 +263,7 @@ class Request extends \lithium\net\http\Message { * @todo Refactor to lazy-load environment settings */ public function env($key) { - if ($key == 'base') { + if (strtolower($key) == 'base') { return $this->_base; } @@ -316,11 +316,17 @@ class Request extends \lithium\net\http\Message { } /** - * Returns the type of content that the client is requesting. + * Returns information about the type of content that the client is requesting. * - * @see lithium\net\http\Media + * @see lithium\net\http\Media::negotiate() + * @param $type mixed If not specified, returns the media type name that the client prefers, + * using content negotiation. If a media type name (string) is passed, returns + * `true` or `false`, indicating whether or not that type is accepted by the client + * at all. If `true`, returns the raw content types from the `Accept` header, + * parsed into an array and sorted by client preference. * @return string Returns a simple type name if the type is registered (i.e. `'json'`), or - * a fully-qualified content-type if not (i.e. `'image/jpeg'`). + * a fully-qualified content-type if not (i.e. `'image/jpeg'`), or a boolean or array, + * depending on the value of `$type`. */ public function accepts($type = null) { if ($type === true) { @@ -329,18 +335,8 @@ class Request extends \lithium\net\http\Message { if (!$type && isset($this->params['type'])) { return $this->params['type']; } - $accept = $this->_parseAccept(); - - if (!$type && !$accept) { - return 'html'; - } $media = $this->_classes['media']; - - foreach ($accept as $type) { - if ($result = $media::type($type)) { - return $result['content']; - } - } + return $media::negotiate($this) ?: 'html'; } protected function _parseAccept() { @@ -348,28 +344,22 @@ class Request extends \lithium\net\http\Message { return $this->_acceptContent; } $accept = $this->env('HTTP_ACCEPT'); - $accept = (strpos($accept, ',') === false) ? array('text/html') : explode(',', $accept); + $accept = (preg_match('/[a-z,-]/i', $accept)) ? explode(',', $accept) : array('text/html'); - foreach ($accept as $i => $type) { + foreach (array_reverse($accept) as $i => $type) { unset($accept[$i]); - list($type, $q) = (explode(';q=', $type, 2) + array($type, '1.0')); - - if ($type == '*/*') { - $q = 0.1; - } - $accept[$type] = floatval($q); + list($type, $q) = (explode(';q=', $type, 2) + array($type, 1.0 + $i / 100)); + $accept[$type] = ($type == '*/*') ? 0.1 : floatval($q); } arsort($accept, SORT_NUMERIC); - if (isset($accept['text/html']) || isset($accept['application/xhtml+xml'])) { + if (isset($accept['application/xhtml+xml']) && $accept['application/xhtml+xml'] >= 1) { unset($accept['application/xml']); } $media = $this->_classes['media']; - if (isset($this->params['type'])) { - $handler = $media::type($this->params['type']); - - if (isset($handler['options'])) { + if (isset($this->params['type']) && ($handler = $media::type($this->params['type']))) { + if (isset($handler['content'])) { $type = (array) $handler['content']; $accept = array(current($type) => 1) + $accept; } @@ -378,10 +368,31 @@ class Request extends \lithium\net\http\Message { } /** - * Uses a custom prefix syntax to extract specific data points from the request. + * This method allows easy extraction of any request data using a prefixed key syntax. By + * passing keys in the form of `'prefix:key'`, it is possible to query different information of + * various different types, including GET and POST data, and server environment variables. The + * full list of prefixes is as follows: + * + * - `'data'`: Retrieves values from POST data. + * - `'params'`: Retrieves query parameters returned from the routing system. + * - `'query'`: Retrieves values from GET data. + * - `'env'`: Retrieves values from the server or environment, such as `'env:https'`, or custom + * environment values, like `'env:base'`. See the `env()` method for more info. + * - `'http'`: Retrieves header values (i.e. `'http:accept'`), or the HTTP request method (i.e. + * `'http:method'`). * - * @param string $key data:title, env:base - * @return string + * This method is used in several different places in the framework in order to provide the + * ability to act conditionally on different aspects of the request. See `Media::type()` (the + * section on content negotiation) and the routing system for more information. + * + * _Note_: All keys should be _lower-cased_, even when getting HTTP headers. + * @see lithium\action\Request::env() + * @see lithium\net\http\Media::type() + * @see lithium\net\http\Router + * @param string $key A prefixed key indiciating what part of the request data the requested + * value should come from, and the name of the value to retrieve, in lower case. + * @return string Returns the value of a GET, POST, routing or environment variable, or an + * HTTP header or method name. */ public function get($key) { list($var, $key) = explode(':', $key); @@ -390,7 +401,7 @@ class Request extends \lithium\net\http\Message { case in_array($var, array('params', 'data', 'query')): return isset($this->{$var}[$key]) ? $this->{$var}[$key] : null; case ($var === 'env'): - return $this->env($key); + return $this->env(strtoupper($key)); case ($var === 'http' && $key === 'method'): return $this->env('REQUEST_METHOD'); case ($var === 'http'): @@ -444,22 +455,36 @@ class Request extends \lithium\net\http\Message { */ public function type($type = null) { if ($type === null) { - $type = $this->type; - - if (!$type) { - $type = $this->env('CONTENT_TYPE'); - } + $type = $this->type ?: $this->env('CONTENT_TYPE'); } return parent::type($type); } /** - * Creates a 'detector' used with Request::is(). A detector is a boolean check that is created - * to determine something about a request. + * Creates a _detector_ used with `Request::is()`. A detector is a boolean check that is + * created to determine something about a request. + * + * A detector check can be either an exact string match or a regular expression match against a + * header or environment variable. A detector check can also be a closure that accepts the + * `Request` object instance as a parameter. + * + * For example, to detect whether a request is from an iPhone, you can do the following: + * {{{ embed:lithium\tests\cases\action\RequestTest::testDetect(11-12) }}} * * @see lithium\action\Request::is() - * @param string $flag - * @param boolean $detector + * @param string $flag The name of the detector check. Used in subsequent calls to + * `Request::is()`. + * @param mixed $detector Detectors can be specified in four different ways: + * - The name of an HTTP header or environment variable. If a string, calling the + * detector will check that the header or environment variable exists and is set + * to a non-empty value. + * - A two-element array containing a header/environment variable name, and a value + * to match against. The second element of the array must be an exact match to + * the header or variable value. + * - A two-element array containing a header/environment variable name, and a + * regular expression that matches against the value, as in the example above. + * - A closure which accepts an instance of the `Request` object and returns a + * boolean value. * @return void */ public function detect($flag, $detector = null) { @@ -476,9 +501,8 @@ class Request extends \lithium\net\http\Message { * @param string $default Default URL to use if HTTP_REFERER cannot be read from headers. * @param boolean $local If true, restrict referring URLs to local server. * @return string Referring URL. - * @todo Rewrite me to remove constant dependencies. */ - function referer($default = null, $local = false) { + public function referer($default = null, $local = false) { if ($ref = $this->env('HTTP_REFERER')) { if (!$local) { return $ref; @@ -491,6 +515,26 @@ class Request extends \lithium\net\http\Message { } /** + * Overrides `lithium\net\http\Request::to()` to provide the correct options for generating + * URLs. For information about this method, see the parent implementation. + * + * @see lithium\net\http\Request::to() + * @param string $format The format to convert to. + * @param array $options Override options. + * @return mixed The return value type depends on `$format`. + */ + public function to($format, array $options = array()) { + $defaults = array( + 'scheme' => $this->env('HTTPS') ? 'https' : 'http', + 'host' => $this->env('HTTP_HOST'), + 'path' => $this->_base . $this->url, + 'query' => $this->query + ); + $options += $defaults; + return parent::to($format, $options); + } + + /** * @todo Replace string directory names with configuration. * @return void */ diff --git a/libraries/lithium/action/Response.php b/libraries/lithium/action/Response.php index ab9caad..72de487 100644 --- a/libraries/lithium/action/Response.php +++ b/libraries/lithium/action/Response.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -34,12 +34,15 @@ class Response extends \lithium\net\http\Response { 'media' => 'lithium\net\http\Media' ); + protected $_autoConfig = array('classes' => 'merge'); + public function __construct(array $config = array()) { $defaults = array('buffer' => 8192, 'location' => null, 'status' => 0, 'request' => null); parent::__construct($config + $defaults); } protected function _init() { + parent::_init(); $config = $this->_config; $this->status($config['status']); unset($this->_config['status']); @@ -49,7 +52,6 @@ class Response extends \lithium\net\http\Response { $location = $classes['router']::match($config['location'], $config['request']); $this->headers('location', $location); } - parent::_init(); } /** @@ -59,7 +61,7 @@ class Response extends \lithium\net\http\Response { * @deprecated */ public function disableCache() { - $message = 'Request::disableCache() is deprecated. Please use Request::cache(false).'; + $message = '`Request::disableCache()` is deprecated. Please use `Request::cache(false)`.'; throw new BadMethodCallException($message); } @@ -105,10 +107,7 @@ class Response extends \lithium\net\http\Response { if (isset($this->headers['location']) && $this->status['code'] === 200) { $code = 302; } - if (!$status = $this->status($code)) { - throw new UnexpectedValueException('Invalid status code'); - } - $this->_writeHeader($status); + $this->_writeHeader($this->status($code) ?: $this->status(500)); foreach ($this->headers as $name => $value) { $key = strtolower($name); diff --git a/libraries/lithium/action/readme.wiki b/libraries/lithium/action/readme.wiki old mode 100755 new mode 100644 diff --git a/libraries/lithium/analysis/Debugger.php b/libraries/lithium/analysis/Debugger.php index 83cb75a..891f462 100644 --- a/libraries/lithium/analysis/Debugger.php +++ b/libraries/lithium/analysis/Debugger.php @@ -2,13 +2,15 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\analysis; +use ReflectionClass; use lithium\util\String; +use lithium\analysis\Inspector; /** * The `Debugger` class provides basic facilities for generating and rendering meta-data about the @@ -16,6 +18,8 @@ use lithium\util\String; */ class Debugger extends \lithium\core\Object { + protected static $_closureCache = array(); + /** * Outputs a stack trace based on the supplied options. * @@ -38,7 +42,8 @@ class Debugger extends \lithium\core\Object { 'start' => 0, 'scope' => array(), 'trace' => array(), - 'includeScope' => true + 'includeScope' => true, + 'closures' => true, ); $options += $defaults; @@ -68,6 +73,9 @@ class Debugger extends \lithium\core\Object { } } + if ($options['closures'] && strpos($function, '{closure}') !== false) { + $function = static::_closureDef($backtrace[$i], $function); + } if (in_array($function, array('call_user_func_array', 'trigger_error'))) { continue; } @@ -116,6 +124,76 @@ class Debugger extends \lithium\core\Object { } return $export; } + + /** + * Locates original location of closures. + * + * @param mixed $reference File or class name to inspect. + * @param integer $callLine Line number of class reference. + */ + protected static function _definition($reference, $callLine) { + if (file_exists($reference)) { + foreach (array_reverse(token_get_all(file_get_contents($reference))) as $token) { + if (!is_array($token) || $token[2] > $callLine) { + continue; + } + if ($token[0] === T_FUNCTION) { + return $token[2]; + } + } + return; + } + list($class, $method) = explode('::', $reference); + + if (!class_exists($class)) { + return; + } + + $classRef = new ReflectionClass($class); + $methodInfo = Inspector::info($reference); + $methodDef = join("\n", Inspector::lines($classRef->getFileName(), range( + $methodInfo['start'] + 1, $methodInfo['end'] - 1 + ))); + + foreach (array_reverse(token_get_all("<?php {$methodDef} ?>")) as $token) { + if (!is_array($token) || $token[2] > $callLine) { + continue; + } + if ($token[0] === T_FUNCTION) { + return $token[2] + $methodInfo['start']; + } + } + } + + protected static function _closureDef($frame, $function) { + $reference = '::'; + $frame += array('file' => '??', 'line' => '??'); + $cacheKey = "{$frame['file']}@{$frame['line']}"; + + if (isset(static::$_closureCache[$cacheKey])) { + return static::$_closureCache[$cacheKey]; + } + + if ($class = Inspector::classes(array('file' => $frame['file']))) { + foreach (Inspector::methods(key($class), 'extents') as $method => $extents) { + $line = $frame['line']; + + if (!($extents[0] <= $line && $line <= $extents[1])) { + continue; + } + $class = key($class); + $reference = "{$class}::{$method}"; + $function = "{$reference}()::{closure}"; + break; + } + } else { + $reference = $frame['file']; + $function = "{$reference}::{closure}"; + } + $line = static::_definition($reference, $frame['line']) ?: '?'; + $function .= " @ {$line}"; + return static::$_closureCache[$cacheKey] = $function; + } } ?> \ No newline at end of file diff --git a/libraries/lithium/analysis/Docblock.php b/libraries/lithium/analysis/Docblock.php index e93dcb3..6b5ee7f 100644 --- a/libraries/lithium/analysis/Docblock.php +++ b/libraries/lithium/analysis/Docblock.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/analysis/Inspector.php b/libraries/lithium/analysis/Inspector.php index 6684e9f..2f12c19 100644 --- a/libraries/lithium/analysis/Inspector.php +++ b/libraries/lithium/analysis/Inspector.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -332,16 +332,16 @@ class Inspector extends \lithium\core\StaticObject { * @todo Add an $options parameter with a 'context' flag, to pull in n lines of context. */ public static function lines($data, $lines) { - if (!strpos($data, "\n")) { + if (!strpos($data, PHP_EOL)) { if (!file_exists($data)) { $data = Libraries::path($data); if (!file_exists($data)) { return null; } } - $data = "\n" . file_get_contents($data); + $data = PHP_EOL . file_get_contents($data); } - $c = explode("\n", $data); + $c = explode(PHP_EOL, $data); if (!count($c) || !count($lines)) { return null; @@ -383,18 +383,22 @@ class Inspector extends \lithium\core\StaticObject { $options += $defaults; $list = get_declared_classes(); + $files = get_included_files(); $classes = array(); - if (!empty($options['file'])) { + if ($file = $options['file']) { $loaded = static::_instance('collection', array('data' => array_map( function($class) { return new ReflectionClass($class); }, $list ))); + $classFiles = $loaded->getFileName(); - if (!in_array($options['file'], $loaded->getFileName())) { - include $options['file']; + if (in_array($file, $files) && !in_array($file, $classFiles)) { + return array(); + } + if (!in_array($file, $classFiles)) { + include $file; $list = array_diff(get_declared_classes(), $list); } else { - $file = $options['file']; $filter = function($class) use ($file) { return $class->getFileName() == $file; }; $list = $loaded->find($filter)->getName(); } @@ -468,7 +472,7 @@ class Inspector extends \lithium\core\StaticObject { */ protected static function _class($class) { if (!class_exists($class)) { - throw new RuntimeException(sprintf('Class "%s" could not be found.', $class)); + throw new RuntimeException(sprintf('Class `%s` could not be found.', $class)); } return unserialize(sprintf('O:%d:"%s":0:{}', strlen($class), $class)); } diff --git a/libraries/lithium/analysis/Logger.php b/libraries/lithium/analysis/Logger.php index c4c0613..affac19 100644 --- a/libraries/lithium/analysis/Logger.php +++ b/libraries/lithium/analysis/Logger.php @@ -2,13 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\analysis; -use \UnexpectedValueException; +use UnexpectedValueException; /** * The `Logger` class provides a consistent, application-wide interface for configuring and writing @@ -65,6 +65,11 @@ class Logger extends \lithium\core\Adaptable { */ protected static $_adapters = 'adapter.analysis.logger'; + /** + * An array of valid message priorities. + * + * @var array + */ protected static $_priorities = array( 'emergency' => 0, 'alert' => 1, @@ -91,6 +96,7 @@ class Logger extends \lithium\core\Adaptable { * failed. * @throws UnexpectedValueException If the value of `$priority` is not a defined priority value, * an `UnexpectedValueException` will be thrown. + * @filter */ public static function write($priority, $message, array $options = array()) { $defaults = array('name' => null); @@ -100,7 +106,7 @@ class Logger extends \lithium\core\Adaptable { if ($name = $options['name']) { $methods = array($name => static::adapter($name)->write($priority, $message, $options)); } elseif (!isset(static::$_priorities[$priority])) { - $message = "Attempted to write log message with invalid priority '{$priority}'."; + $message = "Attempted to write log message with invalid priority `{$priority}`."; throw new UnexpectedValueException($message); } else { $methods = static::_configsByPriority($priority, $message, $options); @@ -109,7 +115,7 @@ class Logger extends \lithium\core\Adaptable { foreach ($methods as $name => $method) { $params = compact('priority', 'message', 'options'); $config = static::_config($name); - $result = $result && static::_filter(__METHOD__, $params, $method, $config['filters']); + $result &= static::_filter(__FUNCTION__, $params, $method, $config['filters']); } return $methods ? $result : false; } diff --git a/libraries/lithium/analysis/Parser.php b/libraries/lithium/analysis/Parser.php index a6740b7..4969289 100644 --- a/libraries/lithium/analysis/Parser.php +++ b/libraries/lithium/analysis/Parser.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/analysis/logger/adapter/Cache.php b/libraries/lithium/analysis/logger/adapter/Cache.php old mode 100755 new mode 100644 index 077897d..9ea8c9c --- a/libraries/lithium/analysis/logger/adapter/Cache.php +++ b/libraries/lithium/analysis/logger/adapter/Cache.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -16,7 +16,7 @@ use lithium\util\String; * for it to write to, as follows: * * {{{ lithium\storage\Cache::config(array( - * 'storage' => array('adapter' => 'Redis', 'server' => '127.0.0.1:6379') + * 'storage' => array('adapter' => 'Redis', 'host' => '127.0.0.1:6379') * ));}}} * * Then, you can configure the `Cache` logger with the `'storage'` config: diff --git a/libraries/lithium/analysis/logger/adapter/File.php b/libraries/lithium/analysis/logger/adapter/File.php old mode 100755 new mode 100644 index 41b0bf0..ca9ecd7 --- a/libraries/lithium/analysis/logger/adapter/File.php +++ b/libraries/lithium/analysis/logger/adapter/File.php @@ -2,65 +2,81 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\analysis\logger\adapter; -use \SplFileInfo; -use \DirectoryIterator; +use lithium\util\String; +use lithium\core\Libraries; /** * A simple log adapter that writes messages to files. By default, messages are written to * `app/resources/tmp/logs/<type>.log`, where `<type>` is the log message priority level. * * {{{ - * lithium\analysis\Logger::config(array( - * 'debug' => array('adapter' => 'File') + * use lithium\analysis\Logger; + * + * Logger::config(array( + * 'simple' => array('adapter' => 'File') * )); - * lithium\analysis\Logger::write('debug', 'Something happened!'); + * Logger::write('debug', 'Something happened!'); * }}} * * This will cause the message and the timestamp of the log event to be written to - * `app/resources/tmp/logs/debug.log`. + * `app/resources/tmp/logs/debug.log`. For available configuration options for this adapter, see + * the `__construct()` method. + * + * @see lithium\analysis\logger\adapter\File::__construct() */ class File extends \lithium\core\Object { /** * Class constructor. * + * @see lithium\util\String::insert() * @param array $config Settings used to configure the adapter. Available options: * - `'path'` _string_: The directory to write log files to. Defaults to - * `app/resources/tmp/logs`. - * - `'timestamp'` _string_: The `date()`-compatible format of the timetstamp, or - * `false` to disable timestamps. Defaults to `'Y-m-d H:i:s'`. + * `<app>/resources/tmp/logs`. + * - `'timestamp'` _string_: The `date()`-compatible timestamp format. Defaults to + * `'Y-m-d H:i:s'`. + * - `'file'` _closure_: A closure which accepts two parameters: an array + * containing the current log message details, and an array containing the `File` + * adapter's current configuration. It must then return a file name to write the + * log message to. The default will produce a log file name corresponding to the + * priority of the log message, i.e. `"debug.log"` or `"alert.log"`. + * - `'format'` _string_: A `String::insert()`-compatible string that specifies how + * the log message should be formatted. The default format is + * `"{:timestamp} {:message}\n"`. * @return void */ public function __construct(array $config = array()) { $defaults = array( - 'path' => LITHIUM_APP_PATH . '/resources/tmp/logs', + 'path' => Libraries::get(true, 'resources') . '/tmp/logs', 'timestamp' => 'Y-m-d H:i:s', + 'file' => function($data, $config) { return "{$data['priority']}.log"; }, + 'format' => "{:timestamp} {:message}\n", ); parent::__construct($config + $defaults); } /** - * Appends $data to file $type. + * Appends a message to a log file. * - * @param string $type - * @param string $message - * @return boolean `True` on successful write, `false` otherwise. + * @see lithium\analysis\Logger::$_priorities + * @param string $priority The message priority. See `Logger::$_priorities`. + * @param string $message The message to write to the log. + * @return closure Returns a closure that writes to the log, which can be wrapped in filters. */ - public function write($type, $message) { + public function write($priority, $message) { $config = $this->_config; return function($self, $params) use (&$config) { - $type = $params['priority']; - $message = $params['message']; - $time = $config['timestamp'] ? date($config['timestamp']) . ' ' : ''; - $path = $config['path']; - return file_put_contents("{$path}/{$type}.log", "{$time}{$message}\n", FILE_APPEND); + $path = $config['path'] . '/' . $config['file']($params, $config); + $params['timestamp'] = date($config['timestamp']); + $message = String::insert($config['format'], $params); + return file_put_contents($path, $message, FILE_APPEND); }; } } diff --git a/libraries/lithium/analysis/logger/adapter/Growl.php b/libraries/lithium/analysis/logger/adapter/Growl.php old mode 100755 new mode 100644 index 335fd50..0df88fb --- a/libraries/lithium/analysis/logger/adapter/Growl.php +++ b/libraries/lithium/analysis/logger/adapter/Growl.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -90,7 +90,7 @@ class Growl extends \lithium\core\Object { if ($conn = fsockopen($host, $port, $message, $code)) { return $conn; } - throw new NetworkException("Growl connection failed: ({$code}) {$message}"); + throw new NetworkException("Growl connection failed: (`{$code}`) `{$message}`."); } ); parent::__construct($config + $defaults); diff --git a/libraries/lithium/analysis/logger/adapter/Syslog.php b/libraries/lithium/analysis/logger/adapter/Syslog.php old mode 100755 new mode 100644 index d564a5d..870f106 --- a/libraries/lithium/analysis/logger/adapter/Syslog.php +++ b/libraries/lithium/analysis/logger/adapter/Syslog.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/console/Command.php b/libraries/lithium/console/Command.php index 9fb4bb6..0e3c65a 100644 --- a/libraries/lithium/console/Command.php +++ b/libraries/lithium/console/Command.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -62,12 +62,9 @@ class Command extends \lithium\core\Object { * @param array $config * @return void */ - public function __construct($config = array()) { - $defaults = array( - 'request' => null, 'response' => array(), 'classes' => $this->_classes - ); - $config += $defaults; - parent::__construct($config); + public function __construct(array $config = array()) { + $defaults = array('request' => null, 'response' => array(), 'classes' => $this->_classes); + parent::__construct($config + $defaults); } /** @@ -81,20 +78,22 @@ class Command extends \lithium\core\Object { parent::_init(); $this->request = $this->_config['request']; - $this->response = $this->_instance('response', $this->_config['response']); - - if (!empty($this->request->params)) { - $params = (array) array_diff_key( - $this->request->params, array('command' => null, 'action' => null, 'args' => null) - ); - foreach ($params as $key => $param) { - $this->{$key} = $param; - } + $resp = $this->_config['response']; + $this->response = is_object($resp) ? $resp : $this->_instance('response', $resp); + + if (!is_object($this->request) || !$this->request->params) { + return; + } + $default = array('command' => null, 'action' => null, 'args' => null); + $params = array_diff_key((array) $this->request->params, $default); + + foreach ($params as $key => $param) { + $this->{$key} = $param; } } /** - * Called by the Dispatcher class to invoke an action. + * Called by the `Dispatcher` class to invoke an action. * * @see lithium\console\Dispatcher * @see lithium\console\Response @@ -102,7 +101,6 @@ class Command extends \lithium\core\Object { * @param array $args the args from the request * @param array $options * @return object The response object associated with this command. - * @todo Implement proper exception catching/throwing. * @todo Implement filters. */ public function __invoke($action, $args = array(), $options = array()) { @@ -117,12 +115,32 @@ class Command extends \lithium\core\Object { } } catch (Exception $e) { $this->error($e->getMessage()); - $this->response->status = 1; } return $this->response; } /** + * This is the main method any subclass is recommended to implement. This + * method is called when the command is invoked without any arguments as + * shown in the examples below. When not overridden help for the + * subclassing command is generated by default. + * + * {{{ + * $ li3 example + * $ li3 example --format=json + * }}} + * + * The invoked Help command will take over request and response objects of + * the originally invoked command. Thus the response of the Help command + * becomes the response of the original one. + * + * @return boolean + */ + public function run() { + return $this->_help(); + } + + /** * Writes string to output stream. * * @param string $output @@ -201,7 +219,7 @@ class Command extends \lithium\core\Object { */ public function header($text, $line = 80) { $this->hr($line); - $this->out($text, 1, 'heading1'); + $this->out($text, 1, 'heading'); $this->hr($line); } @@ -259,7 +277,7 @@ class Command extends \lithium\core\Object { * @return void */ public function clear() { - passthru(substr(PHP_OS, 0, 3) == 'WIN' ? 'cls' : 'clear'); + passthru(strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? 'cls' : 'clear'); } /** @@ -281,15 +299,17 @@ class Command extends \lithium\core\Object { } /** - * Show help generated from the documented code of the command. + * Invokes `Help` command. * * @return boolean */ protected function _help() { - $help = new Help($this->_config); - $result = $help->run(get_class($this)); - $this->response = $help->response; - return $result; + $help = new Help(array( + 'request' => $this->request, + 'response' => $this->response, + 'classes' => $this->_classes + )); + return $help->run(get_class($this)); } /** diff --git a/libraries/lithium/console/Dispatcher.php b/libraries/lithium/console/Dispatcher.php old mode 100755 new mode 100644 index 24152e3..9ddcb9e --- a/libraries/lithium/console/Dispatcher.php +++ b/libraries/lithium/console/Dispatcher.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -40,11 +40,6 @@ class Dispatcher extends \lithium\core\StaticObject { * elements array elements of the flag which are present in the route will be modified using a * `lithium\util\String::insert()`-formatted string. * - * For example, to implement action prefixes (i.e. `admin_index()`), set a rule named 'admin', - * with a value array containing a modifier key for the `action` element of a route, i.e.: - * `array('action' => 'admin_{:action}')`. See `lithium\console\Dispatcher::config()` for - * examples on setting rules. - * * @see lithium\console\Dispatcher::config() * @see lithium\util\String::insert() */ @@ -76,11 +71,11 @@ class Dispatcher extends \lithium\core\StaticObject { * If `$request` is `null`, a new request object is instantiated based on the value of the * `'request'` key in the `$_classes` array. * - * @param object $request An instance of a request object with HTTP request information. If + * @param object $request An instance of a request object with console request information. If * `null`, an instance will be created. * @param array $options * @return object The command action result which is an instance of `lithium\console\Response`. - * @todo Add exception-handling/error page rendering + * @filter */ public static function run($request = null, $options = array()) { $defaults = array('request' => array()); @@ -115,6 +110,7 @@ class Dispatcher extends \lithium\core\StaticObject { * @param string $params * @param string $options * @return class lithium\console\Command + * @filter */ protected static function _callable($request, $params, $options) { $params = compact('request', 'params', 'options'); @@ -130,7 +126,7 @@ class Dispatcher extends \lithium\core\StaticObject { if (class_exists($class = Libraries::locate('command', $name))) { return new $class(compact('request')); } - throw new UnexpectedValueException("Command `{$name}` not found"); + throw new UnexpectedValueException("Command `{$name}` not found."); }); } @@ -170,6 +166,7 @@ class Dispatcher extends \lithium\core\StaticObject { * @param string $request * @param string $params * @return void + * @filter */ protected static function _call($callable, $request, $params) { $params = compact('callable', 'request', 'params'); @@ -191,7 +188,7 @@ class Dispatcher extends \lithium\core\StaticObject { } return $callable($params['action'], $params['args']); } - throw new UnexpectedValueException("{$callable} not callable"); + throw new UnexpectedValueException("Callable `{$callable}` is actually not callable."); }); } } diff --git a/libraries/lithium/console/Request.php b/libraries/lithium/console/Request.php index 2886776..7c3acbf 100644 --- a/libraries/lithium/console/Request.php +++ b/libraries/lithium/console/Request.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -44,7 +44,7 @@ class Request extends \lithium\core\Object { * Enviroment variables. * * @var array - **/ + */ protected $_env = array(); /** diff --git a/libraries/lithium/console/Response.php b/libraries/lithium/console/Response.php index eeb1b6b..cfd9b0e 100644 --- a/libraries/lithium/console/Response.php +++ b/libraries/lithium/console/Response.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -21,14 +21,14 @@ class Response extends \lithium\core\Object { * Output stream, STDOUT * * @var stream - **/ + */ public $output = null; /** * Error stream, STDERR * * @var stream - **/ + */ public $error = null; /** @@ -94,7 +94,7 @@ class Response extends \lithium\core\Object { * * @return void * - **/ + */ public function __destruct() { if ($this->output) { fclose($this->output); @@ -112,13 +112,11 @@ class Response extends \lithium\core\Object { */ public function styles($styles = array()) { $defaults = array( - 'heading1' => "\033[1;30;46m", - 'heading2' => "\033[1;35m", - 'heading3' => "\033[1;34m", - 'option' => "\033[40;37m", - 'command' => "\033[1;40;37m", - 'error' => "\033[0;31m", - 'success' => "\033[0;32m", + 'heading' => "\033[1;36m", + 'option' => "\033[0;35m", + 'command' => "\033[0;35m", + 'error' => "\033[0;31m", + 'success' => "\033[0;32m", 'black' => "\033[0;30m", 'red' => "\033[0;31m", 'green' => "\033[0;32m", diff --git a/libraries/lithium/console/Router.php b/libraries/lithium/console/Router.php index 398410c..a0bd7c9 100644 --- a/libraries/lithium/console/Router.php +++ b/libraries/lithium/console/Router.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -21,23 +21,16 @@ class Router extends \lithium\core\Object { * @param object $request lithium\console\Request * @return array $params * - **/ + */ public static function parse($request = null) { - $params = array( - 'command' => null, 'action' => 'run', 'args' => array() - ); - if (!empty($request->params)) { - $params = $request->params + $params; - } + $defaults = array('command' => null, 'action' => 'run', 'args' => array()); + $params = $request ? (array) $request->params + $defaults : $defaults; if (!empty($request->argv)) { $args = $request->argv; - if (empty($params['command'])) { - $params['command'] = array_shift($args); - } - while ($arg = array_shift($args)) { - if (preg_match('/^-(?P<key>[a-zA-Z0-9]+)$/i', $arg, $match)) { + while ($arg = array_shift($args)) { + if (preg_match('/^-(?P<key>[a-zA-Z0-9])$/i', $arg, $match)) { $params[$match['key']] = true; continue; } @@ -48,9 +41,10 @@ class Router extends \lithium\core\Object { $params['args'][] = $arg; } } - - if (!empty($params['args'])) { - $params['action'] = array_shift($params['args']); + foreach (array('command', 'action') as $param) { + if (!empty($params['args'])) { + $params[$param] = array_shift($params['args']); + } } return $params; } diff --git a/libraries/lithium/console/command/Create.php b/libraries/lithium/console/command/Create.php index af1079f..7e9f37f 100644 --- a/libraries/lithium/console/command/Create.php +++ b/libraries/lithium/console/command/Create.php @@ -2,22 +2,23 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\console\command; +use lithium\util\String; use lithium\core\Libraries; use lithium\util\Inflector; -use lithium\util\String; +use lithium\core\ClassNotFoundException; /** * The `create` command allows you to rapidly develop your models, views, controllers, and tests * by generating the minimum code necessary to test and run your application. * * `li3 create --template=controller Posts` - * `li3 create --template=model Post` + * `li3 create --template=model Posts` * */ class Create extends \lithium\console\Command { @@ -112,7 +113,11 @@ class Create extends \lithium\console\Command { * @return void */ protected function _execute($command) { - if (!$class = $this->_instance($command)) { + try { + if (!$class = $this->_instance($command)) { + return false; + } + } catch (ClassNotFoundException $e) { return false; } $data = array(); @@ -137,9 +142,9 @@ class Create extends \lithium\console\Command { */ protected function _default($name) { $commands = array( - array('model', Inflector::classify($name)), + array('model', Inflector::pluralize($name)), array('controller', Inflector::pluralize($name)), - array('test', 'model', Inflector::classify($name)), + array('test', 'model', Inflector::pluralize($name)), array('test', 'controller', Inflector::pluralize($name)) ); foreach ($commands as $args) { diff --git a/libraries/lithium/console/command/G11n.php b/libraries/lithium/console/command/G11n.php index 907964c..a3fb1f8 100644 --- a/libraries/lithium/console/command/G11n.php +++ b/libraries/lithium/console/command/G11n.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/console/command/Help.php b/libraries/lithium/console/command/Help.php index b3387bb..c47baef 100644 --- a/libraries/lithium/console/command/Help.php +++ b/libraries/lithium/console/command/Help.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -14,81 +14,76 @@ use lithium\analysis\Inspector; use lithium\analysis\Docblock; /** - * Get information about a particular class including methods, properties, and descriptions. - * + * Get information about a particular class including methods, properties, + * and descriptions. */ class Help extends \lithium\console\Command { /** - * Auto run the help command + * Auto run the help command. * - * @param string $name COMMAND to get help + * @param string $command Name of the command to return help about. * @return void */ - public function run($name = null) { - if (!$name) { - $this->out('COMMANDS', 'heading1', 2); - $commands = Libraries::locate('command', null, array('recursive' => false)); - - foreach ($commands as $command) { - $info = Inspector::info($command); - $name = strtolower(Inflector::slug($info['shortName'])); - $this->out($this->_pad($name), 'heading2'); - $this->out($this->_pad($info['description']), 2); - } - $message = 'See `{:command}li3 help COMMAND{:end}`'; - $message .= ' for more information on a specific command.'; - $this->out($message, 2); + public function run($command = null) { + if (!$command) { + $this->_renderCommands(); return true; } - $name = Inflector::classify($name); - - if (!$class = Libraries::locate('command', $name)) { - $this->error("{$name} not found"); + if (!$class = Libraries::locate('command', $command)) { + $this->error("Command `{$command}` not found"); return false; } - if (strpos($name, '\\') !== false) { - $name = join('', array_slice(explode("\\", $name), -1)); + $command = Inflector::classify($command); + + if (strpos($command, '\\') !== false) { + $command = join('', array_slice(explode("\\", $command), -1)); } - $name = strtolower(Inflector::slug($name)); + $command = strtolower(Inflector::slug($command)); + + $run = null; $methods = $this->_methods($class); $properties = $this->_properties($class); - - $this->out('USAGE', 'heading1'); - $this->out($this->_pad(sprintf("{:command}li3 %s{:end}{:option}%s{:end} [ARGS]", - $name ?: 'COMMAND', - array_reduce($properties, function($a, $b) { - return "{$a} {$b['usage']}"; - }) - ))); $info = Inspector::info($class); + $this->out('USAGE', 'heading'); + + if (isset($methods['run'])) { + $run = $methods['run']; + unset($methods['run']); + $this->_renderUsage($command, $run, $properties); + } + foreach ($methods as $method) { + $this->_renderUsage($command, $method); + } + if (!empty($info['description'])) { $this->nl(); - $this->out('DESCRIPTION'); - $this->out($this->_pad(strtok($info['description'], "\n"), 1)); + $this->_renderDescription($info); $this->nl(); } if ($properties || $methods) { - $this->out('OPTIONS', 'heading2'); + $this->out('OPTIONS', 'heading'); } - - if ($properties) { - $this->_render($properties); + if ($run) { + $this->_render($run['args']); } if ($methods) { $this->_render($methods); } + if ($properties) { + $this->_render($properties); + } return true; } /** * Get the api for the class. * - * @param string $class fully namespaced class in dot notation + * @param string $class fully namespaced class in dot notation. * @param string $type method|property - * @param string $name the name of the method or property + * @param string $name the name of the method or property. * @return array */ public function api($class = null, $type = null, $name = null) { @@ -113,7 +108,7 @@ class Help extends \lithium\console\Command { } /** - * Get the methods for the class + * Get the methods for the class. * * @param string $class * @param array $options @@ -122,46 +117,58 @@ class Help extends \lithium\console\Command { protected function _methods($class, $options = array()) { $defaults = array('name' => null); $options += $defaults; - $methods = Inspector::methods($class)->map( - function($item) { - if ($item->name[0] === '_') { - return; - } - $modifiers = array_values(Inspector::invokeMethod('_modifiers', array($item))); - $setAccess = ( - array_intersect($modifiers, array('private', 'protected')) != array() - ); - if ($setAccess) { - $item->setAccessible(true); - } - $result = compact('modifiers') + array( - 'docComment' => $item->getDocComment(), - 'name' => $item->getName(), + $map = function($item) { + if ($item->name[0] === '_') { + return; + } + $modifiers = array_values(Inspector::invokeMethod('_modifiers', array($item))); + $setAccess = array_intersect($modifiers, array('private', 'protected')) != array(); + + if ($setAccess) { + $item->setAccessible(true); + } + $args = array(); + + foreach ($item->getParameters() as $arg) { + $args[] = array( + 'name' => $arg->getName(), + 'optional' => $arg->isOptional(), + 'description' => null ); - if ($setAccess) { - $item->setAccessible(false); - } - return $result; - }, - array('collect' => false) - ); + } + $result = compact('modifiers', 'args') + array( + 'docComment' => $item->getDocComment(), + 'name' => $item->getName() + ); + if ($setAccess) { + $item->setAccessible(false); + } + return $result; + }; + + $methods = Inspector::methods($class)->map($map, array('collect' => false)); $results = array(); + foreach ($methods as $method) { $comment = Docblock::comment($method['docComment']); + $name = $method['name']; $description = $comment['description']; - $args = !isset($comment['tags']['params']) ? null : $comment['tags']['params']; - $return = !isset($comment['tags']['return']) ? null : - trim(strtok($comment['tags']['return'], ' ')); - $command = $name === 'run' ? null : $name; - $command = !$command && !empty($args) ? '[ARGS]' : $command; - $usage = "{$command} "; - $usage .= empty($args) ? null : join(' ', array_map(function ($a) { - return '[' . str_replace('$', '', trim($a)) . ']'; - }, array_keys($args))); - - $results[$name] = compact('name', 'description', 'return', 'args', 'usage'); + $args = $method['args']; + + $return = null; + + foreach ($args as &$arg) { + if (isset($comment['tags']['params']['$' . $arg['name']])) { + $arg['description'] = $comment['tags']['params']['$' . $arg['name']]['text']; + } + $arg['usage'] = $arg['optional'] ? "[<{$arg['name']}>]" : "<{$arg['name']}>"; + } + if (isset($comment['tags']['return'])) { + $return = trim(strtok($comment['tags']['return'], ' ')); + } + $results[$name] = compact('name', 'description', 'return', 'args'); if ($name && $name == $options['name']) { return array($name => $results[$name]); @@ -171,7 +178,7 @@ class Help extends \lithium\console\Command { } /** - * Get the properties for the class + * Get the properties for the class. * * @param string $class * @param array $options @@ -180,15 +187,24 @@ class Help extends \lithium\console\Command { protected function _properties($class, $options = array()) { $defaults = array('name' => null); $options += $defaults; + $properties = Inspector::properties($class); $results = array(); foreach ($properties as &$property) { + $name = str_replace('_', '-', Inflector::underscore($property['name'])); + $comment = Docblock::comment($property['docComment']); $description = trim($comment['description']); $type = isset($comment['tags']['var']) ? strtok($comment['tags']['var'], ' ') : null; - $name = str_replace('_', '-', Inflector::underscore($property['name'])); - $usage = $type == 'boolean' ? "-{$name}" : "--{$name}=" . strtoupper($name); + + $usage = strlen($name) == 1 ? "-{$name}" : "--{$name}"; + + if ($type != 'boolean') { + $usage .= "=<{$type}>"; + } + $usage = "[{$usage}]"; + $results[$name] = compact('name', 'description', 'type', 'usage'); if ($name == $options['name']) { @@ -201,7 +217,9 @@ class Help extends \lithium\console\Command { /** * Output the formatted properties or methods. * - * @param array $params from _properties|_methods + * @see lithium\console\command\Help::_properties() + * @see lithium\console\command\Help::_methods() + * @param array $params From `_properties()` or `_methods()`. * @return void */ protected function _render($params) { @@ -209,17 +227,9 @@ class Help extends \lithium\console\Command { if ($name === 'run' || empty($param['name'])) { continue; } - $usage = (!empty($param['usage'])) ? trim($param['usage']) : $param['name']; + $usage = (!empty($param['usage'])) ? trim($param['usage'], ' []') : $param['name']; $this->out($this->_pad($usage), 'option'); - if (!empty($param['args'])) { - $args = array(); - foreach ((array) $param['args'] as $arg => $desc) { - $arg = str_replace('$', '', trim($arg)); - $args[] = $this->_pad("{$arg}: {$desc['text']}", 2); - } - $this->out($args); - } if ($param['description']) { $this->out($this->_pad($param['description'], 2)); } @@ -228,10 +238,73 @@ class Help extends \lithium\console\Command { } /** + * Output the formatted available commands. + * + * @return void + */ + protected function _renderCommands() { + $commands = Libraries::locate('command', null, array('recursive' => false)); + + foreach ($commands as $key => $command) { + $library = strtok($command, '\\'); + + if (!$key || strtok($commands[$key - 1] , '\\') != $library) { + $this->out("{:heading}COMMANDS{:end} {:blue}via {$library}{:end}"); + } + $info = Inspector::info($command); + $name = strtolower(Inflector::slug($info['shortName'])); + + $this->out($this->_pad($name) , 'heading'); + $this->out($this->_pad($info['description']), 2); + } + + $message = 'See `{:command}li3 help COMMAND{:end}`'; + $message .= ' for more information on a specific command.'; + $this->out($message, 2); + } + + /** + * Output the formatted usage. + * + * @see lithium\console\command\Help::_methods() + * @see lithium\console\command\Help::_properties() + * @param string $command The name of the command. + * @param array $method Information about the method of the command to render usage for. + * @param array $properties From `_properties()`. + * @return void + */ + protected function _renderUsage($command, $method, $properties = array()) { + $params = array_reduce($properties, function($a, $b) { + return "{$a} {$b['usage']}"; + }); + $args = array_reduce($method['args'], function($a, $b) { + return "{$a} {$b['usage']}"; + }); + $this->out($this->_pad(sprintf( + "{:command}li3 %s%s{:end}{:command}%s{:end}{:option}%s{:end}", + $command ?: 'COMMAND', + $method['name'] == 'run' ? '' : " {$method['name']}", + $params, + $args + ))); + } + + /** + * Output the formatted command description. + * + * @param array $info Info from insepcting the class of the command. + * @return void + */ + protected function _renderDescription($info) { + $this->out('DESCRIPTION', 'heading'); + $this->out($this->_pad(strtok($info['description'], "\n"), 1)); + } + + /** * Add left padding for prettier display. * - * @param string $message the text to render - * @param string $level the level of indentation + * @param string $message the text to render. + * @param string $level the level of indentation. * @return void */ protected function _pad($message, $level = 1) { diff --git a/libraries/lithium/console/command/Library.php b/libraries/lithium/console/command/Library.php index 1f9f47d..da4dbbe 100644 --- a/libraries/lithium/console/command/Library.php +++ b/libraries/lithium/console/command/Library.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -214,7 +214,7 @@ class Library extends \lithium\console\Command { */ public function archive($name = null, $result = null) { if (ini_get('phar.readonly') == '1') { - throw new RuntimeException('set phar.readonly = 0 in php.ini'); + throw new RuntimeException('Set `phar.readonly` to `0` in `php.ini`.'); } $from = $name; $to = $name; diff --git a/libraries/lithium/console/command/Route.php b/libraries/lithium/console/command/Route.php new file mode 100644 index 0000000..86fd586 --- /dev/null +++ b/libraries/lithium/console/command/Route.php @@ -0,0 +1,139 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ +namespace lithium\console\command; + +use lithium\net\http\Router; +use lithium\action\Request; +use lithium\core\Environment; + +/** + * The route command lets you inspect your routes and issue requests against the router. + */ +class Route extends \lithium\console\Command { + + /** + * Override the default 'development' environment. + * + * For example: + * {{{ + * li3 route --env=production + * li3 route show /foo --env=test + * }}} + * + * @var string + */ + public $env = 'development'; + + /** + * Load the routes file and set the environment. + * + * @param array $config The default configuration, wherein the absolute path to the routes file + * to load may be specified, using the `'routes_file'` key. + */ + public function __construct($config = array()) { + $defaults = array('routes_file' => LITHIUM_APP_PATH . '/config/routes.php'); + parent::__construct($config + $defaults); + } + + protected function _init() { + parent::_init(); + Environment::set($this->env); + + if (file_exists($this->_config['routes_file'])) { + return require $this->_config['routes_file']; + } + $this->error("The routes file for this library doesn't exist or can't be found."); + } + + /** + * Lists all connected routes to the router. See the `all()` + * method for details and examples. + * + * @return void + */ + public function run() { + $this->all(); + } + + /** + * Lists all connected routes to the router. This is a convenience + * alias for the `show()` method. + * + * Example: + * {{{ + * li3 route + * li3 route all + * }}} + * + * Will return an output similar to: + * + * {{{ + * Template Params + * -------- ------ + * / {"controller":"pages","action":"view"} + * /pages/{:args} {"controller":"pages","action":"view"} + * /{:slug:[\w\-]+} {"controller":"posts","action":"show"} + * /{:controller}/{:action}/{:args} {"action":"index"} + * }}} + * + * @return void + */ + public function all() { + $routes = Router::get(); + $columns = array(array('Template', 'Params'), array('--------', '------')); + + foreach ($routes As $route) { + $info = $route->export(); + $columns[] = array($info['template'], json_encode($info['params'])); + } + $this->columns($columns); + } + + /** + * Returns the corresponding params for a given URL and an optional request + * method. + * + * Examples: + * {{{ + * 1: li3 route show /foo + * 2: li3 route show post /foo/bar/1 + * 3: li3 route show /test + * 4: li3 route show /test --env=production + * }}} + * + * Will return outputs similar to: + * + * {{{ + * 1: {"controller":"foo","action":"index" } + * 2: {"controller":"foo","action":"bar","args":["1"]} + * 3: {"controller":"lithium\\test\\Controller","action":"index"} + * 4: {"controller":"test","action":"index"} + * }}} + * + * @return void + */ + public function show() { + $url = join(" ", $this->request->params['args']); + $method = 'GET'; + + if (!$url) { + $this->error('Please provide a valid URL'); + } + + if (preg_match('/^(GET|POST|PUT|DELETE|HEAD|OPTIONS) (.+)/i', $url, $matches)) { + $method = strtoupper($matches[1]); + $url = $matches[2]; + } + + $request = new Request(compact('url') + array('env' => array('REQUEST_METHOD' => $method))); + $result = Router::process($request); + $this->out($result->params ? json_encode($result->params) : "No route found."); + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/console/command/Test.php b/libraries/lithium/console/command/Test.php index 21b0848..63c7a18 100644 --- a/libraries/lithium/console/command/Test.php +++ b/libraries/lithium/console/command/Test.php @@ -2,163 +2,126 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\console\command; use lithium\core\Libraries; -use lithium\test\Group; use lithium\test\Dispatcher; -use lithium\analysis\Inspector; /** * Runs a given set of tests and outputs the results. + * + * @see lithium\test */ class Test extends \lithium\console\Command { /** - * Path to the test case in dot notation. + * Filters. * * For example: * {{{ - * lithium test --case=console.CommandTest + * lithium test lithium/tests/cases/core/ObjectTest.php --filters=Coverage + * lithium test lithium/tests/cases/core/ObjectTest.php --filters=Coverage,Profiler * }}} * - * @var string + * @var string Name of a filter or a comma separated list of filter names. Builtin filters: + * - `Affected`: Adds tests to the run affected by the classes covered by current tests. + * - `Complexity`: Calculates the cyclomatic complexity of class methods, and shows + * worst-offenders and statistics. + * - `Coverage`: Runs code coverage analysis for the executed tests. + * - `Profiler`: Tracks timing and memory usage information for each test method. */ - public $case = null; + public $filters; /** - * Path to test group in dot notation. - * - * For example: - * {{{ - * lithium test --group=lithium.tests.cases.console - * }}} + * Format to use for rendering results. Any other format than `txt` will + * cause the command to enter quiet mode, surpressing headers and any other + * decoration. * - * @var string + * @var string Either `txt` or `json`. */ - public $group = null; + public $format = 'txt'; /** - * Filters. + * Runs tests given a path to a directory or file containing tests. The path to the + * test(s) may be absolte or relative to the current working directory. * - * For example: * {{{ - * lithium test --case=lithium.tests.cases.core.ObjectTest --filters=Coverage + * lithium test lithium/tests/cases/core/ObjectTest.php + * lithium test lithium/tests/cases/core * }}} * - * @var string + * @param string $path Absolute or relative path to tests. + * @return boolean Will exit with status `1` if one or more tests failed otherwise with `0`. */ - public $filters = array(); + public function run($path = null) { + $path = str_replace('\\', '/', $path); - /** - * Runs tests. Will provide a list of available tests if none are given. - * Test cases should be given in dot notation. - * - * Case example: - * {{{ - * lithium test --case=lithium.tests.cases.core.ObjectTest - * }}} - * - * Group example: - * {{{ - * lithium test --group=lithium.tests.cases.core - * }}} - * - * @return void - */ - public function run() { - $this->header('Test'); + if (!$path) { + $this->error('Please provide a path to tests.'); + return false; + } + if ($path[0] != '/') { + $path = $this->request->env('working') . '/' . $path; + } + if (!$path = realpath($path)) { + $this->error('Not a valid path.'); + return false; + } + $filters = $this->filters ? array_map('trim', explode(',', $this->filters)) : array(); - if ($this->_getTests() != true) { - return 0; + if (!$libraryPath = $this->_library($path)) { + $this->error("No library registered for path `{$path}`."); + return false; } - error_reporting(E_ALL | E_STRICT | E_DEPRECATED); + $path = $libraryPath; - $run = $this->case ?: $this->group; - $run = '\\' . str_replace('.', '\\', $run); - $this->out(sprintf('Running `%s`... ', $run), false); + if ($this->format == 'txt') { + $this->header('Test'); + $this->out(sprintf('Running test(s) in `%s`... ', $path), array('nl' => false)); + } + error_reporting(E_ALL | E_STRICT | E_DEPRECATED); - $report = Dispatcher::run($run, array( - 'filters' => $this->filters, + $report = Dispatcher::run($path, compact('filters') + array( 'reporter' => 'console', - 'format' => 'txt' + 'format' => $this->format )); - $this->out('done.', 2); - $this->out('{:heading1}Results{:end}', 0); - $this->out($report->render('stats')); + $stats = $report->stats(); + + if ($this->format == 'txt') { + $this->out('done.', 2); + $this->out('{:heading}Results{:end}', 0); + } + $this->out($report->render('stats', $stats)); foreach ($report->filters() as $filter => $options) { $data = $report->results['filters'][$filter]; $this->out($report->render($options['name'], compact('data'))); } - $this->hr(); - $this->nl(); - } - - /** - * Shows which classes are un-tested. - * - * @return void - */ - public function missing() { - $this->header('Classes with no test case'); - - $classes = Libraries::find(true, array( - 'recursive' => true, - 'exclude' => '/\w+Test$|webroot|index$|^app\\\\config|^app\\\\views/' - )); - $tests = Group::all(); - $classes = array_diff($classes, $tests); - - sort($classes); - $this->out($classes); - } - - /** - * Show included files. - * - * @return void - */ - public function included() { - $this->header('Included Files'); - $base = dirname(dirname(dirname(dirname(__DIR__)))); - $files = str_replace($base, '', get_included_files()); - sort($files); - $this->out($files); + if ($this->format == 'txt') { + $this->hr(); + $this->nl(); + } + return $stats['success']; } /** - * Provide a list of test cases and accept input as case to run. + * Finds a library for given path. * - * @return void + * @param string $path Normalized (to slashes) absolute or relative path. + * @return string|void The library's path on success. */ - protected function _getTests() { - while (empty($this->case) && empty($this->group)) { - $tests = Libraries::locate('tests', null, array( - 'filter' => '/cases|integration|functional/', - 'exclude' => '/mocks/' - )); - $tests = str_replace('\\', '.', $tests); - - foreach ($tests as $key => $test) { - $this->out(++$key . ". " . $test); - } - $number = $this->in("Choose a test case. (q to quit)"); - - if (isset($tests[--$number])) { - $this->case = $tests[$number]; - } - - if ($number == 'q') { - return 0; + protected function _library($path) { + foreach (Libraries::get() as $name => $library) { + if (strpos($path, $library['path']) === 0) { + $path = str_replace(array($library['path'], '.php'), null, $path); + return '\\' . $name . str_replace('/', '\\', $path); } } - return 1; } } diff --git a/libraries/lithium/console/command/create/Controller.php b/libraries/lithium/console/command/create/Controller.php index 5aaaffb..2a2c94f 100644 --- a/libraries/lithium/console/command/create/Controller.php +++ b/libraries/lithium/console/command/create/Controller.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -27,7 +27,7 @@ class Controller extends \lithium\console\command\Create { */ protected function _use($request) { $request->params['command'] = 'model'; - return '\\' . $this->_namespace($request) . '\\' . $this->_model($request); + return $this->_namespace($request) . '\\' . $this->_model($request); } /** @@ -67,7 +67,7 @@ class Controller extends \lithium\console\command\Create { * @return string */ protected function _model($request) { - return Inflector::classify($request->action); + return Inflector::camelize(Inflector::pluralize($request->action)); } /** diff --git a/libraries/lithium/console/command/create/Mock.php b/libraries/lithium/console/command/create/Mock.php index 42cfde8..1080050 100644 --- a/libraries/lithium/console/command/create/Mock.php +++ b/libraries/lithium/console/command/create/Mock.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -13,8 +13,8 @@ use lithium\util\Inflector; /** * Generate a Mock that extends the name of the given class in the `--library` namespace. * - * `li3 create mock model Post` - * `li3 create --library=li3_plugin mock model Post` + * `li3 create mock model Posts` + * `li3 create --library=li3_plugin mock model Posts` * */ class Mock extends \lithium\console\command\Create { @@ -39,7 +39,7 @@ class Mock extends \lithium\console\command\Create { */ protected function _parent($request) { $namespace = parent::_namespace($request); - $class = $request->action; + $class = Inflector::pluralize($request->action); return "\\{$namespace}\\{$class}"; } @@ -57,7 +57,7 @@ class Mock extends \lithium\console\command\Create { $request->params['action'] = $name; $name = $command->invokeMethod('_class', array($request)); } - return Inflector::classify("Mock{$name}"); + return Inflector::pluralize("Mock{$name}"); } /** diff --git a/libraries/lithium/console/command/create/Model.php b/libraries/lithium/console/command/create/Model.php index 1a22ec3..8a8e47b 100644 --- a/libraries/lithium/console/command/create/Model.php +++ b/libraries/lithium/console/command/create/Model.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -13,8 +13,8 @@ use lithium\util\Inflector; /** * Generate a Model class in the `--library` namespace * - * `li3 create mdoel Post` - * `li3 create --library=li3_plugin model Post` + * `li3 create model Posts` + * `li3 create --library=li3_plugin model Posts` * */ class Model extends \lithium\console\command\Create { @@ -26,7 +26,7 @@ class Model extends \lithium\console\command\Create { * @return string */ protected function _class($request) { - return Inflector::classify($request->action); + return Inflector::camelize(Inflector::pluralize($request->action)); } } diff --git a/libraries/lithium/console/command/create/Test.php b/libraries/lithium/console/command/create/Test.php index f76f685..d1224bd 100644 --- a/libraries/lithium/console/command/create/Test.php +++ b/libraries/lithium/console/command/create/Test.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -11,12 +11,13 @@ namespace lithium\console\command\create; use lithium\core\Libraries; use lithium\util\Inflector; use lithium\analysis\Inspector; +use lithium\core\ClassNotFoundException; /** * Generate a Test class in the `--library` namespace * - * `li3 create test model Post` - * `li3 create --library=li3_plugin test model Post` + * `li3 create test model Posts` + * `li3 create --library=li3_plugin test model Posts` * */ class Test extends \lithium\console\command\Create { @@ -40,9 +41,7 @@ class Test extends \lithium\console\command\Create { * @return string */ protected function _use($request) { - $namespace = parent::_namespace($request); - $name = $this->_name($request); - return "\\{$namespace}\\{$name}"; + return parent::_namespace($request) . '\\' . $this->_name($request); } /** @@ -88,7 +87,13 @@ class Test extends \lithium\console\command\Create { $type = $request->action; $name = $request->args(); - if ($command = $this->_instance($type)) { + try { + $command = $this->_instance($type); + } catch (ClassNotFoundException $e) { + $command = null; + } + + if ($command) { $request->params['action'] = $name; $name = $command->invokeMethod('_class', array($request)); } diff --git a/libraries/lithium/console/command/create/View.php b/libraries/lithium/console/command/create/View.php index 2564d55..7a89487 100644 --- a/libraries/lithium/console/command/create/View.php +++ b/libraries/lithium/console/command/create/View.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/console/command/create/template/app.phar.gz b/libraries/lithium/console/command/create/template/app.phar.gz index 7357e23..b53a2bd 100644 Binary files a/libraries/lithium/console/command/create/template/app.phar.gz and b/libraries/lithium/console/command/create/template/app.phar.gz differ diff --git a/libraries/lithium/console/command/create/template/controller.txt.php b/libraries/lithium/console/command/create/template/controller.txt.php index feb4077..f1e8ab5 100644 --- a/libraries/lithium/console/command/create/template/controller.txt.php +++ b/libraries/lithium/console/command/create/template/controller.txt.php @@ -34,4 +34,13 @@ class {:class} extends \lithium\action\Controller { } return compact('{:singular}'); } + + public function delete() { + if (!$this->request->is('post') && !$this->request->is('delete')) { + $msg = "{:name}::delete can only be called with http:post or http:delete."; + throw new DispatchException($msg); + } + {:model}::find($this->request->id)->delete(); + $this->redirect('{:name}::index'); + } } \ No newline at end of file diff --git a/libraries/lithium/console/command/g11n/Extract.php b/libraries/lithium/console/command/g11n/Extract.php index d458999..6a22e2c 100644 --- a/libraries/lithium/console/command/g11n/Extract.php +++ b/libraries/lithium/console/command/g11n/Extract.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -11,6 +11,7 @@ namespace lithium\console\command\g11n; use DateTime; use Exception; use lithium\g11n\Catalog; +use lithium\core\Libraries; /** * The `Extract` class is a command for extracting messages from files. @@ -26,7 +27,7 @@ class Extract extends \lithium\console\Command { public function _init() { parent::_init(); $this->source = $this->source ?: LITHIUM_APP_PATH; - $this->destination = $this->destination ?: LITHIUM_APP_PATH . '/resources/g11n'; + $this->destination = $this->destination ?: Libraries::get(true, 'resources') . '/g11n'; } /** diff --git a/libraries/lithium/console/li3 b/libraries/lithium/console/li3 index 563742c..baea739 100755 --- a/libraries/lithium/console/li3 +++ b/libraries/lithium/console/li3 @@ -1,9 +1,9 @@ -#!/usr/bin/env php -<?php +#!/bin/sh # # Lithium: the most rad php framework # -# @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) +# @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) # @license http://opensource.org/licenses/bsd-license.php The BSD License # -include __DIR__ . '/lithium.php'; +SELF=$0; test -L $0 && SELF=$(readlink -n $0) +php -f $(dirname $SELF)/lithium.php -- "$@" diff --git a/libraries/lithium/console/li3.bat b/libraries/lithium/console/li3.bat index f0ae8cf..0e575a9 100644 --- a/libraries/lithium/console/li3.bat +++ b/libraries/lithium/console/li3.bat @@ -2,7 +2,7 @@ rem rem Lithium: the most rad php framework rem -rem @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) +rem @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) rem @license http://opensource.org/licenses/bsd-license.php The BSD License rem php -f "%~dp0lithium.php" %* diff --git a/libraries/lithium/console/lithium.php b/libraries/lithium/console/lithium.php index 1cfe4b3..adaa3b0 100644 --- a/libraries/lithium/console/lithium.php +++ b/libraries/lithium/console/lithium.php @@ -2,24 +2,22 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ -namespace lithium; - -use lithium\core\Libraries; -use lithium\console\Dispatcher; - /** - * Determine if we're in an application context by moving up the directory tree looking for - * a `config` directory with a `bootstrap.php` file in it. If no application context is found, - * just boot up the core framework. + * This console front-controller file is the gateway to your application + * through the command line. It is responsible for intercepting requests, and + * handing them off to the `Dispatcher` for processing. + * + * Determine if we're in an application context by moving up the directory tree + * looking for a `config` directory with a `bootstrap.php` file in it. If no + * application context is found, just boot up the core framework. */ - $library = dirname(dirname(__DIR__)); -$app = null; $working = getcwd() ?: __DIR__; +$app = null; while (!$app && $working) { if (file_exists($working . '/config/bootstrap.php')) { @@ -31,21 +29,54 @@ while (!$app && $working) { } } +if ($app && is_dir("{$app}/config/bootstrap") && file_exists("{$app}/webroot/index.php")) { + include "{$app}/config/bootstrap.php"; + exit(lithium\console\Dispatcher::run(new lithium\console\Request())->status); +} + +define('LITHIUM_LIBRARY_PATH', $library); +define('LITHIUM_APP_PATH', $app ? $working : dirname($library) . '/app'); + +if (!include LITHIUM_LIBRARY_PATH . '/lithium/core/Libraries.php') { + $message = "Lithium core could not be found. Check the value of LITHIUM_LIBRARY_PATH in "; + $message .= __FILE__ . ". It should point to the directory containing your "; + $message .= "/libraries directory."; + throw new ErrorException($message); +} + +lithium\core\Libraries::add('lithium'); + if ($app) { - include $app . '/config/bootstrap.php'; -} else { - define('LITHIUM_LIBRARY_PATH', $library); - define('LITHIUM_APP_PATH', dirname($library) . '/app'); - - if (!include LITHIUM_LIBRARY_PATH . '/lithium/core/Libraries.php') { - $message = "Lithium core could not be found. Check the value of `LITHIUM_LIBRARY_PATH` "; - $message .= "in `config/bootstrap.php`. It should point to the directory containing your "; - $message .= "`/libraries` directory."; - trigger_error($message, E_USER_ERROR); - } - Libraries::add('lithium'); + lithium\core\Libraries::add(basename(LITHIUM_APP_PATH), array( + 'path' => LITHIUM_APP_PATH, + 'default' => true, + 'bootstrap' => !file_exists("{$app}/webroot/index.php") + )); } -exit(Dispatcher::run()->status); +/** + * The following will dispatch the request and exit with the status code as + * provided by the `Response` object returned from `run()`. + * + * The following will instantiate a new `Request` object and pass it off to the + * `Dispatcher` class. By default, the `Request` will automatically aggregate + * all the server / environment settings, and request content (i.e. options and + * arguments passed to the command) information. + * + * The `Request` is then used by the `Dispatcher` (in conjunction with the + * `Router`) to determine the correct command to dispatch to. The response + * information is then encapsulated in a `Response` object, which is returned + * from the command to the `Dispatcher`. + * + * The `Response` object will contain information about the status code which + * is used as the exit code when ending the execution of this script and + * returned to the callee. + * + * @see lithium\console\Request + * @see lithium\console\Response + * @see lithium\console\Dispatcher + * @see lithium\console\Router + */ +exit(lithium\console\Dispatcher::run(new lithium\console\Request())->status); ?> \ No newline at end of file diff --git a/libraries/lithium/console/readme.wiki b/libraries/lithium/console/readme.wiki index 13e1712..0138c81 100644 --- a/libraries/lithium/console/readme.wiki +++ b/libraries/lithium/console/readme.wiki @@ -1,42 +1,77 @@ -The console package contains a set of libraries designed for simplifying console -based interaction with users. -#### Command line overview +The console package contains a set of classes required to route and dispatch +incoming console requests. Moreover it contains the console front-controller +file (`lithium.php`) as well as wrappers for both *nix and Windows environments +(`li3` and `li3.bat` respectively), allowing to easily invoke the +console front-controller from the command line. -Lithium ships with a command line tool called `li3`. This tool can be useful -for creating new projects, pieces of a project (controllers, models, views etc) -or integrating your Lithim application with some command line routines of your -own. +A command is to the command line what an action controller is to the HTTP +request/response flow. In that commands are quite similar to controllers. +Commands don't leverage the the full MVC as they don't utilize views, but +directly interact with the user through `in()` and `out()`. -#### Getting started with li3 +Lithium itself provides amongst others commands for creating new applications +or parts thereof. However commands can also be provided through other libraries +or by your application. Commands running in the application context will have +complete access to your application. This is especially useful to reuse +existing logic in an application's model when creating a command to be run as +i.e. a cron-job. -To begin using `li3`, it is first recommended that you add to your system `$PATH` -(or `%PATH%` for Windows users) the location of where the `li3` tool is. +### Invoking the front-controller -**Configuring your $PATH on *nix** +You right away invoke the console front-controller through one of the wrappers +provided. From the root directory of a standard Lithium distribution call one +of the follwing commands. The first is for users on a *nix command line the +second for users on a Windows system. Please note that the preceding `$` in +examples always indicates things that you entere on the command line. -On *nix systems this is almost always achievable on a per-user basis through -the user's `~/.profile`. Try opening `.profile` which is normally located in -your home directory and add the following line: {{{ -export PATH=/path/to/libraries/lithium/console:$PATH +$ libraries/lithium/console/li3 +$ libraries/lithium/console/li3.bat }}} -This assumes the `li3` tool exists in `/path/to/libraries/lithium/console`. Also -make sure that the `li3` tool is executable, which can be done with the -following command: + +However it is recommended you add the path containing the wrapper to the paths +searched by your system. This is `$PATH` for *nix and `%PATH%` for Windows. + + +#### A: Configuring your $PATH on *nix + +This is almost always achievable on a per-user basis through the user's +`.profile` (Users running Bash may prefer `.bash_profile`). The file is located +in your home directory. Open the file and add the following line, assuming the +`li3` wrapper exists in `/path/to/libraries/lithium/console`. + +{{{ +export PATH+=:/path/to/libraries/lithium/console +}}} + +Once you've followed these steps, save your modified the file and reload your environment settings +by sourcing the modified profile through the following command. + +{{{ +$ source ~/.profile +}}} + +If you can't or don't want to modify your `$PATH` you use two other techniques +to make the wrapper available as just `li3`. You can either symlink the +wrapper into one of the paths found in the `$PATH` environment variable or +create a permanent alias by adding an alias to i.e. the `.bashrc` file in your +home directory. + {{{ -chmod +x /path/to/libraries/lithium/console/li3 +$ cd /path/to/a/directory/in/your/path +$ ln -s /path/to/libraries/lithium/console . }}} -Once you've followed these steps, save your modified `.profile`, open a new -shell and that's it. Alternatively, if you are running Bash as your shell, you -can use `~/.bash_profile` to hold you path changes. +{{{ +alias li3='/path/to/lithium/libraries/lithium/console/li3' +}}} -**Configuring your %PATH% on Windows** +#### B: Configuring your %PATH% on Windows -Please note that if you're on Windows you've additionally got to add the PHP directory to -the `%PATH%` environment variable. As we are going to edit that variable for adding -the location of where the `li3` tool is anyway, we can kill two birds with one stone. +Please note that if you're on Windows you've additionally got to add the PHP directory to +the `%PATH%` environment variable. As we are going to edit that variable for adding +the location of where the `li3.bat` wrapper is anyway, we can kill two birds with one stone. - Open _System_ from within the _Control Panel_. - Open the _Advanced_ tab. @@ -44,30 +79,42 @@ the location of where the `li3` tool is anyway, we can kill two birds with one s - Double click the _PATH_ entry in order to edit it. - Add `;C:\path\to\php;C:\path\to\libraries\lithium\console` to the end of the value. -You should get a nice prompt if you now open up the console and type: +#### Finishing up + +Now that you've made the wrapper available as `li3` or `li3.bat` respectively, +you should be able to use it from the command-line just by executing `li3` and +`li3.bat`. Invoking the wrapper like that (without arguments) should give you a +list of available commands. + {{{ -li3.bat +$ li3 +$ li3.bat }}} -#### Using built-in commands - -Now that you've got the command added to your path, you should be able to use it -from the command-line just by executing the `li3` command. +### Built-in commands -More documentation will be placed here as the built-in commands become more -usable and finalized. +Using the commands which come with lithium is easy. Invoke the wrapper without +any arguments to get a list of all available commands. Get a description about +each command and the options and arguments it accept or may require by using +the `help` command. -#### Creating custom commands +{{{ +$ li3 help +$ li3 help create +$ li3 help g11n +}}} -Creating your own commands is very easy. +### Creating custom commands -**Fundamentals** +Creating your own commands is very easy. A few fundamentals: - All commands inherit from `lithium\console\Command`. -- Commands are normally placed in your application or plugin's `exentesions/commands` directory. +- Commands are normally placed in your application or library's `extensions/commands` directory. Here's an example command: -{{{<?php + +{{{ +<?php namespace app\extensions\command; @@ -77,35 +124,44 @@ class HelloWorld extends \lithium\console\Command { $this->header('Welcome to the Hello World command!'); $this->out('Hello, World!'); } - } -?>}}} +?> +}}} If you would like to try this command, create an application or use an existing application, and place the command into the application's `extensions/commands` directory and save it as `HelloWorld.php`. After doing so, open a shell and change directory to your application's directory and run the following command: + {{{ -li3 hello_world +$ li3 hello_world }}} Although it's probably obvious, when this command runs it will output a nice -header with the text "Welcome to the Hello World command!" and some regular -text "Hello, World!" after it. +header with the text `Welcome to the Hello World command!` and some regular +text `Hello, World!` after it. The public method `run()` is called on your command instance every time your command has been requested to run. From this method you can add your own command logic. -#### Parsing command options +#### Parsing options and arguments + +Parsing options and arguments to commands should be simple. In fact, the +parsing is already done for you. -Parsing commands should be easy. In fact, the parsing is already done for you. -Command line options in the form of `--option=value` are automatically parsed -and exposed to your command instance through its properties. +Short and long (GNU-style) options in the form of `-f`, `--foo` and `--foo=bar` +are automatically parsed and exposed to your command instance through its +properties. XF68-style long options (i.e. `-foo`) are not supported by default +but support can be added by extending the console router. + +Arguments are passed directly to the invoked method. Let's look at an example, going back to the `hello_world` command from earlier: -{{{<?php + +{{{ +<?php namespace app\extensions\command; @@ -115,24 +171,21 @@ class HelloWorld extends \lithium\console\Command { public function run() { $this->header('Welcome to the Hello World command!'); - if ($this->recipient) { - $this->out('Hello, ' . $this->recipient . '!'); - } else { - $this->out('Hello, World!'); - } + $this->out('Hello, ' . ($this->recipient ?: 'World') . '!'); } - } -?>}}} +?> +}}} Notice the additional property `$recipient`? Great! Now when `--recipient` is passed to the `hello_world` command, the recipient property on your command instance will be set to whatever was passed into the command at runtime. Try it out with the following command: + {{{ -li3 hello_world --recipient=AwesomeGuy +$ li3 hello_world --recipient=AwesomeGuy }}} You should get a special greeting from our good old `hello_world` command. diff --git a/libraries/lithium/core/Adaptable.php b/libraries/lithium/core/Adaptable.php index 0e6a4bd..5a29e97 100644 --- a/libraries/lithium/core/Adaptable.php +++ b/libraries/lithium/core/Adaptable.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -101,7 +101,7 @@ class Adaptable extends \lithium\core\StaticObject { $config = static::_config($name); if ($config === null) { - throw new ConfigException("Configuration '{$name}' has not been defined."); + throw new ConfigException("Configuration `{$name}` has not been defined."); } if (isset($config['object'])) { @@ -109,8 +109,7 @@ class Adaptable extends \lithium\core\StaticObject { } $class = static::_class($config, static::$_adapters); $settings = static::$_configurations[$name]; - $settings[0]['object'] = new $class($config); - + $settings[0]['object'] = static::_initAdapter($class, $config); static::$_configurations[$name] = $settings; return static::$_configurations[$name][0]['object']; } @@ -126,7 +125,7 @@ class Adaptable extends \lithium\core\StaticObject { $config = static::_config($name); if ($config === null) { - throw new ConfigException("Configuration '{$name}' has not been defined."); + throw new ConfigException("Configuration `{$name}` has not been defined."); } if (!isset($config['strategies'])) { return null; @@ -203,6 +202,21 @@ class Adaptable extends \lithium\core\StaticObject { } /** + * Provides an extension point for modifying how adapters are instantiated. + * + * @see lithium\core\Object::__construct() + * @param string $class The fully-namespaced class name of the adapter to instantiate. + * @param array $config The configuration array to be passed to the adapter instance. See the + * `$config` parameter of `Object::__construct()`. + * @filter This method can be filtered. + */ + protected static function _initAdapter($class, array $config) { + return static::_filter(__FUNCTION__, compact('class', 'config'), function($self, $params) { + return new $params['class']($params['config']); + }); + } + + /** * Looks up an adapter by class by name. * * @see lithium\core\libraries::locate() @@ -213,11 +227,11 @@ class Adaptable extends \lithium\core\StaticObject { protected static function _class($config, $paths = array()) { if (!$name = $config['adapter']) { $self = get_called_class(); - throw new ConfigException("No adapter set for configuration in class {$self}."); + throw new ConfigException("No adapter set for configuration in class `{$self}`."); } if (!$class = static::_locate($paths, $name)) { $self = get_called_class(); - throw new ConfigException("Could not find adapter '{$name}' in class {$self}."); + throw new ConfigException("Could not find adapter `{$name}` in class `{$self}`."); } return $class; } @@ -233,11 +247,11 @@ class Adaptable extends \lithium\core\StaticObject { protected static function _strategy($name, $paths = array()) { if (!$name) { $self = get_called_class(); - throw new ConfigException("No strategy set for configuration in class {$self}."); + throw new ConfigException("No strategy set for configuration in class `{$self}`."); } if (!$class = static::_locate($paths, $name)) { $self = get_called_class(); - throw new ConfigException("Could not find strategy '{$name}' in class {$self}."); + throw new ConfigException("Could not find strategy `{$name}` in class `{$self}`."); } return $class; } @@ -281,6 +295,10 @@ class Adaptable extends \lithium\core\StaticObject { } $env = Environment::get(); $config = isset($settings[$env]) ? $settings[$env] : $settings; + + if (isset($settings[$env]) && isset($settings[true])) { + $config += $settings[true]; + } static::$_configurations[$name] += array(static::_initConfig($name, $config)); return static::$_configurations[$name][0]; } diff --git a/libraries/lithium/core/ClassNotFoundException.php b/libraries/lithium/core/ClassNotFoundException.php index 72c9904..9ce69ca 100644 --- a/libraries/lithium/core/ClassNotFoundException.php +++ b/libraries/lithium/core/ClassNotFoundException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/core/ConfigException.php b/libraries/lithium/core/ConfigException.php index f78d3ec..1021882 100644 --- a/libraries/lithium/core/ConfigException.php +++ b/libraries/lithium/core/ConfigException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/core/Environment.php b/libraries/lithium/core/Environment.php index 4952cbe..1894842 100644 --- a/libraries/lithium/core/Environment.php +++ b/libraries/lithium/core/Environment.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/core/ErrorHandler.php b/libraries/lithium/core/ErrorHandler.php index 9434bc5..774f6fb 100644 --- a/libraries/lithium/core/ErrorHandler.php +++ b/libraries/lithium/core/ErrorHandler.php @@ -9,8 +9,8 @@ namespace lithium\core; use Exception; -use lithium\util\Collection; -use lithium\core\Environment; +use ErrorException; +use lithium\util\collection\Filters; /** * The `ErrorHandler` class allows PHP errors and exceptions to be handled in a uniform way. Using @@ -61,6 +61,8 @@ class ErrorHandler extends \lithium\core\StaticObject { */ protected static $_isRunning = false; + protected static $_runOptions = array(); + /** * Setup basic error handling checks/types, as well as register the error and exception * hanlders. @@ -72,21 +74,15 @@ class ErrorHandler extends \lithium\core\StaticObject { public static function __init() { static::$_checks = array( 'type' => function($config, $info) { - return ( - $config['type'] == $info['type'] || - is_subclass_of($info['type'], $config['type']) - ); + return (boolean) array_filter((array) $config['type'], function($type) use ($info) { + return $type == $info['type'] || is_subclass_of($info['type'], $type); + }); }, 'code' => function($config, $info) { return ($config['code'] & $info['code']); }, 'stack' => function($config, $info) { - foreach ((array) $config['stack'] as $frame) { - if (in_array($frame, $info['stack'])) { - return true; - } - } - return false; + return (boolean) array_intersect((array) $config['stack'], $info['stack']); }, 'message' => function($config, $info) { return preg_match($config['message'], $info['message']); @@ -95,16 +91,15 @@ class ErrorHandler extends \lithium\core\StaticObject { $self = get_called_class(); static::$_exceptionHandler = function($exception, $return = false) use ($self) { - $info = array('type' => get_class($exception)) + compact('exception'); - + $info = compact('exception') + array( + 'type' => get_class($exception), + 'stack' => $self::trace($exception->getTrace()) + ); foreach (array('message', 'file', 'line', 'trace') as $key) { $method = 'get' . ucfirst($key); $info[$key] = $exception->{$method}(); } - if ($return) { - return $info; - } - $self::invokeMethod('handle', array($info)); + return $return ? $info : $self::handle($info); }; } @@ -122,7 +117,7 @@ class ErrorHandler extends \lithium\core\StaticObject { /** * Configure the `ErrorHandler`. * - * @var array $config Configuration directives. + * @param array $config Configuration directives. * @return Current configuration set. */ public static function config($config = array()) { @@ -139,20 +134,44 @@ class ErrorHandler extends \lithium\core\StaticObject { * This method (`ErrorHandler::run()`) needs to be called as early as possible in the bootstrap * cycle; immediately after `require`-ing `bootstrap/libraries.php` is your best bet. * + * @param array $config The configuration with which to start the error handler. Available + * options include: + * - `'trapErrors'` _boolean_: Defaults to `false`. If set to `true`, PHP errors + * will be caught by `ErrorHandler` and handled in-place. Execution will resume + * in the same context in which the error occurred. + * - `'convertErrors'` _boolean_: Defaults to `true`, and specifies that all PHP + * errors should be converted to `ErrorException`s and thrown from the point + * where the error occurred. The exception will be caught at the first point in + * the stack trace inside a matching `try`/`catch` block, or that has a matching + * error handler applied using the `apply()` method. * @return void */ - public static function run() { + public static function run(array $config = array()) { + $defaults = array('trapErrors' => false, 'convertErrors' => true); + + if (static::$_isRunning) { + return; + } + static::$_isRunning = true; + static::$_runOptions = $config + $defaults; $self = get_called_class(); - set_error_handler(function($code, $message, $file, $line = 0, $context = null) use ($self) { + $trap = function($code, $message, $file, $line = 0, $context = null) use ($self) { $trace = debug_backtrace(); $trace = array_slice($trace, 1, count($trace)); - $self::invokeMethod('handle', array( - compact('type', 'code', 'message', 'file', 'line', 'trace', 'context') - )); - }); + $self::handle(compact('type', 'code', 'message', 'file', 'line', 'trace', 'context')); + }; + + $convert = function($code, $message, $file, $line = 0, $context = null) use ($self) { + throw new ErrorException($message, 500, $code, $file, $line); + }; + + if (static::$_runOptions['trapErrors']) { + set_error_handler($trap); + } elseif (static::$_runOptions['convertErrors']) { + set_error_handler($convert); + } set_exception_handler(static::$_exceptionHandler); - static::$_isRunning = true; } /** @@ -212,7 +231,7 @@ class ErrorHandler extends \lithium\core\StaticObject { ); $info = (array) $info + $defaults; - $info['stack'] = static::_trace($info['trace']); + $info['stack'] = static::trace($info['trace']); $info['origin'] = static::_origin($info['trace']); foreach ($rules as $config) { @@ -245,7 +264,7 @@ class ErrorHandler extends \lithium\core\StaticObject { /** * Determine frame from the stack trace where the error/exception was first generated. * - * @var array $stack Stack trace from error/exception that was produced. + * @param array $stack Stack trace from error/exception that was produced. * @return string Class where error/exception was generated. */ protected static function _origin(array $stack) { @@ -256,22 +275,25 @@ class ErrorHandler extends \lithium\core\StaticObject { } } - public static function apply($class, $method, array $conditions, $handler) { + public static function apply($object, array $conditions, $handler) { + $conditions = $conditions ?: array('type' => 'Exception'); + list($class, $method) = is_string($object) ? explode('::', $object) : $object; + $wrap = static::$_exceptionHandler; $_self = get_called_class(); - $filter = function($self, $params, $chain) use ($_self, $conditions, $handler) { + $filter = function($self, $params, $chain) use ($_self, $conditions, $handler, $wrap) { try { return $chain->next($self, $params, $chain); } catch (Exception $e) { if (!$_self::matches($e, $conditions)) { throw $e; } - return $handler($e, $params); + return $handler($wrap($e, true), $params); } }; if (is_string($class)) { - $class::applyFilter($method, $filter); + Filters::apply($class, $method, $filter); } else { $class->applyFilter($method, $filter); } @@ -302,10 +324,10 @@ class ErrorHandler extends \lithium\core\StaticObject { /** * Trim down a typical stack trace to class & method calls. * - * @var array $stack A `debug_backtrace()`-compatible stack trace output. + * @param array $stack A `debug_backtrace()`-compatible stack trace output. * @return array Returns a flat stack array containing class and method references. */ - protected static function _trace(array $stack) { + public static function trace(array $stack) { $result = array(); foreach ($stack as $frame) { diff --git a/libraries/lithium/core/Libraries.php b/libraries/lithium/core/Libraries.php index 3a300a6..74231f6 100644 --- a/libraries/lithium/core/Libraries.php +++ b/libraries/lithium/core/Libraries.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -57,6 +57,13 @@ use lithium\core\ClassNotFoundException; class Libraries { /** + * Stores the closures that represent the method filters. They are indexed by method name. + * + * @var array + */ + protected static $_methodFilters = array(); + + /** * The list of class libraries registered with the class loader. * * @var array @@ -80,25 +87,19 @@ class Libraries { protected static $_paths = array( 'adapter' => array( '{:library}\extensions\adapter\{:namespace}\{:class}\{:name}', - '{:library}\extensions\adapter\{:class}\{:name}', '{:library}\{:namespace}\{:class}\adapter\{:name}' => array('libraries' => 'lithium') ), 'command' => array( '{:library}\extensions\command\{:namespace}\{:class}\{:name}', - '{:library}\extensions\command\{:class}\{:name}', - '{:library}\extensions\command\{:name}', '{:library}\console\command\{:namespace}\{:class}\{:name}' => array( 'libraries' => 'lithium' ), - '{:library}\console\command\{:class}\{:name}' => array('libraries' => 'lithium'), - '{:library}\console\command\{:name}' => array('libraries' => 'lithium'), ), 'controllers' => array( '{:library}\controllers\{:name}Controller' ), 'data' => array( '{:library}\extensions\data\{:namespace}\{:class}\{:name}', - '{:library}\extensions\data\{:class}\{:name}', '{:library}\data\{:namespace}\{:class}\adapter\{:name}' => array( 'libraries' => 'lithium' ), @@ -113,10 +114,12 @@ class Libraries { '{:app}/libraries/{:name}', '{:root}/{:name}' ), - 'models' => '{:library}\models\{:name}', + 'models' => array( + '{:library}\models\{:name}' + ), 'strategy' => array( '{:library}\extensions\strategy\{:namespace}\{:class}\{:name}', - '{:library}\extensions\strategy\{:name}', + '{:library}\extensions\strategy\{:class}\{:name}', '{:library}\{:namespace}\{:class}\strategy\{:name}' => array('libraries' => 'lithium') ), 'socket' => array( @@ -126,13 +129,10 @@ class Libraries { ), 'test' => array( '{:library}\extensions\test\{:namespace}\{:class}\{:name}', - '{:library}\extensions\test\{:class}\{:name}', '{:library}\test\{:namespace}\{:class}\{:name}' => array('libraries' => 'lithium'), - '{:library}\test\{:class}\{:name}' => array('libraries' => 'lithium') ), 'tests' => array( '{:library}\tests\{:namespace}\{:class}\{:name}Test', - '{:library}\tests\{:namespace}\{:name}Test' ) ); @@ -238,25 +238,30 @@ class Libraries { * that contains the `lithium` directory), however, you can change this on a case-by-case basis * using the `'path'` key to specify an absolute path to the library's directory. * - * @param string $name Library name, i.e. `'app'`, `'lithium'`, `'pear'` or `'solar'`. + * @param string $name Library name, i.e. `'app'`, `'lithium'`, `'pear'` or `'aura'`. * @param array $config Specifies where the library is in the filesystem, and how classes * should be loaded from it. Allowed keys are: - * - `'bootstrap'`: A file path (relative to `'path'`) to a bootstrap script that should - * be run when the library is added. - * - `'defer'`: If true, indicates that, when locating classes, this library should - * defer to other libraries in order of preference. - * - `'includePath'`: If `true`, appends the absolutely-resolved value of `'path'` to - * the PHP include path. + * - `'bootstrap'` _mixed_: A file path (relative to `'path'`) to a bootstrap script that + * should be run when the library is added, or `true` to use the default bootstrap + * path, i.e. `config/bootstrap.php`. + * - `'defer'` _boolean_: If `true`, indicates that, when locating classes, this library + * should defer to other libraries in order of preference. + * - `'includePath'` _mixed_: If `true`, appends the absolutely-resolved value of + * `'path'` to the PHP include path. If a string, the value is appended to PHP's. * - `'loader'`: An auto-loader method associated with the library, if any. * - `'path'`: The directory containing the library. - * - `'prefix'`: The class prefix this library uses, i.e. `'lithium\'`, `'Zend_'` or - * `'Solar_'`. - * - `'suffix'`: Gets appended to the end of the file name. For example, most libraries - * end classes in `'.php'`, but some use `'.class.php'`, or `'.inc.php'`. - * - `'transform'`: Defines a custom way to transform a class name into its + * - `'prefix'` _string_: The class prefix this library uses, i.e. `'lithium\'`, + * `'Zend_'` or `'Solar_'`. If the library has no global prefix, set to `false`. + * - `'suffix'` _string_: Gets appended to the end of the file name. For example, most + * libraries end classes in `'.php'`, but some use `'.class.php'`, or `'.inc.php'`. + * - `'transform'` _closure_: Defines a custom way to transform a class name into its * corresponding file path. Accepts either an array of two strings which are * interpreted as the pattern and replacement for a regex, or an anonymous function, - * which receives the class name as a parameter, and returns a file path as output. + * which receives the class name and library configuration arrays as parameters, and + * returns the full physical file path as output. + * - `'resources'` _string_: If this is the default library, this maybe set to the + * absolute path to the write-enabled application resources directory, which is used + * for caching, log files, uploads, etc. * @return array Returns the resulting set of options created for this library. */ public static function add($name, array $config = array()) { @@ -281,12 +286,13 @@ class Libraries { static::$_default = $name; $defaults['path'] = LITHIUM_APP_PATH; $defaults['bootstrap'] = false; + $defaults['resources'] = LITHIUM_APP_PATH . '/resources'; } $config += $defaults; if (!$config['path']) { if (!$config['path'] = static::_locatePath('libraries', compact('name'))) { - throw new ConfigException("Library '{$name}' not found."); + throw new ConfigException("Library `{$name}` not found."); } } $config['path'] = str_replace('\\', '/', $config['path']); @@ -323,14 +329,21 @@ class Libraries { * Returns configuration for given name. * * @param string $name Registered library to retrieve configuration for. - * @return array Retrieved configuration. + * @param string $key Optional key name. If `$name` is set and is the name of a valid library, + * returns the given named configuration key, i.e. `'path'`, `'webroot'` or + * `'resources'`. + * @return mixed A configuation array for one or more libraries, or a string value if `$key` is + * specified. */ - public static function get($name = null) { + public static function get($name = null, $key = null) { $configs = static::$_configurations; if (!$name) { return $configs; } + if ($name === true) { + $name = static::$_default; + } if (is_array($name)) { foreach ($name as $i => $key) { unset($name[$i]); @@ -338,10 +351,12 @@ class Libraries { } return $name; } - if ($name === true) { - $name = static::$_default; + $config = isset($configs[$name]) ? $configs[$name] : null; + + if (!$key) { + return $config; } - return isset($configs[$name]) ? $configs[$name] : null; + return isset($config[$key]) ? $config[$key] : null; } /** @@ -363,11 +378,31 @@ class Libraries { } /** - * Finds the classes in a library/namespace/folder - * - * @param string $library - * @param string $options - * @return array + * Finds the classes or namespaces belonging to a particular library. _Note_: This method + * assumes loaded class libraries use a consistent class-to-file naming convention. + * + * @param mixed $library The name of a library added to the application with `Libraries::add()`, + * or `true` to search all libraries. + * @param array $options The options this method accepts: + * + * - `'path'` _string_: A physical filesystem path relative to the directory of the + * library being searched. If provided, only the classes or namespaces within + * this path will be returned. + * - `'recursive'` _boolean_: If `true`, recursively searches all directories + * (namespaces) in the given library. If `false` (the default), only searches the + * top level of the given path. + * - `'filter'` _string_: A regular expression applied to a class after it is + * transformed into a fully-namespaced class name. The default regular expression + * filters class names based on the + * [PSR-0](http://groups.google.com/group/php-standards/web/psr-0-final-proposal) + * PHP 5.3 naming standard. + * - `'exclude'` _mixed_: Can be either a regular expression of classes/namespaces + * to exclude, or a PHP callable to be used with `array_filter()`. + * - `'namespaces'` _boolean_: Indicates whether namespaces should be included in + * the search results. If `false` (the default), only classes are returned. + * @return array Returns an array of fully-namespaced class names found in the given library or + * libraries. + * @todo Patch this to skip paths belonging to nested libraries in recursive searches. */ public static function find($library, array $options = array()) { $format = function($file, $config) { @@ -434,7 +469,7 @@ class Libraries { static::$_cachedPaths[$class] = $path; method_exists($class, '__init') ? $class::__init() : null; } elseif ($require) { - throw new RuntimeException("Failed to load {$class} from {$path}"); + throw new RuntimeException("Failed to load class `{$class}` from path `{$path}`."); } } @@ -476,15 +511,50 @@ class Libraries { $fullPath = "{$params['path']}/{$path}"; if (!$options['dirs']) { - return static::$_cachedPaths[$class] = realpath($fullPath . $suffix); + return static::$_cachedPaths[$class] = static::realPath($fullPath . $suffix); } $list = glob(dirname($fullPath) . '/*'); $list = array_map(function($i) { return str_replace('\\', '/', $i); }, $list); if (in_array($fullPath . $suffix, $list)) { - return static::$_cachedPaths[$class] = realpath($fullPath . $suffix); + return static::$_cachedPaths[$class] = static::realPath($fullPath . $suffix); + } + return is_dir($fullPath) ? static::realPath($fullPath) : null; + } + } + + /** + * Wraps the PHP `realpath()` function to add support for finding paths to files inside Phar + * archives. + * + * @param string $path An unresolved path to a file inside a Phar archive which may or may not + * exist. + * @return string If `$path` is a valid path to a file inside a Phar archive, returns a string + * in the format `'phar://<path-to-phar>/<path-to-file>'`. Otherwise returns + * `null`. + */ + public static function realPath($path) { + if (($absolutePath = realpath($path)) !== false) { + return $absolutePath; + } + if (!preg_match('%^phar://([^.]+\.phar(?:\.gz)?)(.+)%', $path, $pathComponents)) { + return; + } + list(, $relativePath, $pharPath) = $pathComponents; + + $pharPath = implode('/', array_reduce(explode('/', $pharPath), function ($parts, $value) { + if ($value == '..') { + array_pop($parts); + } elseif ($value != '.') { + $parts[] = $value; + } + return $parts; + })); + + if (($resolvedPath = realpath($relativePath)) !== false) { + if (file_exists($absolutePath = "phar://{$resolvedPath}{$pharPath}")) { + return $absolutePath; } - return is_dir($fullPath) ? realpath($fullPath) : null; } } @@ -529,12 +599,56 @@ class Libraries { * @return object If the class is found, returns an instance of it, otherwise throws an * exception. * @throws lithium\core\ClassNotFoundException Throws an exception if the class can't be found. + * @filter */ public static function instance($type, $name, array $options = array()) { - if (!$class = (string) static::locate($type, $name)) { - throw new ClassNotFoundException("Class '{$name}' of type '{$type}' not found."); + $params = compact('type', 'name', 'options'); + $_paths =& static::$_paths; + + $implementation = function($self, $params) use (&$_paths) { + $name = $params['name']; + $type = $params['type']; + + if (!$name && !$type) { + $message = "Invalid class lookup: `\$name` and `\$type` are empty."; + throw new ClassNotFoundException($message); + } + if (!is_string($type) && $type !== null && !isset($_paths[$type])) { + throw new ClassNotFoundException("Invalid class type `{$type}`."); + } + if (!$class = $self::locate($type, $name)) { + throw new ClassNotFoundException("Class `{$name}` of type `{$type}` not found."); + } + if (is_object($class)) { + return $class; + } + if (!(is_string($class) && class_exists($class))) { + throw new ClassNotFoundException("Class `{$name}` of type `{$type}` not defined."); + } + return new $class($params['options']); + }; + if (!isset(static::$_methodFilters[__FUNCTION__])) { + return $implementation(get_called_class(), $params); + } + $class = get_called_class(); + $method = __FUNCTION__; + $data = array_merge(static::$_methodFilters[__FUNCTION__], array($implementation)); + return Filters::run($class, $params, compact('data', 'class', 'method')); + } + + /** + * Apply a closure to a method in `Libraries`. + * + * @see lithium\util\collection\Filters + * @param string $method The name of the method to apply the closure to. + * @param closure $filter The closure that is used to filter the method. + * @return void + */ + public static function applyFilter($method, $filter = null) { + if (!isset(static::$_methodFilters[$method])) { + static::$_methodFilters[$method] = array(); } - return class_exists($class) ? new $class($options) : null; + static::$_methodFilters[$method][] = $filter; } /** @@ -592,36 +706,34 @@ class Libraries { * found which match `$type`. */ public static function locate($type, $name = null, array $options = array()) { - $defaults = array('type' => 'class'); - $options += $defaults; - if (is_object($name) || strpos($name, '\\') !== false) { return $name; } $ident = $name ? ($type . '.' . $name) : ($type . '.*'); + $ident .= $options ? '.' . md5(serialize($options)) : null; if (isset(static::$_cachedPaths[$ident])) { return static::$_cachedPaths[$ident]; } $params = static::_params($type, $name); - extract($params); - if (!isset(static::$_paths[$type])) { + $defaults = array( + 'type' => 'class', + 'library' => $params['library'] !== '*' ? $params['library'] : null + ); + $options += $defaults; + unset($params['library']); + $paths = static::paths($params['type']); + + if (!isset($paths)) { return null; } - if (!$name) { - $result = static::_locateAll(array('name' => '*') + $params, $options); + if ($params['name'] === '*') { + $result = static::_locateAll($params, $options); return (static::$_cachedPaths[$ident] = $result); } - $paths = (array) static::$_paths[$type]; - - if (strpos($name, '.')) { - list($params['library'], $params['name']) = explode('.', $name); - $params['library'][0] = strtolower($params['library'][0]); - - $result = static::_locateDeferred(null, $paths, $params, $options + array( - 'library' => $params['library'] - )); + if ($options['library']) { + $result = static::_locateDeferred(null, $paths, $params, $options); return static::$_cachedPaths[$ident] = $result; } foreach (array(false, true) as $defer) { @@ -669,12 +781,11 @@ class Libraries { * class in any path matching any of the parameters is located. */ protected static function _locateDeferred($defer, $paths, $params, array $options = array()) { + $libraries = static::$_configurations; + if (isset($options['library'])) { $libraries = static::get((array) $options['library']); - } else { - $libraries = static::$_configurations; } - foreach ($libraries as $library => $config) { if ($config['defer'] !== $defer && $defer !== null) { continue; @@ -713,11 +824,6 @@ class Libraries { if (isset($opts['libraries']) && !in_array($library, (array) $opts['libraries'])) { continue; } - foreach ($params as $key => $value) { - if (($value && $value !== '*') && strpos($tpl, "{:{$key}}") === false) { - continue 2; - } - } $result[] = $tpl; } return $result; @@ -735,7 +841,8 @@ class Libraries { $options += $defaults; $paths = (array) static::$_paths[$params['type']]; - $libraries = static::get($options['libraries'] ? (array) $options['libraries'] : null); + $libraries = $options['library'] ? $options['library'] : $options['libraries']; + $libraries = static::get((array) $libraries); $flags = array('escape' => '/'); $classes = array(); @@ -744,6 +851,7 @@ class Libraries { foreach (static::_searchPaths($paths, $library, $params) as $tpl) { $options['path'] = str_replace('\\', '/', String::insert($tpl, $params, $flags)); + $options['path'] = str_replace('*/', '', $options['path']); $classes = array_merge($classes, static::_search($config, $options)); } } @@ -820,7 +928,7 @@ class Libraries { if ($suffix) { $libs = $options['preFilter'] ? preg_grep($options['preFilter'], $libs) : $libs; } - return static::_filter($libs, $config, $options + compact('name')); + return static::_filter($libs, (array) $config, $options + compact('name')); } /** @@ -864,9 +972,10 @@ class Libraries { * @return array type, namespace, class, name */ protected static function _params($type, $name = "*") { - $namespace = $class = '*'; + $name = $name ?: "*"; + $library = $namespace = $class = '*'; - if (strpos($type, '.')) { + if (strpos($type, '.') !== false) { $parts = explode('.', $type); $type = array_shift($parts); @@ -883,7 +992,13 @@ class Libraries { break; } } - return compact('type', 'namespace', 'class', 'name'); + if (strpos($name, '.') !== false) { + $parts = explode('.', $name); + $library = array_shift($parts); + $name = array_pop($parts); + $namespace = $parts ? join('\\', $parts) : "*"; + } + return compact('library', 'namespace', 'type', 'class', 'name'); } } diff --git a/libraries/lithium/core/NetworkException.php b/libraries/lithium/core/NetworkException.php index 3f573e4..683dcbb 100644 --- a/libraries/lithium/core/NetworkException.php +++ b/libraries/lithium/core/NetworkException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/core/Object.php b/libraries/lithium/core/Object.php index f57fcbb..54239b3 100644 --- a/libraries/lithium/core/Object.php +++ b/libraries/lithium/core/Object.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\core; +use lithium\core\Libraries; use lithium\util\collection\Filters; /** @@ -205,19 +206,14 @@ class Object { * in `_init` to create the dependencies used in the current class. * * @param string|object $name A `classes` key or fully-namespaced class name. - * @param array $config The configuration passed to the constructor. - * @return void + * @param array $options The configuration passed to the constructor. + * @return object */ - protected function _instance($name, array $config = array()) { - if (is_object($name) || !$name) { - return $name; - } - $name = (string) $name; - - if (isset($this->_classes[$name])) { + protected function _instance($name, array $options = array()) { + if (is_string($name) && isset($this->_classes[$name])) { $name = $this->_classes[$name]; } - return (is_string($name) && class_exists($name)) ? new $name($config) : null; + return Libraries::instance(null, $name, $options); } /** diff --git a/libraries/lithium/core/StaticObject.php b/libraries/lithium/core/StaticObject.php index abcf66e..9f30ee0 100644 --- a/libraries/lithium/core/StaticObject.php +++ b/libraries/lithium/core/StaticObject.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\core; +use lithium\core\Libraries; use lithium\util\collection\Filters; /** @@ -87,17 +88,14 @@ class StaticObject { * in `_init` to create the dependencies used in the current class. * * @param string|object $name A `classes` key or fully-namespaced class name. - * @param array $config The configuration passed to the constructor. - * @return void + * @param array $options The configuration passed to the constructor. + * @return object */ - protected static function _instance($name, array $config = array()) { - if (is_object($name) || !$name) { - return $name; - } - if (isset(static::$_classes[$name])) { + protected static function _instance($name, array $options = array()) { + if (is_string($name) && isset(static::$_classes[$name])) { $name = static::$_classes[$name]; } - return new $name($config); + return Libraries::instance(null, $name, $options); } /** diff --git a/libraries/lithium/data/Collection.php b/libraries/lithium/data/Collection.php index 2a3628c..bc79012 100644 --- a/libraries/lithium/data/Collection.php +++ b/libraries/lithium/data/Collection.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -116,7 +116,11 @@ abstract class Collection extends \lithium\util\Collection { unset($this->_config[$key]); } if ($model = $this->_model) { - $options = array('pathKey' => $this->_pathKey, 'schema' => $model::schema()); + $options = array( + 'pathKey' => $this->_pathKey, + 'schema' => $model::schema(), + 'exists' => isset($this->_config['exists']) ? $this->_config['exists'] : null + ); $this->_data = $model::connection()->cast($this, $this->_data, $options); } } @@ -162,10 +166,10 @@ abstract class Collection extends \lithium\util\Collection { } /** - * Returns a boolean indicating whether an offset exists for the + * Returns a boolean indicating whether an offset exists for the * current `Collection`. * - * @param string $offset String or integer indicating the offset or + * @param string $offset String or integer indicating the offset or * index of an entity in the set. * @return boolean Result. */ @@ -226,14 +230,24 @@ abstract class Collection extends \lithium\util\Collection { * @param callback $filter The filter to apply. * @param array $options The available options are: * - `'collect'`: If `true`, the results will be returned wrapped - * in a new Collection object or subclass. + * in a new `Collection` object or subclass. * @return array|object The filtered data. */ public function map($filter, array $options = array()) { + $defaults = array('collect' => true); + $options += $defaults; + if (!$this->closed()) { while($this->next()) {} } - return parent::map($filter, $options); + $data = parent::map($filter, $options); + + if ($options['collect']) { + foreach (array('_model', '_schema', '_pathKey') as $key) { + $data->{$key} = $this->{$key}; + } + } + return $data; } /** diff --git a/libraries/lithium/data/Connections.php b/libraries/lithium/data/Connections.php index b90e58a..f8f18e0 100644 --- a/libraries/lithium/data/Connections.php +++ b/libraries/lithium/data/Connections.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -123,11 +123,11 @@ class Connections extends \lithium\core\Adaptable { * * // Gets the instance of the connection object, configured with the settings defined for * // this object in Connections::add() - * $dbConnection = Connection::get('db'); + * $dbConnection = Connections::get('db'); * * // Gets the connection object, but only if it has already been built. * // Otherwise returns null. - * $dbConnection = Connection::get('db', array('autoCreate' => false)); + * $dbConnection = Connections::get('db', array('autoCreate' => false)); * }}} * * @param string $name The name of the connection to get, as defined in the first parameter of diff --git a/libraries/lithium/data/Entity.php b/libraries/lithium/data/Entity.php index 23b2329..33ccb60 100644 --- a/libraries/lithium/data/Entity.php +++ b/libraries/lithium/data/Entity.php @@ -2,13 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\data; use BadMethodCallException; +use UnexpectedValueException; use lithium\data\Source; use lithium\util\Collection as Col; @@ -25,12 +26,20 @@ use lithium\util\Collection as Col; class Entity extends \lithium\core\Object { /** - * Namespaced name of model that this record is linked to. + * Fully-namespaced class name of model that this record is bound to. Instance methods declared + * in the model may be called on the entity. See the `Model` class documentation for more + * information. + * + * @see lithium\data\Model + * @see lithium\data\Entity::__call() + * @var string */ protected $_model = null; /** * Associative array of the entity's fields and values. + * + * @var array */ protected $_data = array(); @@ -59,23 +68,38 @@ class Entity extends \lithium\core\Object { protected $_handle = null; /** - * Validation errors + * The list of validation errors associated with this object, where keys are field names, and + * values are arrays containing one or more validation error messages. + * + * @see lithium\data\Entity::errors() + * @var array */ protected $_errors = array(); /** - * An array of flags to track which fields in this record have been modified, where the keys - * are field names, and the values are always `true`. If, for example, a change to a field is - * reverted, that field's flag should be unset from the list. + * Contains the values of updated fields. These values will be persisted to the backend data + * store when the document is saved. + * + * @var array + */ + protected $_updated = array(); + + /** + * An array of key/value pairs corresponding to fields that should be updated using atomic + * incrementing / decrementing operations. Keys match field names, and values indicate the value + * each field should be incremented or decrememnted by. * + * @see lithium\data\Entity::increment() + * @see lithium\data\Entity::decrement() * @var array */ - protected $_modified = array(); + protected $_increment = array(); /** - * A flag indicating whether or not this record exists. Set to false if this is a newly-created - * record, or if this record has been loaded and subsequently deleted. True if the record has - * been loaded from the database, or has been created and subsequently saved. + * A flag indicating whether or not this entity exists. Set to `false` if this is a + * newly-created entity, or if this entity has been loaded and subsequently deleted. Set to + * `true` if the entity has been loaded from the database, or has been created and subsequently + * saved. * * @var boolean */ @@ -142,6 +166,9 @@ class Entity extends \lithium\core\Object { } } } + if (isset($this->_updated[$name])) { + return $this->_updated[$name]; + } if (isset($this->_data[$name])) { return $this->_data[$name]; } @@ -157,13 +184,9 @@ class Entity extends \lithium\core\Object { */ public function __set($name, $value = null) { if (is_array($name) && !$value) { - foreach ($name as $key => $value) { - $this->__set($key, $value); - } - return; + return array_map(array(&$this, '__set'), array_keys($name), array_values($name)); } - $this->_modified[$name] = true; - $this->_data[$name] = $value; + $this->_updated[$name] = $value; } /** @@ -173,7 +196,7 @@ class Entity extends \lithium\core\Object { * @return mixed Result. */ public function __isset($name) { - return array_key_exists($name, $this->_data); + return isset($this->_data[$name]) || isset($this->_updated[$name]); } /** @@ -188,7 +211,7 @@ class Entity extends \lithium\core\Object { */ public function __call($method, $params) { if (!($model = $this->_model) || !method_exists($model, $method)) { - $message = "No model bound or unhandled method call '{$method}'."; + $message = "No model bound or unhandled method call `{$method}`."; throw new BadMethodCallException($message); } array_unshift($params, $this); @@ -212,14 +235,17 @@ class Entity extends \lithium\core\Object { } /** - * Access the data fields of the record. Can also access a $named field. - * - * @param string $name Optionally included field name. - * @return array|string Entire data array if $name is empty, otherwise the value from the named - * field. - */ + * Access the data fields of the record. Can also access a $named field. + * + * @param string $name Optionally included field name. + * @return array|string Entire data array if $name is empty, otherwise the value from the named + * field. + */ public function data($name = null) { - return empty($name) ? $this->_data : $this->__get($name); + if ($name) { + return $this->__get($name); + } + return $this->_updated + $this->_data; } /** @@ -232,6 +258,8 @@ class Entity extends \lithium\core\Object { } public function schema($field = null) { + $schema = array(); + switch (true) { case ($this->_schema): $schema = $this->_schema; @@ -239,12 +267,9 @@ class Entity extends \lithium\core\Object { case ($model = $this->_model): $schema = $model::schema(); break; - default: - $schema = array(); - break; } if ($field) { - return isset($self->_schema[$field]) ? $self->_schema[$field] : null; + return isset($schema[$field]) ? $schema[$field] : null; } return $schema; } @@ -252,18 +277,18 @@ class Entity extends \lithium\core\Object { /** * Access the errors of the record. * - * @param array|string $field If an array, overwrites `$this->_errors`. If a string, and $value - * is not null, sets the corresponding key in $this->_errors to $value + * @see lithium\data\Entity::$_errors + * @param array|string $field If an array, overwrites `$this->_errors`. If a string, and + * `$value` is not `null`, sets the corresponding key in `$this->_errors` to `$value`. * @param string $value Value to set. - * @return array|string Either the $this->_errors array, or single value from it. + * @return array|string Either the `$this->_errors` array, or single value from it. */ public function errors($field = null, $value = null) { if ($field === null) { return $this->_errors; } if (is_array($field)) { - $this->_errors = $field; - return $this->_errors; + return ($this->_errors = $field); } if ($value === null && isset($this->_errors[$field])) { return $this->_errors[$field]; @@ -286,34 +311,62 @@ class Entity extends \lithium\core\Object { /** * Called after an `Entity` is saved. Updates the object's internal state to reflect the - * corresponding database record, and sets the `Record`'s primary key, if this is a - * newly-created object. + * corresponding database entity, and sets the `Entity` object's key, if this is a newly-created + * object. * * @param mixed $id The ID to assign, where applicable. * @param array $data Any additional generated data assigned to the object by the database. * @return void */ public function update($id = null, array $data = array()) { - $this->_modified = array(); $this->_exists = true; + $model = $this->_model; + $key = array(); - if (!$id) { - return; + if ($id && $model) { + $key = $model::meta('key'); + $key = is_array($key) ? array_combine($key, $id) : array($key => $id); } + $this->_data = ($key + $data + $this->_updated + $this->_data); + $this->_updated = array(); + } - $model = $this->_model; - $key = $model::meta('key'); - - if (is_array($key)) { - foreach ($key as $i => $k) { - $this->_data[$k] = $id[$i]; - } - } else { - $this->_data[$key] = $id; + /** + * Safely (atomically) increments the value of the specified field by an arbitrary value. + * Defaults to `1` if no value is specified. Throws an exception if the specified field is + * non-numeric. + * + * @param string $field The name of the field to be incrememnted. + * @param string $value The value to increment the field by. Defaults to `1` if this parameter + * is not specified. + * @return integer Returns the current value of `$field`, based on the value retrieved from the + * data source when the entity was loaded, plus any increments applied. Note that it may + * not reflect the most current value in the persistent backend data source. + * @throws UnexpectedValueException Throws an exception when `$field` is set to a non-numeric + * type. + */ + public function increment($field, $value = 1) { + if (!isset($this->_data[$field])) { + return $this->_data[$field] = $value; } - foreach ($data as $key => $value) { - $this->_data[$key] = $value; + if (!is_numeric($this->_data[$field])) { + throw new UnexpectedValueException("Field '{$field}' cannot be incremented."); } + $base = isset($this->_updated[$field]) ? $this->_updated[$field] : $this->_data[$field]; + return $this->_updated[$field] = ($base + $value); + } + + /** + * Decrements a field by the specified value. Works identically to `increment()`, but in + * reverse. + * + * @see lithium\data\Entity::increment() + * @param string $field The name of the field to decrement. + * @param string $value The value by which to decrement the field. Defaults to `1`. + * @return integer Returns the new value of `$field`, after modification. + */ + public function decrement($field, $value = 1) { + return $this->increment($field, $value * -1); } /** @@ -330,8 +383,13 @@ class Entity extends \lithium\core\Object { return $this->_modified; } - public function export(Source $dataSource, array $options = array()) { - return array_intersect_key($this->_data, $this->_modified); + public function export() { + return array( + 'exists' => $this->_exists, + 'data' => $this->_data, + 'update' => $this->_updated, + 'increment' => $this->_increment, + ); } /** @@ -358,7 +416,7 @@ class Entity extends \lithium\core\Object { public function to($format, array $options = array()) { switch ($format) { case 'array': - $result = Col::toArray($this->_data); + $result = Col::toArray($this->data()); break; default: $result = $this; @@ -399,7 +457,7 @@ class Entity extends \lithium\core\Object { if ($model) { $exists = $this->_exists; $options += compact('parent', 'exists', 'pathKey'); - return $model::connection()->cast($model, $data, $options); + return $model::connection()->cast($this, $data, $options); } } } diff --git a/libraries/lithium/data/Model.php b/libraries/lithium/data/Model.php old mode 100755 new mode 100644 index 8f930a0..8febce4 --- a/libraries/lithium/data/Model.php +++ b/libraries/lithium/data/Model.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -26,40 +26,42 @@ use BadMethodCallException; * data mutation (saving/updating/deleting). All query-related operations may be done through the * static `find()` method, along with some additional utility methods provided for convenience. * - * Classes extending this one should, conventionally, be named as Singular, CamelCase and be - * placed in the app/models directory. i.e. a posts model would be app/model/Post.php. + * Classes extending this one should, conventionally, be named as Plural, CamelCase and be + * placed in the `app/models` directory. i.e. a posts model would be `app/model/Posts.php`. * * Examples: * {{{ * // Return all 'post' records - * Post::find('all'); - * Post::all(); + * Posts::find('all'); + * Posts::all(); * * // With conditions and a limit - * Post::find('all', array('conditions' => array('published' => true), 'limit' => 10)); - * Post::all(array('conditions' => array('published' => true), 'limit' => 10)); + * Posts::find('all', array('conditions' => array('published' => true), 'limit' => 10)); + * Posts::all(array('conditions' => array('published' => true), 'limit' => 10)); * * // Integer count of all 'post' records - * Post::find('count'); - * Post::count(); // This is equivalent to the above. + * Posts::find('count'); + * Posts::count(); // This is equivalent to the above. * * // With conditions - * Post::find('count', array('conditions' => array('published' => true))); - * Post::count(array('published' => true)); + * Posts::find('count', array('conditions' => array('published' => true))); + * Posts::count(array('published' => true)); * }}} * - * The actual objects returned from `find()` calls will depend on the type of datasource in use. - * MongoDB, for example, will return results as a `Document`, while MySQL will return results - * as a `RecordSet`. Both of these classes extend a common `data\Collection` class, and provide - * the necessary abstraction to make working with either type completely transparent. + * The actual objects returned from `find()` calls will depend on the type of data source in use. + * MongoDB, for example, will return results as a `Document` (as will CouchDB), while MySQL will + * return results as a `RecordSet`. Both of these classes extend a common `lithium\data\Collection` + * class, and provide the necessary abstraction to make working with either type completely + * transparent. * * For data mutation (saving/updating/deleting), the `Model` class acts as a broker to the proper - * objects. When creating a new record, for example, a call to `Post::create()` will return a - * `data\model\Record` object, which can then be acted upon. + * objects. When creating a new record or document, for example, a call to `Posts::create()` will + * return an instance of `lithium\data\entity\Record` or `lithium\data\entity\Document`, which can + * then be acted upon. * * Example: * {{{ - * $post = Post::create(); + * $post = Posts::create(); * $post->author = 'Robert'; * $post->title = 'Newest Post!'; * $post->content = 'Lithium rocks. That is all.'; @@ -168,10 +170,9 @@ class Model extends \lithium\core\StaticObject { */ protected $_relationTypes = array( 'belongsTo' => array('class', 'key', 'conditions', 'fields'), - 'hasOne' => array('class', 'key', 'conditions', 'fields', 'dependent'), + 'hasOne' => array('class', 'key', 'conditions', 'fields'), 'hasMany' => array( - 'class', 'key', 'conditions', 'fields', 'order', 'limit', - 'dependent', 'exclusive', 'finder', 'counter' + 'class', 'key', 'conditions', 'fields', 'order', 'limit' ) ); @@ -319,9 +320,7 @@ class Model extends \lithium\core\StaticObject { if (static::_isBase($class = get_called_class())) { return; } - $self = static::_object(); - $base = get_class_vars(__CLASS__); - + $self = static::_object(); $meta = array(); $schema = array(); $source = array(); @@ -340,16 +339,16 @@ class Model extends \lithium\core\StaticObject { } } $tmp = $options + $self->_meta + $meta; + $source = array('meta' => array(), 'finders' => array(), 'schema' => array()); if ($tmp['connection']) { $conn = $classes['connections']::get($tmp['connection']); - $source = ($conn) ? $conn->configureClass($class) : array(); + $source = (($conn) ? $conn->configureClass($class) : array()) + $source; } - $source += array('meta' => array(), 'finders' => array(), 'schema' => array()); static::$_classes = $classes; $name = static::_name(); - $local = compact('class', 'name') + $options + array_diff($self->_meta, $base['_meta']); + $local = compact('class', 'name') + $options + $self->_meta; $self->_meta = ($local + $source['meta'] + $meta); $self->_meta['initialized'] = false; $self->_schema += $schema + $source['schema']; @@ -387,7 +386,7 @@ class Model extends \lithium\core\StaticObject { preg_match('/^findBy(?P<field>\w+)$|^find(?P<type>\w+)By(?P<fields>\w+)$/', $method, $args); if (!$args) { - $message = "Method %s not defined or handled in class %s"; + $message = "Method `%s` not defined or handled in class `%s`."; throw new BadMethodCallException(sprintf($message, $method, get_class($self))); } $field = Inflector::underscore($args['field'] ? $args['field'] : $args['fields']); @@ -443,7 +442,7 @@ class Model extends \lithium\core\StaticObject { $type = 'first'; } - $options += ((array) $self->_query + (array) $defaults); + $options = (array) $options + (array) $self->_query + (array) $defaults; $meta = array('meta' => $self->_meta, 'name' => get_called_class()); $params = compact('type', 'options'); @@ -570,7 +569,7 @@ class Model extends \lithium\core\StaticObject { $self = static::_object(); if (!isset($self->_relationTypes[$type])) { - throw new ConfigException("Invalid relationship type '{$type}' specified."); + throw new ConfigException("Invalid relationship type `{$type}` specified."); } $rel = static::connection()->relationship(get_called_class(), $type, $name, $config); return static::_object()->_relations[$name] = $rel; @@ -580,13 +579,18 @@ class Model extends \lithium\core\StaticObject { * Lazy-initialize the schema for this Model object, if it is not already manually set in the * object. You can declare `protected $_schema = array(...)` to define the schema manually. * - * @param string $field Optional. You may pass a field name to get schema information for just - * one field. Otherwise, an array containing all fields is returned. + * @param mixed $field Optional. You may pass a field name to get schema information for just + * one field. Otherwise, an array containing all fields is returned. If `false`, the + * schema is reset to an empty value. If an array, field definitions contained are + * appended to the schema. * @return array */ public static function schema($field = null) { $self = static::_object(); + if ($field === false) { + return $self->_schema = array(); + } if (!$self->_schema) { $self->_schema = static::connection()->describe($self::meta('source'), $self->_meta); } @@ -620,16 +624,36 @@ class Model extends \lithium\core\StaticObject { } /** - * Instantiates a new record object, initialized with any data passed in. For example: + * Instantiates a new record or document object, initialized with any data passed in. For + * example: + * * {{{ - * $post = Post::create(array("title" => "New post")); + * $post = Posts::create(array("title" => "New post")); * echo $post->title; // echoes "New post" * $success = $post->save(); * }}} * - * @param array $data Any data that this record should be populated with initially. + * Note that while this method creates a new object, there is no effect on the database until + * the `save()` method is called. + * + * In addition, this method can be used to simulate loading a pre-existing object from the + * database, without actually querying the database: + * + * {{{ + * $post = Posts::create(array("id" => $id, "moreData" => "foo"), array("exists" => true)); + * $post->title = "New title"; + * $success = $post->save(); + * }}} + * + * This will create an update query against the object with an ID matching `$id`. Also note that + * only the `title` field will be updated. + * + * @param array $data Any data that this object should be populated with initially. * @param array $options Options to be passed to item. - * @return object Returns a new, **un-saved** record object. + * @return object Returns a new, _un-saved_ record or document object. In addition to the values + * passed to `$data`, the object will also contain any values assigned to the + * `'default'` key of each field defined in `$_schema`. + * @filter */ public static function create(array $data = array(), array $options = array()) { $self = static::_object(); @@ -638,14 +662,14 @@ class Model extends \lithium\core\StaticObject { return static::_filter(__FUNCTION__, $params, function($self, $params) { $data = $params['data']; $options = $params['options']; + $defaults = array(); - if ($schema = $self::schema()) { - foreach ($schema as $field => $config) { - if (!isset($data[$field]) && isset($config['default'])) { - $data[$field] = $config['default']; - } + foreach ((array) $self::schema() as $field => $config) { + if (isset($config['default'])) { + $defaults[$field] = $config['default']; } } + $data = Set::merge(Set::expand($defaults), $data); return $self::connection()->item($self, $data, $options); }); } @@ -654,19 +678,45 @@ class Model extends \lithium\core\StaticObject { * An instance method (called on record and document objects) to create or update the record or * document in the database that corresponds to `$entity`. * - * For example: + * For example, to create a new record or document: * {{{ - * $post = Post::create(); + * $post = Posts::create(); // Creates a new object, which doesn't exist in the database yet * $post->title = "My post"; + * $success = $post->save(); + * }}} + * + * It is also used to update existing database objects, as in the following: + * {{{ + * $post = Posts::first($id); + * $post->title = "Revised title"; + * $success = $post->save(); + * }}} + * + * By default, an object's data will be checked against the validation rules of the model it is + * bound to. Any validation errors that result can then be accessed through the `errors()` + * method. + * + * {{{ + * if (!$post->save($someData)) { + * return array('errors' => $post->errors()); + * } + * }}} + * + * To override the validation checks and save anyway, you can pass the `'validate'` option: + * + * {{{ + * $post->title = "We Don't Need No Stinkin' Validation"; + * $post->body = "I know what I'm doing."; * $post->save(null, array('validate' => false)); * }}} * * @see lithium\data\Model::$validates * @see lithium\data\Model::validates() + * @see lithium\data\Model::errors() * @param object $entity The record or document object to be saved in the database. This * parameter is implicit and should not be passed under normal circumstances. * In the above example, the call to `save()` on the `$post` object is - * transparently proxied through to the `Post` model class, and `$post` is passed + * transparently proxied through to the `Posts` model class, and `$post` is passed * in as the `$entity` parameter. * @param array $data Any data that should be assigned to the record before it is saved. * @param array $options Options: @@ -680,6 +730,7 @@ class Model extends \lithium\core\StaticObject { * record. * * @return boolean Returns `true` on a successful save operation, `false` on failure. + * @filter */ public function save($entity, $data = null, array $options = array()) { $self = static::_object(); @@ -707,8 +758,8 @@ class Model extends \lithium\core\StaticObject { return false; } } - if ($options['whitelist'] || $options['locked']) { - $whitelist = $options['whitelist'] ?: array_keys($_schema); + if (($whitelist = $options['whitelist']) || $options['locked']) { + $whitelist = $whitelist ?: array_keys($_schema); } $type = $entity->exists() ? 'update' : 'create'; @@ -718,18 +769,53 @@ class Model extends \lithium\core\StaticObject { }; if (!$options['callbacks']) { - return $filter($entity, $options); + return $filter(get_called_class(), $params); } return static::_filter(__FUNCTION__, $params, $filter); } /** - * Indicates whether the `Model`'s current data validates, given the - * current rules setup. + * An important part of describing the business logic of a model class is defining the + * validation rules. In Lithium models, rules are defined through the `$validates` class + * property, and are used by this method before saving to verify the correctness of the data + * being sent to the backend data source. * - * @param string $entity Model record to validate. - * @param array $options Options. - * @return boolean Success. + * Note that these are application-level validation rules, and do not + * interact with any rules or constraints defined in your data source. If such constraints fail, + * an exception will be thrown by the database layer. The `validates()` method only checks + * against the rules defined in application code. + * + * This method uses the `Validator` class to perform data validation. An array representation of + * the entity object to be tested is passed to the `check()` method, along with the model's + * validation rules. Any rules defined in the `Validator` class can be used to validate fields. + * See the `Validator` class to add custom rules, or override built-in rules. + * + * @see lithium\data\Model::$validates + * @see lithium\util\Validator::check() + * @see lithium\data\Entity::errors() + * @param string $entity Model entity to validate. Typically either a `Record` or `Document` + * object. In the following example: + * {{{ + * $post = Posts::create($data); + * $success = $post->validates(); + * }}} + * The `$entity` parameter is equal to the `$post` object instance. + * @param array $options Available options: + * - `'rules'` _array_: If specified, this array will _replace_ the default + * validation rules defined in `$validates`. + * - `'events'` _mixed_: A string or array defining one or more validation + * _events_. Events are different contexts in which data events can occur, and + * correspond to the optional `'on'` key in validation rules. For example, by + * default, `'events'` is set to either `'create'` or `'update'`, depending on + * whether `$entity` already exists. Then, individual rules can specify + * `'on' => 'create'` or `'on' => 'update'` to only be applied at certain times. + * Using this parameter, you can set up custom events in your rules as well, such + * as `'on' => 'login'`. Note that when defining validation rules, the `'on'` key + * can also be an array of multiple events. + * @return boolean Returns `true` if all validation rules on all fields succeed, otherwise + * `false`. After validation, the messages for any validation failures are assigned to + * the entity, and accessible through the `errors()` method of the entity object. + * @filter */ public function validates($entity, array $options = array()) { $defaults = array( @@ -761,6 +847,7 @@ class Model extends \lithium\core\StaticObject { * @param object $entity Entity to delete. * @param array $options Options. * @return boolean Success. + * @filter */ public function delete($entity, array $options = array()) { $self = static::_object(); @@ -788,6 +875,7 @@ class Model extends \lithium\core\StaticObject { * the `delete()` method of the corresponding backend database for available * options. * @return boolean Returns `true` if the update operation succeeded, otherwise `false`. + * @filter */ public static function update($data, $conditions = array(), array $options = array()) { $self = static::_object(); @@ -814,6 +902,7 @@ class Model extends \lithium\core\StaticObject { * the `delete()` method of the corresponding backend database for available * options. * @return boolean Returns `true` if the remove operation succeeded, otherwise `false`. + * @filter */ public static function remove($conditions = array(), array $options = array()) { $self = static::_object(); @@ -843,12 +932,12 @@ class Model extends \lithium\core\StaticObject { if ($conn = $connections::get($name)) { return $conn; } - throw new ConfigException("The data connection {$name} is not configured"); + throw new ConfigException("The data connection `{$name}` is not configured."); } /** * Gets just the class name portion of a fully-name-spaced class name, i.e. - * `app\models\Post::_name()` returns `'Post'`. + * `app\models\Posts::_name()` returns `'Posts'`. * * @return string */ @@ -948,7 +1037,7 @@ class Model extends \lithium\core\StaticObject { */ protected static function _findFilters() { $self = static::_object(); - $_query =& $self->_query; + $_query = $self->_query; return array( 'first' => function($self, $params, $chain) { diff --git a/libraries/lithium/data/Source.php b/libraries/lithium/data/Source.php index c0bb33a..052c645 100644 --- a/libraries/lithium/data/Source.php +++ b/libraries/lithium/data/Source.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -210,7 +210,8 @@ abstract class Source extends \lithium\core\Object { /** * Casts data into proper format when added to a collection or entity object. * - * @param string $model The name of the model class to which the entity or collection is bound. + * @param mixed $entity The entity or collection for which data is being cast, or the name of + * the model class to which the entity/collection is bound. * @param array $data An array of data being assigned. * @param array $options Any associated options with, for example, instantiating new objects in * which to wrap the data. Options implemented by `cast()` itself: @@ -220,7 +221,7 @@ abstract class Source extends \lithium\core\Object { * @return mixed Returns the value of `$data`, cast to the proper format according to the schema * definition of the model class specified by `$model`. */ - public function cast($model, array $data, array $options = array()) { + public function cast($entity, array $data, array $options = array()) { $defaults = array('first' => false); $options += $defaults; return $options['first'] ? reset($data) : $data; diff --git a/libraries/lithium/data/collection/DocumentArray.php b/libraries/lithium/data/collection/DocumentArray.php index 2e4fd88..3cd1931 100644 --- a/libraries/lithium/data/collection/DocumentArray.php +++ b/libraries/lithium/data/collection/DocumentArray.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -13,6 +13,26 @@ use lithium\util\Collection; class DocumentArray extends \lithium\data\Collection { + protected $_exists = false; + + /** + * Holds an array of values that should be processed on initialization. + * + * @var array + */ + protected $_autoConfig = array( + 'data', 'model', 'result', 'query', 'parent', 'stats', 'pathKey', 'exists' + ); + + public function exists() { + return $this->_exists; + } + + public function update($id = null, array $data = array()) { + $this->_exists = true; + $this->_data = $data ?: $this->_data; + } + /** * Adds conversions checks to ensure certain class types and embedded values are properly cast. * @@ -71,9 +91,8 @@ class DocumentArray extends \lithium\data\Collection { public function offsetSet($offset, $data) { if ($model = $this->_model) { - $data = $model::connection()->cast($model, array($this->_pathKey => $data), array( - 'first' => true - )); + $options = array('first' => true, 'schema' => $model::schema()); + $data = $model::connection()->cast($this, array($this->_pathKey => $data), $options); } if ($offset) { return $this->_data[$offset] = $data; @@ -115,17 +134,12 @@ class DocumentArray extends \lithium\data\Collection { return $this->_valid ? $this->offsetGet(key($this->_data)) : null; } - public function export(Source $dataSource, array $options = array()) { - $result = array(); - - foreach ($this->_data as $key => $doc) { - if (is_object($doc) && method_exists($doc, 'export')) { - $result[$key] = $doc->export($dataSource, $options); - continue; - } - $result[$key] = $doc; - } - return $result; + public function export() { + return array( + 'exists' => $this->_exists, + 'key' => $this->_pathKey, + 'data' => $this->_data, + ); } protected function _populate($data = null, $key = null) {} diff --git a/libraries/lithium/data/collection/DocumentSet.php b/libraries/lithium/data/collection/DocumentSet.php index d0c2ccb..54a91d8 100644 --- a/libraries/lithium/data/collection/DocumentSet.php +++ b/libraries/lithium/data/collection/DocumentSet.php @@ -2,14 +2,12 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\data\collection; -use lithium\data\Source; - class DocumentSet extends \lithium\data\Collection { /** @@ -39,7 +37,7 @@ class DocumentSet extends \lithium\data\Collection { $next = $current->__get($key); if (!is_object($next) && ($model = $this->_model)) { - $next = $model::connection()->cast($model, $next); + $next = $model::connection()->cast($this, $next); $current->_data[$key] = $next; } $current = $next; @@ -109,7 +107,7 @@ class DocumentSet extends \lithium\data\Collection { return $null; } if (is_array($data = $this->_data[$offset]) && $model) { - $this->_data[$offset] = $model::connection()->cast($model, $data); + $this->_data[$offset] = $model::connection()->cast($this, $data); } if (isset($this->_data[$offset])) { return $this->_data[$offset]; @@ -159,9 +157,9 @@ class DocumentSet extends \lithium\data\Collection { return $this->_valid ? $this->offsetGet(key($this->_data)) : null; } - public function export(Source $dataSource, array $options = array()) { - $map = function($doc) use ($dataSource, $options) { - return is_array($doc) ? $doc : $doc->export($dataSource, $options); + public function export(array $options = array()) { + $map = function($doc) use ($options) { + return is_array($doc) ? $doc : $doc->export(); }; return array_map($map, $this->_data); } @@ -184,7 +182,7 @@ class DocumentSet extends \lithium\data\Collection { return $this->close(); } $options = array('exists' => true, 'first' => true, 'pathKey' => $this->_pathKey); - return $this->_data[] = $conn->cast($model, array($key => $data), $options); + return $this->_data[] = $conn->cast($this, array($key => $data), $options); } /** diff --git a/libraries/lithium/data/collection/RecordSet.php b/libraries/lithium/data/collection/RecordSet.php index 593852f..4298e92 100644 --- a/libraries/lithium/data/collection/RecordSet.php +++ b/libraries/lithium/data/collection/RecordSet.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/data/entity/Document.php b/libraries/lithium/data/entity/Document.php index 630b947..d699271 100644 --- a/libraries/lithium/data/entity/Document.php +++ b/libraries/lithium/data/entity/Document.php @@ -2,14 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\data\entity; -use lithium\data\Source; use lithium\util\Collection; +use UnexpectedValueException; /** * `Document` is an alternative to the `entity\Record` class, which is optimized for @@ -70,15 +70,6 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { protected $_pathKey = null; /** - * Indicates whether this document has already been created in the database. - * - * @var boolean - */ - protected $_exists = false; - - protected $_errors = array(); - - /** * An array containing all related documents, keyed by relationship name, as defined in the * bound model class. * @@ -87,13 +78,12 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { protected $_relations = array(); /** - * An array of flags to track which fields in this document have been modified, where the keys - * are field names, and the values are always `true`. If, for example, a change to a field is - * reverted, that field's flag should be unset from the list. + * Contains an array of removed fields, where the field names are the keys, and the values are + * always `true`. * * @var array */ - protected $_modified = array(); + protected $_removed = array(); /** * Contains an array of backend-specific statistics generated by the query that produced this @@ -114,12 +104,15 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { protected function _init() { parent::_init(); - $this->_data = (array) $this->_data; - - if ($model = $this->_model) { - $pathKey = $this->_pathKey; - $this->_data = $model::connection()->cast($model, $this->_data, compact('pathKey')); - } + $data = (array) $this->_data; + $this->_data = array(); + $this->set($data); + $exists = $this->_exists; + + $this->_data = $this->_updated; + $this->_updated = array(); + $this->update(); + $this->_exists = $exists; unset($this->_autoConfig); } @@ -143,6 +136,13 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { return $this->_getNested($name); } + if (isset($this->_removed[$name])) { + return $null; + } + if (isset($this->_updated[$name])) { + return $this->_updated[$name]; + } + if ($model && $conn) { foreach ($model::relations() as $relation => $config) { if ($config && (($linkKey = $config->data('fieldName')) === $name)) { @@ -153,70 +153,41 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { } if (!isset($this->_data[$name]) && $schema = $model::schema($name)) { $schema = array($name => $schema); - $pathKey = $this->_pathKey ? "{$this->_pathKey}.{$name}" : $name; + $pathKey = $this->_pathKey ? $this->_pathKey : null; $options = compact('pathKey', 'schema') + array('first' => true); - $this->_data[$name] = $conn->cast($model, array($name => null), $options); - return $this->_data[$name]; - } - } - if (!isset($this->_data[$name])) { - return $null; - } - return $this->_data[$name]; - } - - public function export(Source $dataSource, array $options = array()) { - $defaults = array('atomic' => true); - $options += $defaults; - list($data, $nested) = $this->_exportRecursive($dataSource, $options); - - if ($options['atomic'] && $this->_exists) { - $data = array_intersect_key($data, $this->_modified + $nested); - } - if ($model = $this->_model) { - $name = null; - $options = array('atomic' => false) + $options; - $relations = new Collection(array('data' => $model::relations())); - $find = function($relation) use (&$name) { return $relation->fieldName === $name; }; - - foreach ($this->_relationships as $name => $subObject) { - if (($rel = $relations->first($find)) && $rel->link == $rel::LINK_EMBEDDED) { - $data[$name] = $subObject->export($dataSource, $options); + if (($value = $conn->cast($this, array($name => null), $options)) !== null) { + $this->_data[$name] = $value; + return $this->_data[$name]; } } } - return $data; + if (isset($this->_data[$name])) { + return $this->_data[$name]; + } + return $null; } - protected function _exportRecursive(Source $dataSource, array $options = array()) { - $data = array(); - $nested = array(); - - foreach ($this->_data as $key => $val) { - if (!is_object($val) || !method_exists($val, 'export')) { - $data[$key] = $val; - continue; - } - $nestedOptions = $options; - - if (!$this->_exists || isset($this->_modified[$key])) { - $nestedOptions = array('atomic' => false) + $nestedOptions; - } - if ($data[$key] = $val->export($dataSource, $nestedOptions)) { - $nested[$key] = true; + public function export() { + foreach ($this->_updated as $key => $val) { + if (is_a($val, __CLASS__)) { + $path = $this->_pathKey ? "{$this->_pathKey}." : ''; + $this->_updated[$key]->_pathKey = "{$path}{$key}"; + $this->_updated[$key]->_exists = false; } } - return array($data, $nested); + return parent::export() + array('key' => $this->_pathKey, 'remove' => $this->_removed); } public function update($id = null, array $data = array()) { + parent::update($id, $data); + foreach ($this->_data as $key => $val) { if (is_object($val) && method_exists($val, 'update')) { $this->_data[$key]->update(null, isset($data[$key]) ? $data[$key] : array()); } } - return parent::update($id, $data); + $this->_removed = array(); } /** @@ -257,8 +228,10 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { foreach ($path as $i => $key) { if (is_array($current)) { $current =& $current[$key]; - } else { + } elseif (isset($current->{$key})) { $current =& $current->{$key}; + } else { + return $null; } if (is_scalar($current) && $i < $length) { @@ -290,25 +263,34 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { if ($model = $this->_model) { $pathKey = $this->_pathKey; $options = compact('pathKey') + array('first' => true); - $value = $model::connection()->cast($model, array($name => $value), $options); + $value = $model::connection()->cast($this, array($name => $value), $options); } - $this->_data[$name] = $value; - $this->_modified[$name] = true; + $this->_updated[$name] = $value; + unset($this->_increment[$name], $this->_removed[$name]); } protected function _setNested($name, $value) { - $current = $this; + $current =& $this; $path = explode('.', $name); $length = count($path) - 1; for ($i = 0; $i < $length; $i++) { $key = $path[$i]; - $next = $current->__get($key); + + if (is_array($current) && isset($current[$key])) { + $next =& $current[$key]; + } elseif (isset($current->{$key})) { + $next =& $current->{$key}; + } else { + unset($next); + $next = null; + } if ($next === null && ($model = $this->_model)) { - $next = $current->_data[$key] = $model::connection()->item($model); + $current->__set($key, $model::connection()->item($model)); + $next =& $current->{$key}; } - $current = $next; + $current =& $next; } if (is_object($current)) { @@ -324,19 +306,25 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { * @return boolean True if the field specified in `$name` exists, false otherwise. */ public function __isset($name) { - return isset($this->_data[$name]); + $exists = isset($this->_data[$name]) || isset($this->_updated[$name]); + return ($exists && !isset($this->_removed[$name])); } /** * PHP magic method used when unset() is called on a `Document` instance. - * Use case for this would be when you wish to edit a document and remove a field, ie. : - * {{{ $doc = Post::find($id); unset($doc->fieldName); $doc->save(); }}} + * Use case for this would be when you wish to edit a document and remove a field, ie.: + * {{{ + * $doc = Post::find($id); + * unset($doc->fieldName); + * $doc->save(); + * }}} * - * @param unknown_type $name - * @return unknown_type + * @param string $name The name of the field to remove. + * @return void */ public function __unset($name) { - unset($this->_data[$name]); + $this->_removed[$name] = true; + unset($this->_updated[$name]); } /** @@ -437,13 +425,15 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { * @return mixed */ public function to($format, array $options = array()) { - $defaults = array('handlers' => array('MongoId' => function($value) { - return (string) $value; - })); + $defaults = array('handlers' => array( + 'MongoId' => function($value) { return (string) $value; }, + 'MongoDate' => function($value) { return $value->sec; } + )); if ($format == 'array') { $options += $defaults; - return Collection::toArray($this->_data, $options); + $data = array_merge($this->_data, $this->_updated); + return Collection::toArray(array_diff_key($data, $this->_removed), $options); } return parent::to($format, $options); } @@ -480,6 +470,32 @@ class Document extends \lithium\data\Entity implements \Iterator, \ArrayAccess { $map = function($rel) { return $rel->data(); }; return $this->to('array') + array_map($map, $this->_relationships); } + + /** + * Safely (atomically) increments the value of the specified field by an arbitrary value. + * Defaults to `1` if no value is specified. Throws an exception if the specified field is + * non-numeric. + * + * @param string $field The name of the field to be incrememnted. + * @param string $value The value to increment the field by. Defaults to `1` if this parameter + * is not specified. + * @return integer Returns the current value of `$field`, based on the value retrieved from the + * data source when the entity was loaded, plus any increments applied. Note that it + * may not reflect the most current value in the persistent backend data source. + * @throws UnexpectedValueException Throws an exception when `$field` is set to a non-numeric + * type. + */ + public function increment($field, $value = 1) { + if (!isset($this->_increment[$field])) { + $this->_increment[$field] = 0; + } + $this->_increment[$field] += $value; + + if (!is_numeric($this->_data[$field])) { + throw new UnexpectedValueException("Field `{$field}` cannot be incremented."); + } + $this->_data[$field] += $value; + } } ?> \ No newline at end of file diff --git a/libraries/lithium/data/entity/Record.php b/libraries/lithium/data/entity/Record.php index 800eb5c..821598d 100644 --- a/libraries/lithium/data/entity/Record.php +++ b/libraries/lithium/data/entity/Record.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -41,6 +41,9 @@ class Record extends \lithium\data\Entity { } } } + if (isset($this->_updated[$name])) { + return $this->_updated[$name]; + } if (isset($this->_data[$name])) { return $this->_data[$name]; } diff --git a/libraries/lithium/data/model/Query.php b/libraries/lithium/data/model/Query.php index 6875d53..0de3285 100644 --- a/libraries/lithium/data/model/Query.php +++ b/libraries/lithium/data/model/Query.php @@ -2,13 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\data\model; use lithium\data\Source; +use lithium\core\ConfigException; use lithium\data\model\QueryException; /** @@ -84,6 +85,7 @@ class Query extends \lithium\core\Object { 'calculate' => null, 'conditions' => array(), 'fields' => array(), + 'data' => array(), 'model' => null, 'alias' => null, 'source' => null, @@ -117,6 +119,12 @@ class Query extends \lithium\core\Object { if ($this->_config['with']) { $this->_associate($this->_config['with']); } + $joins = $this->_config['joins']; + $this->_config['joins'] = array(); + + foreach ($joins as $i => $join) { + $this->join($i, $join); + } if ($this->_entity && !$this->_config['model']) { $this->model($this->_entity->model()); } @@ -136,6 +144,7 @@ class Query extends \lithium\core\Object { * Generates a schema map of the query's result set, where the keys are fully-namespaced model * class names, and the values are arrays of field names. * + * @param array $map * @return array */ public function map($map = null) { @@ -361,11 +370,15 @@ class Query extends \lithium\core\Object { * @return array of query objects */ public function join($name = null, $join = null) { + if (is_scalar($name) && !$join && isset($this->_config['joins'][$name])) { + return $this->_config['joins'][$name]; + } if ($name && !$join) { $join = $name; $name = null; } if ($join) { + $join = is_array($join) ? $this->_instance(get_class($this), $join) : $join; $name ? $this->_config['joins'][$name] = $join : $this->_config['joins'][] = $join; return $this; } @@ -380,12 +393,12 @@ class Query extends \lithium\core\Object { * @return array Returns an array containing a data source-specific representation of a query. */ public function export(Source $dataSource, array $options = array()) { - $defaults = array('data' => array()); + $defaults = array('keys' => array()); $options += $defaults; - $keys = array_keys($this->_config); + $keys = $options['keys'] ?: array_keys($this->_config); $methods = $dataSource->methods(); - $results = array(); + $results = array('type' => $this->_type); $apply = array_intersect($keys, $methods); $copy = array_diff($keys, $apply); @@ -394,15 +407,19 @@ class Query extends \lithium\core\Object { $results[$item] = $dataSource->{$item}($this->{$item}(), $this); } foreach ($copy as $item) { - $results[$item] = $this->_config[$item]; + if (in_array($item, $keys)) { + $results[$item] = $this->_config[$item]; + } + } + if (in_array('data', $keys)) { + $results['data'] = $this->_exportData(); + } + if (isset($results['source'])) { + $results['source'] = $dataSource->name($results['source']); + } + if (!isset($results['fields'])) { + return $results; } - $entity =& $this->_entity; - $data = $entity ? $entity->export($dataSource, $options['data']) : $this->_data; - $data = ($list = $this->_config['whitelist']) ? array_intersect_key($data, $list) : $data; - $results = compact('data') + $results; - - $results['type'] = $this->_type; - $results['source'] = $dataSource->name($this->_config['source']); $created = array('fields', 'values'); if (is_array($results['fields']) && array_keys($results['fields']) == $created) { @@ -411,6 +428,32 @@ class Query extends \lithium\core\Object { return $results; } + /** + * Helper method used by `export()` to extract the data either from a bound entity, or from + * passed configuration, and filter it through a configured whitelist, if present. + * + * @return array + */ + protected function _exportData() { + $data = $this->_entity ? $this->_entity->export() : $this->_data; + + if (!$list = $this->_config['whitelist']) { + return $data; + } + $list = array_combine($list, $list); + + if (!$this->_entity) { + return array_intersect_key($data, $list); + } + foreach ($data as $type => $values) { + if (!is_array($values)) { + continue; + } + $data[$type] = array_intersect_key($values, $list); + } + return $data; + } + public function schema($field = null) { if (is_array($field)) { $this->_config['schema'] = $field; @@ -461,15 +504,22 @@ class Query extends \lithium\core\Object { * Will return a find first condition on the associated model if a record is connected. * Called by conditions when it is called as a get and no condition is set. * - * @return array ([model's primary key'] => [that key set in the record]). + * @return array Returns an array in the following format: + * `([model's primary key'] => [that key set in the record])`. */ protected function _entityConditions() { if (!$this->_entity || !($model = $this->_config['model'])) { return; } - if (is_array($key = $model::key($this->_entity->data()))) { + $key = $model::key($this->_entity->data()); + + if (!$key && $this->_type != "create") { + throw new ConfigException('No matching primary key found.'); + } + if (is_array($key)) { return $key; } + $key = $model::meta('key'); $val = $this->_entity->{$key}; return $val ? array($key => $val) : array(); @@ -487,7 +537,7 @@ class Query extends \lithium\core\Object { $config = array(); } if (!$relation = $model::relations($name)) { - throw new QueryException("Related model not found"); + throw new QueryException("Related model not found."); } $config += $relation->data(); } diff --git a/libraries/lithium/data/model/QueryException.php b/libraries/lithium/data/model/QueryException.php index e941d4d..b284542 100644 --- a/libraries/lithium/data/model/QueryException.php +++ b/libraries/lithium/data/model/QueryException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/data/model/Relationship.php b/libraries/lithium/data/model/Relationship.php index 22275fe..f615834 100644 --- a/libraries/lithium/data/model/Relationship.php +++ b/libraries/lithium/data/model/Relationship.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,6 +10,7 @@ namespace lithium\data\model; use lithium\core\Libraries; use lithium\util\Inflector; +use lithium\core\ClassNotFoundException; /** * The `Relationship` class encapsulates the data and functionality necessary to link two model @@ -57,7 +58,7 @@ class Relationship extends \lithium\core\Object { 'link' => self::LINK_KEY, 'fields' => true, 'fieldName' => null, - 'conditions' => null, + 'constraint' => array(), ); parent::__construct($config + $defaults); } @@ -96,7 +97,10 @@ class Relationship extends \lithium\core\Object { if (!($related = ($config['type'] == 'belongsTo') ? $config['to'] : $config['from'])) { return array(); } - return array_combine((array) $keys, (array) $related::key()); + if (class_exists($related)) { + return array_combine((array) $keys, (array) $related::key()); + } + throw new ClassNotFoundException("Related model class '{$related}' not found."); } } diff --git a/libraries/lithium/data/source/Database.php b/libraries/lithium/data/source/Database.php index aa58da2..2b4c3e1 100644 --- a/libraries/lithium/data/source/Database.php +++ b/libraries/lithium/data/source/Database.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -43,7 +43,7 @@ abstract class Database extends \lithium\data\Source { 'update' => "UPDATE {:source} SET {:fields} {:conditions};{:comment}", 'delete' => "DELETE {:flags} FROM {:source} {:alias} {:conditions};{:comment}", 'schema' => "CREATE TABLE {:source} (\n{:columns}{:indexes});{:comment}", - 'join' => "{:type} JOIN {:source} {:alias} {:constraint} {:conditions}" + 'join' => "{:type} JOIN {:source} {:alias} {:constraint}" ); /** @@ -71,7 +71,14 @@ abstract class Database extends \lithium\data\Source { '!=' => array('multiple' => 'NOT IN'), '<>' => array('multiple' => 'NOT IN'), 'between' => array('format' => 'BETWEEN ? AND ?'), - 'BETWEEN' => array('format' => 'BETWEEN ? AND ?') + 'BETWEEN' => array('format' => 'BETWEEN ? AND ?'), + 'like' => array(), + 'LIKE' => array() + ); + + protected $_constraintTypes = array( + 'AND' => true, + 'OR' => true ); /** @@ -241,7 +248,7 @@ abstract class Database extends \lithium\data\Source { * @filter */ public function read($query, array $options = array()) { - $defaults = array('return' => 'item', 'schema' => array()); + $defaults = array('return' => is_string($query) ? 'array' : 'item', 'schema' => array()); $options += $defaults; return $this->_filter(__METHOD__, compact('query', 'options'), function($self, $params) { @@ -266,8 +273,8 @@ abstract class Database extends \lithium\data\Source { while ($data = $result->next()) { // @hack: Fix this to support relationships - if ((count($columns) != count($data) && isset($columns[0])) || is_array($columns[0])) { - $columns = $columns[0]; + if (count($columns) != count($data) && is_array(current($columns))) { + $columns = current($columns); } $records[] = array_combine($columns, $data); } @@ -286,6 +293,7 @@ abstract class Database extends \lithium\data\Source { * @param object $query A `lithium\data\model\Query` object * @param array $options none * @return boolean + * @filter */ public function update($query, array $options = array()) { return $this->_filter(__METHOD__, compact('query', 'options'), function($self, $params) { @@ -311,6 +319,7 @@ abstract class Database extends \lithium\data\Source { * parameter values to be inserted into the query. * @return boolean Returns `true` on successful query execution (not necessarily if records are * deleted), otherwise `false`. + * @filter */ public function delete($query, array $options = array()) { return $this->_filter(__METHOD__, compact('query', 'options'), function($self, $params) { @@ -341,7 +350,9 @@ abstract class Database extends \lithium\data\Source { switch ($type) { case 'count': - $fields = $this->fields($query->fields(), $query); + if (strpos($fields = $this->fields($query->fields(), $query), ',') !== false) { + $fields = "*"; + } $query->fields("COUNT({$fields}) as count", true); $query->map(array($query->model() => array('count'))); list($record) = $this->read($query, $options)->data(); @@ -381,7 +392,7 @@ abstract class Database extends \lithium\data\Source { $type = $context->type(); } if (!isset($this->_strings[$type])) { - throw new InvalidArgumentException("Invalid query type '{$type}'"); + throw new InvalidArgumentException("Invalid query type `{$type}`."); } $data = array_filter($data); return trim(String::insert($this->_strings[$type], $data, array('clean' => true))); @@ -410,12 +421,16 @@ abstract class Database extends \lithium\data\Source { } $namespace = preg_replace('/\w+$/', '', $model); $relations = $model ? $model::relations() : array(); + $schema = $model::schema(); foreach ($fields as $scope => $field) { switch (true) { case (is_numeric($scope) && $field == '*'): $result[$model] = array_keys($model::schema()); break; + case (is_numeric($scope) && isset($schema[$field])): + $result[$model][] = $field; + break; case (is_numeric($scope) && isset($relations[$field])): $scope = $field; case (in_array($scope, $relations, true) && $field == '*'): @@ -465,34 +480,59 @@ abstract class Database extends \lithium\data\Source { foreach ($conditions as $key => $value) { $schema[$key] = isset($schema[$key]) ? $schema[$key] : array(); + $return = $this->_processConditions($key,$value, $schema); - switch (true) { - case (is_numeric($key) && is_string($value)): - $result[] = $value; - break; - case (is_string($key) && is_object($value)): - $value = trim(rtrim($this->renderCommand($value), ';')); - $result[] = "{$key} IN ({$value})"; - break; - case (is_string($key) && is_array($value) && isset($ops[key($value)])): - foreach ($value as $op => $val) { - $result[] = $this->_operator($key, array($op => $val), $schema[$key]); - } - break; - case (is_string($key) && is_array($value)): - $value = join(', ', $this->value($value, $schema)); - $result[] = "{$key} IN ({$value})"; - break; - default: - $value = $this->value($value, $schema); - $result[] = "{$key} = {$value}"; - break; + if ($return) { + $result[] = $return; } } $result = join(" AND ", $result); return ($options['prepend'] && !empty($result)) ? "WHERE {$result}" : $result; } + protected function _processConditions($key, $value, $schema, $glue = 'AND'){ + $constraintTypes = &$this->_constraintTypes; + + switch (true) { + case (is_numeric($key) && is_string($value)): + return $value; + case is_string($value): + return $this->name($key) . ' = ' . $this->value($value); + case is_numeric($key) && is_array($value): + $result = array(); + foreach($value as $cField => $cValue) { + $result[] = $this->_processConditions($cField, $cValue, $schema, $glue); + } + return '(' . implode(' ' . $glue . ' ', $result) . ')'; + case (is_string($key) && is_object($value)): + $value = trim(rtrim($this->renderCommand($value), ';')); + return "{$key} IN ({$value})"; + case is_array($value) && isset($constraintTypes[strtoupper($key)]): + $result = array(); + $glue = strtoupper($key); + + foreach($value as $cField => $cValue) { + $result[] = $this->_processConditions($cField, $cValue, $schema, $glue); + } + return '(' . implode(' ' . $glue . ' ', $result) . ')'; + case (is_string($key) && is_array($value) && isset($this->_operators[key($value)])): + foreach ($value as $op => $val) { + $result[] = $this->_operator($key, array($op => $val), $schema[$key]); + } + return '(' . implode(' ' . $glue . ' ', $result) . ')'; + case is_array($value): + $value = join(', ', $this->value($value, $schema)); + return "{$key} IN ({$value})"; + default: + if (isset($value)) { + $value = $this->value($value, $schema); + return "{$key} = {$value}"; + } + break; + } + return false; + } + /** * Returns either a formatted string for a select query, or an array of key/value pairs for a * create or update query. @@ -630,7 +670,7 @@ abstract class Database extends \lithium\data\Source { return $alias ? "AS " . $this->name($alias) : null; } - public function cast($model, array $data, array $options = array()) { + public function cast($entity, array $data, array $options = array()) { return $data; } @@ -651,10 +691,6 @@ abstract class Database extends \lithium\data\Source { while (list($field, $value) = each($data)) { $schema += array($field => array('default' => null)); - - if ($value === null && $schema[$field]['default'] === null) { - continue; - } $fields[] = $this->name($field) . ' = ' . $this->value($value, $schema[$field]); } return join(', ', $fields); diff --git a/libraries/lithium/data/source/Http.php b/libraries/lithium/data/source/Http.php index 4952f6d..e283031 100644 --- a/libraries/lithium/data/source/Http.php +++ b/libraries/lithium/data/source/Http.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -108,6 +108,7 @@ class Http extends \lithium\data\Source { * @param string $method * @param array $params * @return mixed + * @filter */ public function __call($method, $params) { if (isset($this->_config['methods'][$method])) { @@ -180,6 +181,7 @@ class Http extends \lithium\data\Source { * @param object $query * @param array $options * @return void + * @filter */ public function create($query, array $options = array()) { $params = compact('query', 'options'); @@ -212,6 +214,7 @@ class Http extends \lithium\data\Source { * @param object $query * @param array $options * @return string + * @filter */ public function read($query, array $options = array()) { $params = compact('query', 'options'); @@ -246,6 +249,7 @@ class Http extends \lithium\data\Source { * @param object $query * @param array $options * @return string + * @filter */ public function update($query, array $options = array()) { $params = compact('query', 'options'); @@ -278,6 +282,7 @@ class Http extends \lithium\data\Source { * @param object $query * @param array $options * @return string + * @filter */ public function delete($query, array $options = array()) { $params = compact('query', 'options'); diff --git a/libraries/lithium/data/source/Mock.php b/libraries/lithium/data/source/Mock.php index 3b64946..970fd59 100644 --- a/libraries/lithium/data/source/Mock.php +++ b/libraries/lithium/data/source/Mock.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/data/source/MongoDb.php b/libraries/lithium/data/source/MongoDb.php index f008d8a..99ca04f 100644 --- a/libraries/lithium/data/source/MongoDb.php +++ b/libraries/lithium/data/source/MongoDb.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -14,6 +14,7 @@ use MongoCode; use MongoDate; use MongoDBRef; use MongoRegex; +use MongoBinData; use MongoGridFSFile; use lithium\util\Inflector; use lithium\core\NetworkException; @@ -62,11 +63,12 @@ class MongoDb extends \lithium\data\Source { * @var array */ protected $_classes = array( - 'entity' => 'lithium\data\entity\Document', - 'array' => 'lithium\data\collection\DocumentArray', - 'set' => 'lithium\data\collection\DocumentSet', - 'result' => 'lithium\data\source\mongo_db\Result', - 'relationship' => 'lithium\data\model\Relationship' + 'entity' => 'lithium\data\entity\Document', + 'array' => 'lithium\data\collection\DocumentArray', + 'set' => 'lithium\data\collection\DocumentSet', + 'result' => 'lithium\data\source\mongo_db\Result', + 'exporter' => 'lithium\data\source\mongo_db\Exporter', + 'relationship' => 'lithium\data\model\Relationship', ); /** @@ -168,12 +170,15 @@ class MongoDb extends \lithium\data\Source { return is_string($v) && preg_match('/^[0-9a-f]{24}$/', $v) ? new MongoId($v) : $v; }, 'date' => function($v) { - return new MongoDate(is_numeric($v) ? intval($v) : strtotime($v)); + $v = is_numeric($v) ? intval($v) : strtotime($v); + return (!$v || time() == $v) ? new MongoDate() : new MongoDate($v); }, 'regex' => function($v) { return new MongoRegex($v); }, 'integer' => function($v) { return (integer) $v; }, 'float' => function($v) { return (float) $v; }, - 'boolean' => function($v) { return (boolean) $v; } + 'boolean' => function($v) { return (boolean) $v; }, + 'code' => function($v) { return new MongoCode($v); }, + 'binary' => function($v) { return new MongoBinData($v); }, ); } @@ -356,39 +361,44 @@ class MongoDb extends \lithium\data\Source { * @param string $query * @param string $options * @return boolean + * @filter */ public function create($query, array $options = array()) { + $defaults = array('safe' => false, 'fsync' => false); + $options += $defaults; $this->_checkConnection(); + $params = compact('query', 'options'); $_config = $this->_config; + $_exp = $this->_classes['exporter']; - return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config) { - $query = $params['query']; + return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config, $_exp) { + $query = $params['query']; $options = $params['options']; - $data = array(); - $params = $query->export($self); - $gridCol = "{$_config['gridPrefix']}.files"; + $args = $query->export($self, array('keys' => array('source', 'data'))); + $data = $_exp::get('create', $args['data']); + $source = $args['source']; - if ($query->source() == $gridCol && isset($params['data']['file'])) { + if ($source == "{$_config['gridPrefix']}.files" && isset($data['create']['file'])) { $result = array('ok' => true); - $data['_id'] = $self->invokeMethod('_saveFile', array($params)); + $data['create']['_id'] = $self->invokeMethod('_saveFile', array($data['create'])); } else { - $result = $self->connection->{$params['source']}->insert($params['data'], true); + $result = $self->connection->{$source}->insert($data['create'], $options); } - if (isset($result['ok']) && (boolean) $result['ok'] === true) { - $params['data'] += $data; - $query->entity()->update($params['data']['_id']); + if ($result === true || isset($result['ok']) && (boolean) $result['ok'] === true) { + if ($query->entity()) { + $query->entity()->update($data['create']['_id']); + } return true; } return false; }); } - protected function _saveFile($params) { + protected function _saveFile($data) { $uploadKeys = array('name', 'type', 'tmp_name', 'error', 'size'); - $data = $params['data']; $grid = $this->connection->getGridFS(); $file = null; $method = null; @@ -398,7 +408,7 @@ class MongoDb extends \lithium\data\Source { if (!$data['file']['error'] && is_uploaded_file($data['file']['tmp_name'])) { $method = 'storeFile'; $file = $data['file']['tmp_name']; - $data += array('filename' => $data['file']['name']); + $data['filename'] = $data['file']['name']; } break; case (is_string($data['file']) && file_exists($data['file'])): @@ -429,6 +439,7 @@ class MongoDb extends \lithium\data\Source { * @param string $query * @param string $options * @return object + * @filter */ public function read($query, array $options = array()) { $this->_checkConnection(); @@ -442,7 +453,6 @@ class MongoDb extends \lithium\data\Source { $query = $params['query']; $options = $params['options']; $args = $query->export($self); - $self->connection->resetError(); $source = $args['source']; if ($group = $args['group']) { @@ -484,27 +494,37 @@ class MongoDb extends \lithium\data\Source { * @param string $query * @param array $options * @return boolean + * @filter */ public function update($query, array $options = array()) { - $this->_checkConnection(); - $defaults = array('atomic' => true); + $defaults = array('upsert' => false, 'multiple' => true, 'safe' => false, 'fsync' => false); $options += $defaults; + $this->_checkConnection(); + $params = compact('query', 'options'); $_config = $this->_config; + $_exp = $this->_classes['exporter']; - return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config) { - $query = $params['query']; + return $this->_filter(__METHOD__, $params, function($self, $params) use ($_config, $_exp) { $options = $params['options']; - $args = $query->export($self, $options); - $gridCol = "{$_config['gridPrefix']}.files"; + $query = $params['query']; + $args = $query->export($self, array('keys' => array('conditions', 'source', 'data'))); + $source = $args['source']; + $data = $args['data']; - if ($args['source'] == $gridCol && isset($args['data']['file'])) { - $args['data']['_id'] = $self->invokeMethod('_saveFile', array($args)); + if ($query->entity()) { + $data = $_exp::get('update', $data); } - unset($args['data']['_id']); - $update = ($options['atomic']) ? array('$set' => $args['data']) : $args['data']; - if ($self->connection->{$args['source']}->update($args['conditions'], $update)) { + if ($source == "{$_config['gridPrefix']}.files" && isset($data['update']['file'])) { + $args['data']['_id'] = $self->invokeMethod('_saveFile', array($data['update'])); + } + $update = $query->entity() ? $_exp::toCommand($data) : $data; + + if ($options['multiple'] && !preg_grep('/^\$/', array_keys($update))) { + $update = array('$set' => $update); + } + if ($self->connection->{$source}->update($args['conditions'], $update, $options)) { $query->entity() ? $query->entity()->update() : null; return true; } @@ -518,6 +538,7 @@ class MongoDb extends \lithium\data\Source { * @param string $query * @param string $options * @return boolean + * @filter */ public function delete($query, array $options = array()) { $this->_checkConnection(); @@ -527,7 +548,7 @@ class MongoDb extends \lithium\data\Source { return $this->_filter(__METHOD__, compact('query', 'options'), function($self, $params) { $query = $params['query']; $options = $params['options']; - $args = $query->export($self); + $args = $query->export($self, array('keys' => array('source', 'conditions'))); return $self->connection->{$args['source']}->remove($args['conditions'], $options); }); } @@ -641,7 +662,7 @@ class MongoDb extends \lithium\data\Source { continue; } if (!is_array($value)) { - $conditions[$key] = $this->cast($model, array($key => $value), $castOpts); + $conditions[$key] = $this->cast(null, array($key => $value), $castOpts); continue; } $current = key($value); @@ -751,56 +772,32 @@ class MongoDb extends \lithium\data\Source { return $order ?: array(); } - public function cast($model, array $data, array $options = array()) { - $defaults = array('schema' => null, 'first' => false, 'pathKey' => null, 'arrays' => true); + public function cast($entity, array $data, array $options = array()) { + $defaults = array('schema' => null, 'first' => false, 'pathKey' => null); $options += $defaults; + $model = null; - if ($model && !$options['schema']) { - $options['schema'] = $model::schema() ?: array('_id' => array('type' => 'id')); + if (!$data) { + return $data; } - $schema = $options['schema']; - unset($options['schema']); - - $typeMap = array( - 'MongoId' => 'id', - 'MongoDate' => 'date', - 'datetime' => 'date', - 'timestamp' => 'date', - 'int' => 'integer' - ); - foreach ($data as $key => $value) { - if (is_object($value)) { - continue; - } - $path = $options['pathKey'] ? "{$options['pathKey']}.{$key}" : $key; - $field = (isset($schema[$path]) ? $schema[$path] : array()); - $field += array('type' => null, 'array' => null); - $type = isset($typeMap[$field['type']]) ? $typeMap[$field['type']] : $field['type']; - $isObject = ($type == 'object'); - $isArray = (is_array($value) && $field['array'] !== false && !$isObject); - - if (isset($this->_handlers[$type])) { - $handler = $this->_handlers[$type]; - $value = $isArray ? array_map($handler, $value) : $handler($value); - } - if (!$options['arrays']) { - $data[$key] = $value; - continue; - } - $pathKey = $path; - - if (is_array($value)) { - $arrayType = !$isObject && (array_keys($value) === range(0, count($value) - 1)); - $opts = $arrayType ? array('class' => 'array') + $options : $options; - $value = $this->item($model, $value, compact('pathKey') + $opts); - } elseif ($field['array']) { - $opts = array('class' => 'array') + $options; - $value = $this->item($model, array($value), compact('pathKey') + $opts); - } - $data[$key] = $value; + if (is_string($entity)) { + $model = $entity; + $entity = null; + $options['schema'] = $options['schema'] ?: $model::schema(); + } + if ($entity && !$options['schema']) { + $options['schema'] = $entity->schema(); } - return $options['first'] ? reset($data) : $data; + if ($entity) { + $model = $entity->model(); + } + $schema = $options['schema'] ?: array('_id' => array('type' => 'id')); + unset($options['schema']); + $exporter = $this->_classes['exporter']; + $options += compact('model') + array('handlers' => $this->_handlers); + + return parent::cast($entity, $exporter::cast($data, $schema, $this, $options), $options); } protected function _checkConnection() { diff --git a/libraries/lithium/data/source/database/Result.php b/libraries/lithium/data/source/database/Result.php index 4373267..2639fa9 100644 --- a/libraries/lithium/data/source/database/Result.php +++ b/libraries/lithium/data/source/database/Result.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/data/source/database/adapter/MySql.php b/libraries/lithium/data/source/database/adapter/MySql.php old mode 100755 new mode 100644 index 8533658..0f99f88 --- a/libraries/lithium/data/source/database/adapter/MySql.php +++ b/libraries/lithium/data/source/database/adapter/MySql.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -128,7 +128,7 @@ class MySql extends \lithium\data\source\Database { } else { $this->connection = mysql_pconnect($host, $config['login'], $config['password']); } - + if (!$this->connection) { return false; } @@ -138,7 +138,7 @@ class MySql extends \lithium\data\source\Database { } else { return false; } - + if ($config['encoding']) { $this->encoding($config['encoding']); } @@ -271,10 +271,10 @@ class MySql extends \lithium\data\source\Database { } $result = array(); - $count = mysql_num_fields($resource); + $count = mysql_num_fields($resource->resource()); for ($i = 0; $i < $count; $i++) { - $result[] = mysql_field_name($resource, $i); + $result[] = mysql_field_name($resource->resource(), $i); } return $result; } @@ -301,9 +301,9 @@ class MySql extends \lithium\data\source\Database { /** * @todo Eventually, this will need to rewrite aliases for DELETE and UPDATE queries, same with * order(). - * @param string $conditions - * @param string $context - * @param array $options + * @param string $conditions + * @param string $context + * @param array $options * @return void */ public function conditions($conditions, $context, array $options = array()) { @@ -320,6 +320,7 @@ class MySql extends \lithium\data\source\Database { * sends the SQL query query to MySQL without automatically fetching and buffering the * result rows as `mysql_query()` does (for less memory usage). * @return resource Returns the result resource handle if the query is successful. + * @filter */ protected function _execute($sql, array $options = array()) { $defaults = array('buffered' => true); diff --git a/libraries/lithium/data/source/database/adapter/Sqlite3.php b/libraries/lithium/data/source/database/adapter/Sqlite3.php old mode 100755 new mode 100644 index e381951..f815286 --- a/libraries/lithium/data/source/database/adapter/Sqlite3.php +++ b/libraries/lithium/data/source/database/adapter/Sqlite3.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License * */ @@ -20,6 +20,21 @@ use lithium\data\model\QueryException; */ class Sqlite3 extends \lithium\data\source\Database { + protected $_classes = array( + 'entity' => 'lithium\data\entity\Record', + 'set' => 'lithium\data\collection\RecordSet', + 'relationship' => 'lithium\data\model\Relationship', + 'result' => 'lithium\data\source\database\adapter\sqlite3\Result' + ); + + /** + * Pair of opening and closing quote characters used for quoting identifiers in queries. + * + * @link http://www.sqlite.org/lang_keywords.html + * @var array + */ + protected $_quotes = array('"', '"'); + /** * Sqlite column type definitions. * @@ -42,6 +57,17 @@ class Sqlite3 extends \lithium\data\source\Database { ); /** + * Holds commonly regular expressions used in this class. + * + * @see lithium\data\source\database\adapter\Sqlite3::describe() + * @see lithium\data\source\database\adapter\Sqlite3::_column() + * @var array + */ + protected $_regex = array( + 'column' => '(?P<type>[^(]+)(?:\((?P<length>[^)]+)\))?' + ); + + /** * Constructs the Sqlite adapter * * @see lithium\data\source\Database::__construct() @@ -63,25 +89,42 @@ class Sqlite3 extends \lithium\data\source\Database { public function __construct(array $config = array()) { $defaults = array( 'database' => '', - 'flags' => NULL, + 'flags' => SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, 'key' => NULL ); parent::__construct($config + $defaults); } /** + * Check for required PHP extension, or supported database feature. + * + * @param string $feature Test for support for a specific feature, i.e. `'transactions'`. + * @return boolean Returns `true` if the particular feature (or if Sqlite) support is enabled, + * otherwise `false`. + */ + public static function enabled($feature = null) { + if (!$feature) { + return extension_loaded('sqlite3'); + } + $features = array( + 'arrays' => false, + 'transactions' => false, + 'booleans' => true, + 'relationships' => true + ); + return isset($features[$feature]) ? $features[$feature] : null; + } + + /** * Connects to the database using options provided to the class constructor. * * @return boolean True if the database could be connected, else false */ public function connect() { - $config = $this->_config; - $this->_isConnected = false; - - if ($this->connection = new SQLite($config['database'], $config['flags'], $config['key'])) { - $this->_isConnected = true; - } - return $this->_isConnected; + $this->connection = new SQLite( + $this->_config['database'], $this->_config['flags'], $this->_config['key'] + ); + return $this->_isConnected = (boolean) $this->connection; } /** @@ -90,11 +133,7 @@ class Sqlite3 extends \lithium\data\source\Database { * @return boolean True on success, else false. */ public function disconnect() { - if ($this->_isConnected) { - $this->_isConnected = !$this->connection->close(); - return !$this->_isConnected; - } - return true; + return !$this->_isConnected || !($this->_isConnected = !$this->connection->close()); } /** @@ -115,6 +154,12 @@ class Sqlite3 extends \lithium\data\source\Database { /** * Gets the column schema for a given Sqlite3 table. * + * A column type may not always be available, i.e. when during creation of + * the column no type was declared. Those columns are internally treated + * by SQLite3 as having a `NONE` affinity. The final schema will contain no + * information about type and length of such columns (both values will be + * `null`). + * * @param mixed $entity Specifies the table name for which the schema should be returned, or * the class name of the model object requesting the schema, in which case the model * class will be queried for the correct table name. @@ -135,13 +180,13 @@ class Sqlite3 extends \lithium\data\source\Database { $fields = array(); foreach ($columns as $column) { - list($type, $length) = explode('(', $column['type']); - $length = trim($length, ')'); + preg_match("/{$this->_regex['column']}/", $column['type'], $matches); + $fields[$column['name']] = array( - 'type' => $type, - 'length' => $length, - 'null' => ($column['notnull'] == 1 ? true : false), - 'default' => $column['dflt_value'], + 'type' => isset($matches['type']) ? $matches['type'] : null, + 'length' => isset($matches['length']) ? $matches['length'] : null, + 'null' => $column['notnull'] == 1, + 'default' => $column['dflt_value'] ); } return $fields; @@ -154,7 +199,9 @@ class Sqlite3 extends \lithium\data\source\Database { * @param object $query The given query, usually an instance of `lithium\data\model\Query`. * @return void */ - protected function _insertId($query) {} + protected function _insertId($query) { + return $this->connection->lastInsertRowID(); + } /** * Gets or sets the encoding for the connection. @@ -179,6 +226,7 @@ class Sqlite3 extends \lithium\data\source\Database { /** * Converts a given value into the proper type based on a given schema definition. * + * @link http://www.sqlite.org/lang_keywords.html * @see lithium\data\source\Database::schema() * @param mixed $value The value to be converted. Arrays will be recursively converted. * @param array $schema Formatted array from `lithium\data\source\Database::schema()` @@ -188,7 +236,7 @@ class Sqlite3 extends \lithium\data\source\Database { if (is_array($value)) { return parent::value($value, $schema); } - return $this->connection->escapeString($value); + return "'" . $this->connection->escapeString($value) . "'"; } /** @@ -223,19 +271,6 @@ class Sqlite3 extends \lithium\data\source\Database { if ($this->connection->lastErrorMsg()) { return array($this->connection->lastErrorCode(), $this->connection->lastErrorMsg()); } - return null; - } - - /** - * Quotes identifiers. - * - * Currently, this method simply returns the identifier. - * - * @param string $name The identifier to quote. - * @return string The quoted identifier. - */ - public function name($name) { - return $name; } /** @@ -245,6 +280,7 @@ class Sqlite3 extends \lithium\data\source\Database { * @param string $sql The sql string to execute * @param array $options No available options. * @return resource + * @filter */ protected function _execute($sql, array $options = array()) { $params = compact('sql', 'options'); @@ -253,11 +289,11 @@ class Sqlite3 extends \lithium\data\source\Database { return $this->_filter(__METHOD__, $params, function($self, $params) use (&$conn) { extract($params); - if (!($result = $conn->query($sql)) instanceof SQLite3Result) { + if (!($resource = $conn->query($sql)) instanceof SQLite3Result) { list($code, $error) = $self->error(); - throw new QueryException("$sql: $error", $code); + throw new QueryException("{$sql}: {$error}", $code); } - return $result; + return $self->invokeMethod('_instance', array('result', compact('resource'))); }); } @@ -272,7 +308,7 @@ class Sqlite3 extends \lithium\data\source\Database { return $real['type'] . (isset($real['length']) ? "({$real['length']})" : ''); } - if (!preg_match('/(?P<type>[^(]+)(?:\((?P<length>[^)]+)\))?/', $real, $column)) { + if (!preg_match("/{$this->_regex['column']}/", $real, $column)) { return $real; } $column = array_intersect_key($column, array('type' => null, 'length' => null)); diff --git a/libraries/lithium/data/source/database/adapter/my_sql/Result.php b/libraries/lithium/data/source/database/adapter/my_sql/Result.php index 021dd94..74b5c42 100644 --- a/libraries/lithium/data/source/database/adapter/my_sql/Result.php +++ b/libraries/lithium/data/source/database/adapter/my_sql/Result.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/data/source/database/adapter/sqlite3/Result.php b/libraries/lithium/data/source/database/adapter/sqlite3/Result.php index 6986a49..c3c75f4 100644 --- a/libraries/lithium/data/source/database/adapter/sqlite3/Result.php +++ b/libraries/lithium/data/source/database/adapter/sqlite3/Result.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -12,18 +12,32 @@ use SQLite3Result; class Result extends \lithium\data\source\database\Result { + protected function _prev() { + if ($this->_resource->reset()) { + return $this->_next(); + } + } + protected function _next() { - if (!$this->_resource instanceof SQLite3Result) { - return; + if ($this->_resource instanceof SQLite3Result) { + return $this->_resource->fetchArray(SQLITE3_ASSOC); } - return $resource->fetchArray(SQLITE3_ASSOC); } protected function _close() { + if ($this->_resource instanceof SQLite3Result) { + $this->_resource->finalize(); + } + } + + public function __call($name, $arguments) { if (!$this->_resource instanceof SQLite3Result) { return; } - $resource->finalize(); + + if (is_callable(array($this->_resource, $name))) { + return call_user_func_array(array(&$this->_resource, $name), $arguments); + } } } diff --git a/libraries/lithium/data/source/http/adapter/CouchDb.php b/libraries/lithium/data/source/http/adapter/CouchDb.php index 5d1944f..65d1013 100644 --- a/libraries/lithium/data/source/http/adapter/CouchDb.php +++ b/libraries/lithium/data/source/http/adapter/CouchDb.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -11,21 +11,25 @@ namespace lithium\data\source\http\adapter; use lithium\core\ConfigException; /** - * CouchDb adapter + * A data source adapter which allows you to connect to Apache CouchDB. * + * By default, it will attempt to connect to the CouchDB running on `localhost` on port + * 5984 using HTTP version 1.0. + * + * @link http://couchdb.apache.org */ class CouchDb extends \lithium\data\source\Http { /** - * increment value of current result set loop - * used by `result` to handle rows of json responses + * Increment value of current result set loop + * used by `result` to handle rows of json responses. * * @var string */ protected $_iterator = 0; /** - * True if Database exists + * True if Database exists. * * @var boolean */ @@ -40,12 +44,13 @@ class CouchDb extends \lithium\data\source\Http { 'service' => 'lithium\net\http\Service', 'entity' => 'lithium\data\entity\Document', 'set' => 'lithium\data\collection\DocumentSet', + 'array' => 'lithium\data\collection\DocumentArray', ); protected $_handlers = array(); /** - * Constructor + * Constructor. * * @param array $config * @return void @@ -100,7 +105,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * Magic for passing methods to http service + * Magic for passing methods to http service. * * @param string $method * @param string $params @@ -112,7 +117,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * entities + * Entities. * * @param object $class * @return void @@ -121,7 +126,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * Describe database, create if it does not exist + * Describe database, create if it does not exist. * * @param string $entity * @param string $meta @@ -148,7 +153,7 @@ class CouchDb extends \lithium\data\source\Http { } } if (!$this->_db) { - throw new ConfigException("{$entity} is not available."); + throw new ConfigException("Database `{$entity}` is not available."); } } @@ -165,11 +170,12 @@ class CouchDb extends \lithium\data\source\Http { } /** - * Create new document + * Create new document. * * @param string $query * @param string $options * @return boolean + * @filter */ public function create($query, array $options = array()) { $defaults = array('model' => $query->model()); @@ -185,6 +191,9 @@ class CouchDb extends \lithium\data\source\Http { $data = $query->data(); $data += array('type' => $options['model']::meta('source')); + if (isset($data['id'])) { + return $self->update($query, $options); + } $result = $conn->post($config['database'], $data, $request); $result = is_string($result) ? json_decode($result, true) : $result; @@ -198,11 +207,12 @@ class CouchDb extends \lithium\data\source\Http { } /** - * Read from document + * Read from document. * * @param string $query * @param string $options * @return object + * @filter */ public function read($query, array $options = array()) { $defaults = array('return' => 'resource', 'model' => $query->model()); @@ -222,29 +232,35 @@ class CouchDb extends \lithium\data\source\Http { $_path = '_all_docs'; $conditions['include_docs'] = 'true'; } + $path = "{$config['database']}/{$_path}"; + $args = (array) $conditions + (array) $limit + (array) $order; - $result = json_decode($conn->get("{$config['database']}/{$_path}", $args), true); - $data = array(); + $result = (array) json_decode($conn->get($path, $args), true); - if (isset($result['rows'])) { - $data = $result['rows']; + $data = $stats = array(); + + if (isset($result['_id'])) { + $data = array($result); + } elseif (isset($result['rows'])) { + $data = array_map(function($row) { return $row['value']; }, $result['rows']); + + unset($result['rows']); + $stats = $result; } - unset($result['rows']); - $stats = $result + array('total_rows' => null, 'offset' => null); - $data = array_map(function($i) { return $i['doc']; }, $data); + $stats += array('total_rows' => null, 'offset' => null); $opts = compact('stats') + array('class' => 'set', 'exists' => true); - return $self->item($query->model(), $data, $opts); }); } /** - * Update document + * Update document. * * @param string $query * @param string $options * @return boolean + * @filter */ public function update($query, array $options = array()) { $params = compact('query', 'options'); @@ -281,11 +297,12 @@ class CouchDb extends \lithium\data\source\Http { } /** - * Delete document + * Delete document. * * @param string $query * @param string $options * @return boolean + * @filter */ public function delete($query, array $options = array()) { $params = compact('query', 'options'); @@ -356,7 +373,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * get result + * Get result. * * @param string $type * @param string $resource @@ -390,7 +407,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * handle conditions + * Handle conditions. * * @param string $conditions * @param string $context @@ -419,7 +436,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * fields for query + * Fields for query. * * @param string $fields * @param string $context @@ -430,7 +447,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * limit for query + * Limit for query. * * @param string $limit * @param string $context @@ -441,7 +458,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * order for query + * Order for query. * * @param string $order * @param string $context @@ -474,7 +491,7 @@ class CouchDb extends \lithium\data\source\Http { } /** - * Formats a CouchDb result set into a standard result to be passed to item + * Formats a CouchDb result set into a standard result to be passed to item. * * @param string $data data returned from query * @param string $options diff --git a/libraries/lithium/data/source/mongo_db/Exporter.php b/libraries/lithium/data/source/mongo_db/Exporter.php new file mode 100644 index 0000000..736212a --- /dev/null +++ b/libraries/lithium/data/source/mongo_db/Exporter.php @@ -0,0 +1,164 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\data\source\mongo_db; + +use lithium\util\Set; + +class Exporter extends \lithium\core\StaticObject { + + protected static $_map = array( + 'MongoId' => 'id', + 'MongoDate' => 'date', + 'MongoCode' => 'code', + 'MongoBinData' => 'binary', + 'datetime' => 'date', + 'timestamp' => 'date', + 'int' => 'integer', + ); + + public static function get($type, $export, array $options = array()) { + $defaults = array('whitelist' => array()); + $options += $defaults; + + if (!method_exists(get_called_class(), $method = "_{$type}") || !$export) { + return; + } + return static::$method($export, array('finalize' => true) + $options); + } + + public static function cast($data, $schema, $database, array $options = array()) { + $defaults = array( + 'pathKey' => null, 'handlers' => array(), 'model' => null, 'arrays' => true + ); + $options += $defaults; + $typeMap = static::$_map; + + foreach ($data as $key => $value) { + if (is_object($value)) { + continue; + } + $path = $options['pathKey'] ? "{$options['pathKey']}.{$key}" : $key; + $field = (isset($schema[$path]) ? $schema[$path] : array()); + $field += array('type' => null, 'array' => null); + $type = isset($typeMap[$field['type']]) ? $typeMap[$field['type']] : $field['type']; + + $isObject = ($type == 'object'); + $isArray = (is_array($value) && $field['array'] !== false && !$isObject); + $isArray = $field['array'] || $isArray; + + if (isset($options['handlers'][$type]) && $handler = $options['handlers'][$type]) { + $value = $isArray ? array_map($handler, (array) $value) : $handler($value); + } + if (!$options['arrays']) { + $data[$key] = $value; + continue; + } + $pathKey = $path; + + if (!is_array($value) && !$field['array']) { + $data[$key] = $value; + continue; + } + + if ($field['array']) { + $opts = array('class' => 'array') + $options; + $value = ($value === null) ? array() : $value; + $value = is_array($value) ? $value : array($value); + } elseif (is_array($value)) { + $arrayType = !$isObject && (array_keys($value) === range(0, count($value) - 1)); + $opts = $arrayType ? array('class' => 'array') + $options : $options; + } + $data[$key] = $database->item($options['model'], $value, compact('pathKey') + $opts); + } + return $data; + } + + public static function toCommand($changes) { + $map = array( + 'create' => null, + 'update' => '$set', + 'increment' => '$inc', + 'remove' => '$unset', + 'rename' => '$rename', + ); + $result = array(); + + foreach ($map as $from => $to) { + if (!isset($changes[$from])) { + continue; + } + if (!$to) { + $result = array_merge($result, $changes[$from]); + } + $result[$to] = $changes[$from]; + } + unset($result['$set']['_id']); + return $result; + } + + protected static function _create($export, array $options) { + $export += array('data' => array(), 'update' => array(), 'remove' => array(), 'key' => ''); + $data = array_merge($export['data'], $export['update']); + $data = array_diff_key($data, $export['remove']); + + $result = array('create' => array()); + $localOpts = array('finalize' => false) + $options; + + foreach ($data as $key => $val) { + if (is_object($val) && method_exists($val, 'export')) { + $data[$key] = static::_create($val->export($options), $localOpts); + } + } + return ($options['finalize']) ? array('create' => $data) : $data; + } + + protected static function _update($export) { + $export += array('update' => array(), 'remove' => array(), 'key' => ''); + $path = $export['key'] ? "{$export['key']}." : ""; + $data = $export['update']; + $result = array(); + + if (!$export['exists']) { + $data = array_merge($export['data'], $data); + } + $data = array_diff_key($data, $export['remove']); + $nested = array_diff_key($export['data'], $data); + + foreach ($export['remove'] as $key => $val) { + $result['remove']["{$path}{$key}"] = $val; + } + + foreach ($data as $key => $val) { + if (is_object($val) && method_exists($val, 'export')) { + $result = static::_appendObject($result, $path, $key, $val); + continue; + } + $result['update']["{$path}{$key}"] = $val; + } + + foreach (array_diff_key($nested, $export['remove']) as $key => $val) { + if (is_object($val) && method_exists($val, 'export')) { + $result = static::_appendObject($result, $path, $key, $val); + } + } + return $result; + } + + protected static function _appendObject($changes, $path, $key, $object) { + $options = array('finalize' => false); + + if ($object->exists()) { + return Set::merge($changes, static::_update($object->export())); + } + $changes['update']["{$path}{$key}"] = static::_create($object->export(), $options); + return $changes; + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/data/source/mongo_db/Result.php b/libraries/lithium/data/source/mongo_db/Result.php index 596ae9e..a6f86ed 100644 --- a/libraries/lithium/data/source/mongo_db/Result.php +++ b/libraries/lithium/data/source/mongo_db/Result.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/Catalog.php b/libraries/lithium/g11n/Catalog.php index df625e5..00998d2 100644 --- a/libraries/lithium/g11n/Catalog.php +++ b/libraries/lithium/g11n/Catalog.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/Locale.php b/libraries/lithium/g11n/Locale.php index 17bb845..8e9bbe9 100644 --- a/libraries/lithium/g11n/Locale.php +++ b/libraries/lithium/g11n/Locale.php @@ -2,14 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\g11n; -use \BadMethodCallException; -use \InvalidArgumentException; +use BadMethodCallException; +use InvalidArgumentException; /** * The `Locale` class provides methods to deal with locale identifiers. The locale @@ -70,7 +70,7 @@ class Locale extends \lithium\core\StaticObject { $tags = static::invokeMethod('decompose', $params); if (!isset(static::$_tags[$method])) { - throw new BadMethodCallException("Invalid locale tag `{$method}`"); + throw new BadMethodCallException("Invalid locale tag `{$method}`."); } return isset($tags[$method]) ? $tags[$method] : null; } @@ -109,7 +109,7 @@ class Locale extends \lithium\core\StaticObject { $regex .= '(?:[_-](?P<variant>[a-z]{5,}))?'; if (!preg_match("/^{$regex}$/i", $locale, $matches)) { - throw new InvalidArgumentException("Locale `{$locale}` could not be parsed"); + throw new InvalidArgumentException("Locale `{$locale}` could not be parsed."); } return array_filter(array_intersect_key($matches, static::$_tags)); } diff --git a/libraries/lithium/g11n/Message.php b/libraries/lithium/g11n/Message.php old mode 100755 new mode 100644 index c962e16..47c4970 --- a/libraries/lithium/g11n/Message.php +++ b/libraries/lithium/g11n/Message.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -187,7 +187,7 @@ class Message extends \lithium\core\StaticObject { $params = compact('id', 'count', 'locale', 'options'); $cache =& static::$_cachedPages; - return static::_filter(__METHOD__, $params, function($self, $params) use (&$cache) { + return static::_filter(__FUNCTION__, $params, function($self, $params) use (&$cache) { extract($params); if (!isset($cache[$options['scope']][$locale])) { diff --git a/libraries/lithium/g11n/catalog/Adapter.php b/libraries/lithium/g11n/catalog/Adapter.php index 0540858..ac66d14 100644 --- a/libraries/lithium/g11n/catalog/Adapter.php +++ b/libraries/lithium/g11n/catalog/Adapter.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/catalog/adapter/Code.php b/libraries/lithium/g11n/catalog/adapter/Code.php index ccbd7b0..ac02856 100644 --- a/libraries/lithium/g11n/catalog/adapter/Code.php +++ b/libraries/lithium/g11n/catalog/adapter/Code.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -48,9 +48,8 @@ class Code extends \lithium\g11n\catalog\Adapter { protected function _init() { parent::_init(); if (!is_dir($this->_config['path'])) { - throw new ConfigException( - "Code directory does not exist at `{$this->_config['path']}`" - ); + $message = "Code directory does not exist at path `{$this->_config['path']}`."; + throw new ConfigException($message); } } diff --git a/libraries/lithium/g11n/catalog/adapter/Gettext.php b/libraries/lithium/g11n/catalog/adapter/Gettext.php index 4ade215..e7a1b40 100644 --- a/libraries/lithium/g11n/catalog/adapter/Gettext.php +++ b/libraries/lithium/g11n/catalog/adapter/Gettext.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -92,7 +92,8 @@ class Gettext extends \lithium\g11n\catalog\Adapter { protected function _init() { parent::_init(); if (!is_dir($this->_config['path'])) { - throw new ConfigException("Gettext directory does not exist at `{$this->_config['path']}`"); + $message = "Gettext directory does not exist at path `{$this->_config['path']}`."; + throw new ConfigException($message); } } @@ -265,7 +266,7 @@ class Gettext extends \lithium\g11n\catalog\Adapter { $stat = fstat($stream); if ($stat['size'] < self::MO_HEADER_SIZE) { - throw new RangeException("MO stream caontent has an invalid format"); + throw new RangeException("MO stream content has an invalid format."); } $magic = unpack('V1', fread($stream, 4)); $magic = hexdec(substr(dechex(current($magic)), -8)); @@ -275,7 +276,7 @@ class Gettext extends \lithium\g11n\catalog\Adapter { } elseif ($magic == self::MO_BIG_ENDIAN_MAGIC) { $isBigEndian = true; } else { - throw new RangeException("MO stream content has an invalid format"); + throw new RangeException("MO stream content has an invalid format."); } $header = array( diff --git a/libraries/lithium/g11n/catalog/adapter/Memory.php b/libraries/lithium/g11n/catalog/adapter/Memory.php index 855c11c..efca6df 100644 --- a/libraries/lithium/g11n/catalog/adapter/Memory.php +++ b/libraries/lithium/g11n/catalog/adapter/Memory.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/catalog/adapter/Php.php b/libraries/lithium/g11n/catalog/adapter/Php.php index 8435e62..588f613 100644 --- a/libraries/lithium/g11n/catalog/adapter/Php.php +++ b/libraries/lithium/g11n/catalog/adapter/Php.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -71,7 +71,8 @@ class Php extends \lithium\g11n\catalog\Adapter { protected function _init() { parent::_init(); if (!is_dir($this->_config['path'])) { - throw new ConfigException("Php directory does not exist at `{$this->_config['path']}`"); + $message = "Php directory does not exist at path `{$this->_config['path']}`."; + throw new ConfigException($message); } } diff --git a/libraries/lithium/g11n/resources/php/da_DK/validation/default.php b/libraries/lithium/g11n/resources/php/da_DK/validation/default.php index 549b84a..51aa32e 100644 --- a/libraries/lithium/g11n/resources/php/da_DK/validation/default.php +++ b/libraries/lithium/g11n/resources/php/da_DK/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/de/message/default.php b/libraries/lithium/g11n/resources/php/de/message/default.php new file mode 100644 index 0000000..ffb813a --- /dev/null +++ b/libraries/lithium/g11n/resources/php/de/message/default.php @@ -0,0 +1,21 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +/** + * Message data for `de`. + * + * Plural rule and forms derived from the GNU gettext documentation. + * + * @link http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms + */ +return array( + 'pluralForms' => 2, + 'pluralRule' => function ($n) { return $n == 1 ? 0 : 1; } +); + +?> \ No newline at end of file diff --git a/libraries/lithium/g11n/resources/php/de_BE/validation/default.php b/libraries/lithium/g11n/resources/php/de_BE/validation/default.php index 885e31b..fe900fa 100644 --- a/libraries/lithium/g11n/resources/php/de_BE/validation/default.php +++ b/libraries/lithium/g11n/resources/php/de_BE/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/de_DE/validation/default.php b/libraries/lithium/g11n/resources/php/de_DE/validation/default.php index 889f4a0..2f4f190 100644 --- a/libraries/lithium/g11n/resources/php/de_DE/validation/default.php +++ b/libraries/lithium/g11n/resources/php/de_DE/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/en/message/default.php b/libraries/lithium/g11n/resources/php/en/message/default.php index 84d4ca9..c0d0148 100644 --- a/libraries/lithium/g11n/resources/php/en/message/default.php +++ b/libraries/lithium/g11n/resources/php/en/message/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/en_CA/validation/default.php b/libraries/lithium/g11n/resources/php/en_CA/validation/default.php index 5ef8f85..12c865d 100644 --- a/libraries/lithium/g11n/resources/php/en_CA/validation/default.php +++ b/libraries/lithium/g11n/resources/php/en_CA/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/en_GB/validation/default.php b/libraries/lithium/g11n/resources/php/en_GB/validation/default.php index f69e56e..9f21a99 100644 --- a/libraries/lithium/g11n/resources/php/en_GB/validation/default.php +++ b/libraries/lithium/g11n/resources/php/en_GB/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/en_US/validation/default.php b/libraries/lithium/g11n/resources/php/en_US/validation/default.php index fde2f76..8c398d5 100644 --- a/libraries/lithium/g11n/resources/php/en_US/validation/default.php +++ b/libraries/lithium/g11n/resources/php/en_US/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/fr/message/default.php b/libraries/lithium/g11n/resources/php/fr/message/default.php new file mode 100644 index 0000000..9549fa7 --- /dev/null +++ b/libraries/lithium/g11n/resources/php/fr/message/default.php @@ -0,0 +1,21 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +/** + * Message data for `fr`. + * + * Plural rule and forms derived from the GNU gettext documentation. + * + * @link http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms + */ +return array( + 'pluralForms' => 2, + 'pluralRule' => function ($n) { return $n == 1 ? 0 : 1; } +); + +?> \ No newline at end of file diff --git a/libraries/lithium/g11n/resources/php/fr_BE/validation/default.php b/libraries/lithium/g11n/resources/php/fr_BE/validation/default.php index 31baadc..870ed0f 100644 --- a/libraries/lithium/g11n/resources/php/fr_BE/validation/default.php +++ b/libraries/lithium/g11n/resources/php/fr_BE/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/fr_CA/validation/default.php b/libraries/lithium/g11n/resources/php/fr_CA/validation/default.php index 0dbe28e..30f05c2 100644 --- a/libraries/lithium/g11n/resources/php/fr_CA/validation/default.php +++ b/libraries/lithium/g11n/resources/php/fr_CA/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/it_IT/validation/default.php b/libraries/lithium/g11n/resources/php/it_IT/validation/default.php index fe6b691..aaf75aa 100644 --- a/libraries/lithium/g11n/resources/php/it_IT/validation/default.php +++ b/libraries/lithium/g11n/resources/php/it_IT/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/nl_BE/validation/default.php b/libraries/lithium/g11n/resources/php/nl_BE/validation/default.php index 8058f46..c49d947 100644 --- a/libraries/lithium/g11n/resources/php/nl_BE/validation/default.php +++ b/libraries/lithium/g11n/resources/php/nl_BE/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/g11n/resources/php/nl_NL/validation/default.php b/libraries/lithium/g11n/resources/php/nl_NL/validation/default.php index b6bb938..5907ce6 100644 --- a/libraries/lithium/g11n/resources/php/nl_NL/validation/default.php +++ b/libraries/lithium/g11n/resources/php/nl_NL/validation/default.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/net/Message.php b/libraries/lithium/net/Message.php index 9f8623d..ec682b4 100644 --- a/libraries/lithium/net/Message.php +++ b/libraries/lithium/net/Message.php @@ -2,12 +2,15 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\net; +use ReflectionClass; +use ReflectionProperty; + /** * Base message class for any URI based request/response. * @see http://tools.ietf.org/html/rfc3986#section-1.1.1 @@ -117,8 +120,9 @@ class Message extends \lithium\core\Object { switch ($format) { case 'array': $array = array(); - $r = new \ReflectionClass(get_class($this)); - foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + $class = new ReflectionClass(get_class($this)); + + foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) { $array[$prop->getName()] = $prop->getValue($this); } return $array; diff --git a/libraries/lithium/net/Socket.php b/libraries/lithium/net/Socket.php index 875f1a9..d01ea27 100644 --- a/libraries/lithium/net/Socket.php +++ b/libraries/lithium/net/Socket.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -28,6 +28,7 @@ abstract class Socket extends \lithium\core\Object { * @var array */ protected $_classes = array( + 'request' => 'lithium\net\Message', 'response' => 'lithium\net\Message' ); @@ -98,18 +99,6 @@ abstract class Socket extends \lithium\core\Object { abstract public function write($data); /** - * Aggregates read and write methods into a coherent request response - * - * @param mixed $request array or object like `\lithium\net\http\Request` - * @params array $options - * - path: path for the current request - * - classes: array of classes to use - * - response: a class to use for the response - * @return array headers, body, message - */ - abstract public function send($message, array $options = array()); - - /** * Sets the timeout on the socket *connection*. * * @param integer $time Seconds after the connection times out. @@ -126,6 +115,36 @@ abstract class Socket extends \lithium\core\Object { abstract public function encoding($charset); /** + * Sets the options to be used in subsequent requests. + * + * @param array $flags If $values is an array, $flags will be used as the + * keys to an associative array of curl options. If $values is not set, + * then $flags will be used as the associative array. + * @param array $value If set, this array becomes the values for the + * associative array of curl options. + * @return void + */ + public function set($flags, $value = null) {} + + /** + * Aggregates read and write methods into a coherent request response + * + * @param mixed $message a request object based on `\lithium\net\Message` + * @param array $options + * - '`response`': a fully-namespaced string for the response object + * @return object a response object based on `\lithium\net\Message` + */ + public function send($message = null, array $options = array()) { + $defaults = array('response' => $this->_classes['response']); + $options += $defaults; + + if ($this->write($message)) { + $config = array('message' => $this->read()) + $this->_config; + return $this->_instance($options['response'], $config); + } + } + + /** * Destructor. * * @return void diff --git a/libraries/lithium/net/http/Media.php b/libraries/lithium/net/http/Media.php old mode 100755 new mode 100644 index 8511804..da388ad --- a/libraries/lithium/net/http/Media.php +++ b/libraries/lithium/net/http/Media.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -32,7 +32,7 @@ class Media extends \lithium\core\StaticObject { /** * Maps file extensions to content-types. Used to set response types and determine request - * types. Can be modified with Media::type(). + * types. Can be modified with `Media::type()`. * * @var array * @see lithium\net\http\Media::type() @@ -59,6 +59,14 @@ class Media extends \lithium\core\StaticObject { protected static $_assets = array(); /** + * Placeholder for class dependencies. This class' dependencies (i.e. templating classes) are + * typically specified through other configuration. + * + * @var array + */ + protected static $_classes = array(); + + /** * Returns the list of registered media types. New types can be set with the `type()` method. * * @return array Returns an array of media type extensions or short-names, which comprise the @@ -98,50 +106,105 @@ class Media extends \lithium\core\StaticObject { } /** - * Map an extension to a particular content-type (or types) with a set of options. + * Maps a type name to a particular content-type (or multiple types) with a set of options, or + * retrieves information about a type that has been defined. * * Examples: - * {{{// Get a list of all available media types: - * Media::types(); // returns array('ai', 'amf', 'atom', ...); - * }}} + * {{{ embed:lithium\tests\cases\net\http\MediaTest::testMediaTypes(1-2) }}} * - * {{{// Add a custom media type: - * Media::type('my', 'text/x-my', array('view' => '\my\custom\View', 'layout' => false)); - * }}} + * {{{ embed:lithium\tests\cases\net\http\MediaTest::testMediaTypes(19-20) }}} * - * {{{// Remove a custom media type: - * Media::type('my', false); - * }}} + * {{{ embed:lithium\tests\cases\net\http\MediaTest::testMediaTypes(35-36) }}} * * Alternatively, can be used to detect the type name of a registered content type: * {{{ * Media::type('application/json'); // returns 'json' * Media::type('application/javascript'); // returns 'javascript' * Media::type('text/javascript'); // also returns 'javascript' + * + * Media::type('text/html'); // returns 'html' + * Media::type('application/xhtml+xml'); // also returns 'html' + * }}} + * + * #### Content negotiation + * + * When creating custom media types, specifying which content-type(s) to match isn't always + * enough. For example, if you wish to serve a different set of templates to mobile web + * browsers, you'd still want those templates served as HTML. You might add something like this: + * + * {{{ + * Media::type('mobile', array('application/xhtml+xml', 'text/html')); * }}} * + * However, this would cause _all_ requests for HTML content to be interpreted as + * `'mobile'`-type requests. Instead, we can use _content negotiation_ to granularly specify how + * to match a particular type. Content negotiation is the process of examining the HTTP headers + * provided in the request (including the content-types listed in the `Accept` header, and + * optionally other things as well, like the `Accept-Language` or `User-Agent` headers), in + * order to produce the best representation of the requested resource for the client; in other + * words, the resource that most closely matches what the client is asking for. + * + * Content negotiation with media types is made possible through the `'conditions'` key of the + * `$options` parameter, which contains an array of assertions made against the `Request` + * object. Each assertion (array key) can be one of three different things: + * + * - `'type'` _boolean_: In the default routing, some routes have `{:type}` keys, which are + * designed to match file extensions in URLs. These values act as overrides for the HTTP + * `Accept` header, allowing different formats to be served with the same content type. For + * example, if you're serving [ JSONP](http://en.wikipedia.org/wiki/JSON#JSONP), you'll want + * to serve it with the same content-type as JavaScript (since it is JavaScript), but you + * probably won't want to use the same template(s) or other settings. Therefore, when serving + * JSONP content, you can specify that the extension defined in the type must be present in + * the URL: + * {{{ + * Media::type('jsonp', array('text/html'), array( + * // template settings... + * 'conditions' => array('type' => true) + * )); + * }}} + * Then, JSONP content will only ever be served when the request URL ends in `.jsonp`. + * + * - `'<prefix>:<key>'` _string_: This type of assertion can be used to match against arbitrary + * information in the request, including headers (i.e. `'http:user_agent'`), environment + * varialbes (i.e. `'env:home'`), GET and POST data (i.e. `'query:foo'` or `'data:foo'`, + * respectively), and the HTTP method (`'http:method'`) of the request. For more information + * on possible keys, see `lithium\action\Request::get()`. + * + * - `'<detector>'` _boolean_: Uses detector checks added to the `Request` object to make + * boolean assertions against the request. For example, if a detector called `'iPhone'` is + * attached, you can add `'iPhone' => true` to the `'conditions'` array in order to filter for + * iPhone requests only. See `lithium\action\Request::detect()` for more information on adding + * detectors. + * * @see lithium\net\http\Media::$_types * @see lithium\net\http\Media::$_handlers + * @see lithium\net\http\Media::negotiate() + * @see lithium\action\Request::get() + * @see lithium\action\Request::is() + * @see lithium\action\Request::detect() * @see lithium\util\String::insert() - * @param string $type A file extension for the type, i.e. `'txt'`, `'js'`, or `'atom'`. - * Alternatively, may be a content type, i.e. `'text/html'`, - * `'application/atom+xml'`, etc.; in which case, the type name (i.e. '`html'` or - * `'atom'`) will be returned. + * @param string $type A file-extension-style type name, i.e. `'txt'`, `'js'`, or `'atom'`. + * Alternatively, a mapped content type, i.e. `'text/html'`, + * `'application/atom+xml'`, etc.; in which case, the matching type name (i.e. + * '`html'` or `'atom'`) will be returned. * @param mixed $content Optional. A string or array containing the content-type(s) that * `$type` should map to. If `$type` is an array of content-types, the first one listed - * should be the "primary" type. + * should be the "primary" type, and will be used as the `Content-type` header of any + * `Response` objects served through this type. * @param array $options Optional. The handling options for this media type. Possible keys are: - * - `'decode'`: A (string) function name or (object) closure that handles + * - `'decode'` _mixed_: A (string) function name or (object) closure that handles * decoding or unserializing content from this format. - * - `'encode'`: A (string) function name or (object) closure that handles encoding or - * serializing content into this format. - * - `'cast'`: Used with `'encode'`. If `true`, all data passed into the specified encode - * function is first cast to array structures. - * - `'layout'`: Specifies a `String::insert()`-style path to use when searching for - * layout files. - * - `'template'`: Specifies a `String::insert()`-style path to use when searching for - * template files. - * - `'view'`: Specifies the view class to use when rendering this content. + * - `'encode'` _mixed_: A (string) function name or (object) closure that handles + * encoding or serializing content into this format. + * - `'cast'` _boolean_: Used with `'encode'`. If `true`, all data passed into the + * specified encode function is first cast to array structures. + * - `'layout'` _mixed_: Specifies one or more `String::insert()`-style paths to use when + * searching for layout files (either a string or array of strings). + * - `'template'` _mixed_: Specifies one or more `String::insert()`-style paths to use + * when searching for template files (either a string or array of strings). + * - `'view'` _string_: Specifies the view class to use when rendering this content. + * - `'conditions'` _array_: Optional key/value pairs used as assertions in content + * negotiation. See the above section on **Content Negotiation**. * @return mixed If `$content` and `$options` are empty, returns an array with `'content'` and * `'options'` keys, where `'content'` is the content-type(s) that correspond to * `$type` (can be a string or array, if multiple content-types are available), and @@ -156,6 +219,7 @@ class Media extends \lithium\core\StaticObject { 'encode' => false, 'decode' => false, 'cast' => true, + 'conditions' => array(), ); if ($content === false) { @@ -165,9 +229,11 @@ class Media extends \lithium\core\StaticObject { if (!$content = static::_types($type)) { return; } + if (strpos($type, '/')) { + return $content; + } if (is_array($content) && isset($content['alias'])) { - $type = $content['alias']; - $content = static::_types($type); + return static::type($content['alias']); } return compact('content') + array('options' => static::_handlers($type)); } @@ -178,6 +244,95 @@ class Media extends \lithium\core\StaticObject { } /** + * Performs content-type negotiation on a `Request` object, by iterating over the accepted + * types in sequence, from most preferred to least, and attempting to match each one against a + * content type defined by `Media::type()`, until a match is found. If more than one defined + * type matches for a given content type, they will be checked in the order they were added + * (usually, this corresponds to the order they were defined in the application bootstrapping + * process). + * + * @see lithium\net\http\Media::type() + * @see lithium\net\http\Media::match() + * @see lithium\action\Request + * @param object $request The instance of `lithium\action\Request` which contains the details of + * the request to be content-negotiated. + * @return string Returns the first matching type name, i.e. `'html'` or `'json'`. + */ + public static function negotiate($request) { + $self = get_called_class(); + + $match = function($name) use ($self, $request) { + if (($cfg = $self::type($name)) && $self::match($request, compact('name') + $cfg)) { + return true; + } + return false; + }; + + if (($type = $request->type) && $match($type)) { + return $type; + } + + foreach ($request->accepts(true) as $type) { + if (!$types = (array) static::_types($type)) { + continue; + } + foreach ($types as $name) { + if (!$match($name)) { + continue; + } + return $name; + } + } + } + + /** + * Assists `Media::negotiate()` in processing the negotiation conditions of a content type, by + * iterating through the conditions and checking each one against the `Request` object. + * + * @see lithium\net\http\Media::negotiate() + * @see lithium\net\http\Media::type() + * @see lithium\action\Request + * @param object $request The instance of `lithium\action\Request` to be checked against a + * set of conditions (if applicable). + * @param array $config Represents a content type configuration, which is an array containing 3 + * keys: + * - `'name'` _string_: The type name, i.e. `'html'` or `'json'`. + * - `'content'` _mixed_: One or more content types that the configuration + * represents, i.e. `'text/html'`, `'application/xhtml+xml'` or + * `'application/json'`, or an array containing multiple content types. + * - `'options'` _array_: An array containing rendering information, and an + * optional `'conditions'` key, which contains an array of matching parameters. + * For more details on these matching parameters, see `Media::type()`. + * @return boolean Returns `true` if the information in `$request` matches the type + * configuration in `$config`, otherwise false. + */ + public static function match($request, array $config) { + if (!isset($config['options']['conditions'])) { + return true; + } + $conditions = $config['options']['conditions']; + + foreach ($conditions as $key => $value) { + switch (true) { + case $key == 'type': + if ($value !== ($request->type === $config['name'])) { + return false; + } + break; + case strpos($key, ':'): + if ($request->get($key) !== $value) { + return false; + } + break; + case ($request->is($key) !== $value): + return false; + break; + } + } + return true; + } + + /** * Gets or sets options for various asset types. * * @see lithium\util\String::insert() @@ -288,7 +443,9 @@ class Media extends \lithium\core\StaticObject { } if ($path[0] === '/') { - $path = strpos($path, $options['base']) !== 0 ? "{$options['base']}{$path}" : $path; + if ($options['base'] && strpos($path, $options['base']) !== 0) { + $path = "{$options['base']}{$path}"; + } } else { $path = String::insert(key($paths), compact('path') + $options); } @@ -314,8 +471,8 @@ class Media extends \lithium\core\StaticObject { /** * Gets the physical path to the web assets (i.e. `/webroot`) directory of a library. * - * @param string $library The name of the library for which to find the path, or `true` for the - * default library. + * @param string|boolean $library The name of the library for which to find the path, or `true` + * for the default library. * @return string Returns the physical path to the web assets directory for a library. For * example, the `/webroot` directory of the default library would be * `LITHIUM_APP_PATH . '/webroot'`. @@ -388,7 +545,6 @@ class Media extends \lithium\core\StaticObject { * @param array $options * @return void * @filter - * @todo Implement proper exception handling */ public static function render(&$response, $data = null, array $options = array()) { $params = array('response' => &$response) + compact('data', 'options'); @@ -403,54 +559,108 @@ class Media extends \lithium\core\StaticObject { $result = null; $type = $options['type']; - $hasHandler = isset($handlers[$type]); - $handler = $options + ($hasHandler ? $handlers[$type] : array()) + $defaults; + if (!isset($handlers[$type])) { + throw new MediaException("Unhandled media type `{$type}`."); + } + $handler = $options + $handlers[$type] + $defaults; $filter = function($v) { return $v !== null; }; $handler = array_filter($handler, $filter) + $handlers['default'] + $defaults; - if (!$hasHandler) { - throw new MediaException("Unhandled media type '{$type}'"); - } - if (isset($types[$type])) { - $response->headers('Content-type', current((array) $types[$type])); + $header = current((array) $types[$type]); + $header .= $response->encoding ? "; charset={$response->encoding}" : ''; + $response->headers('Content-type', $header); } $response->body($self::invokeMethod('_handle', array($handler, $data, $response))); }); } /** + * Configures a template object instance, based on a media handler configuration. + * + * @see lithium\net\http\Media::type() + * @see lithium\template\View::render() + * @see lithium\action\Response + * @param mixed $handler Either a string specifying the name of a media type for which a handler + * is defined, or an array representing a handler configuration. For more on types + * and type handlers, see the `type()` method. + * @param mixed $data The data to be rendered. Usually an array. + * @param object $response The `Response` object associated with this dispatch cycle. Usually an + * instance of `lithium\action\Response`. + * @param array $options Any options that will be passed to the `render()` method of the + * templating object. + * @return object Returns an instance of a templating object, usually `lithium\template\View`. + * @filter + */ + public static function view($handler, $data, &$response = null, array $options = array()) { + $params = array('response' => &$response) + compact('handler', 'data', 'options'); + + return static::_filter(__FUNCTION__, $params, function($self, $params) { + $data = $params['data']; + $options = $params['options']; + $handler = $params['handler']; + $response =& $params['response']; + + if (!is_array($handler)) { + $handler = $self::invokeMethod('_handlers', array($handler)); + } + $class = $handler['view']; + unset($handler['view']); + + $config = $handler + array('response' => &$response); + return $self::invokeMethod('_instance', array($class, $config)); + }); + } + + /** * For media types registered in `$_handlers` which include an `'encode'` setting, encodes data * according to the specified media type. * - * @param string $type Specifies the media type into which `$data` will be encoded. This media - * type must have an `'encode'` setting specified in `Media::$_handlers`. + * @see lithium\net\http\Media::type() + * @param mixed $handler Specifies the media type into which `$data` will be encoded. This media + * type must have an `'encode'` setting specified in `Media::$_handlers`. + * Alternatively, `$type` can be an array, in which case it is used as the type + * handler configuration. See the `type()` method for information on adding type + * handlers, and the available configuration keys. * @param mixed $data Arbitrary data you wish to encode. Note that some encoders can only handle * arrays or objects. + * @param object $response A reference to the `Response` object for this dispatch cycle. * @param array $options Handler-specific options. - * @return mixed + * @return mixed Returns the result of `$data`, encoded with the encoding configuration + * specified by `$type`, the result of which is usually a string. + * @filter */ - public static function encode($type, $data, &$response = null) { - $handler = is_array($type) ? $type : static::_handlers($type); + public static function encode($handler, $data, &$response = null) { + $params = array('response' => &$response) + compact('handler', 'data'); - if (!$handler || !isset($handler['encode'])) { - return null; - } + return static::_filter(__FUNCTION__, $params, function($self, $params) { + $data = $params['data']; + $handler = $params['handler']; + $response =& $params['response']; - $cast = function($data) { - if (is_object($data)) { - return method_exists($data, 'to') ? $data->to('array') : get_object_vars($data); + if (!is_array($handler)) { + $handler = $self::invokeMethod('_handlers', array($handler)); } - return $data; - }; - if (!isset($handler['cast']) || $handler['cast']) { - $data = is_object($data) ? $cast($data) : $data; - $data = is_array($data) ? array_map($cast, $data) : $data; - } - $method = $handler['encode']; - return is_string($method) ? $method($data) : $method($data, $handler, $response); + if (!$handler || !isset($handler['encode'])) { + return null; + } + + $cast = function($data) { + if (!is_object($data)) { + return $data; + } + return method_exists($data, 'to') ? $data->to('array') : get_object_vars($data); + }; + + if (!isset($handler['cast']) || $handler['cast']) { + $data = is_object($data) ? $cast($data) : $data; + $data = is_array($data) ? array_map($cast, $data) : $data; + } + $method = $handler['encode']; + return is_string($method) ? $method($data) : $method($data, $handler, $response); + }); } /** @@ -497,6 +707,7 @@ class Media extends \lithium\core\StaticObject { */ protected static function _handle($handler, $data, &$response) { $params = array('response' => &$response) + compact('handler', 'data'); + return static::_filter(__FUNCTION__, $params, function($self, $params) { $response = $params['response']; $handler = $params['handler']; @@ -511,15 +722,15 @@ class Media extends \lithium\core\StaticObject { switch (true) { case $handler['encode']: return $self::encode($handler, $data, $response); - case class_exists($handler['view']): - $view = new $handler['view']($handler + array('response' => &$response)); - return $view->render('all', $data, $options); case ($handler['template'] === false) && is_string($data): return $data; + case $handler['view']: + unset($options['view']); + $instance = $self::view($handler, $data, $response, $options); + return $instance->render('all', (array) $data, $options); default: throw new MediaException("Could not interpret type settings for handler."); } - return $result; }); } @@ -532,14 +743,14 @@ class Media extends \lithium\core\StaticObject { */ protected static function _types($type = null) { $types = static::$_types + array( - 'atom' => 'application/atom+xml', - 'css' => 'text/css', - 'form' => 'application/x-www-form-urlencoded', - 'htm' => array('alias' => 'html'), 'html' => array('text/html', 'application/xhtml+xml', '*/*'), - 'js' => array('application/javascript', 'text/javascript'), + 'htm' => array('alias' => 'html'), + 'form' => 'application/x-www-form-urlencoded', 'json' => 'application/json', 'rss' => 'application/rss+xml', + 'atom' => 'application/atom+xml', + 'css' => 'text/css', + 'js' => array('application/javascript', 'text/javascript'), 'text' => 'text/plain', 'txt' => array('alias' => 'text'), 'xml' => array('application/xml', 'text/xml'), @@ -554,11 +765,17 @@ class Media extends \lithium\core\StaticObject { if (strpos($type, ';')) { list($type) = explode(';', $type); } + $result = array(); + foreach ($types as $name => $cTypes) { if ($type == $cTypes || (is_array($cTypes) && in_array($type, $cTypes))) { - return $name; + $result[] = $name; } } + if (count($result) == 1) { + return reset($result); + } + return $result ?: null; } /** @@ -569,26 +786,24 @@ class Media extends \lithium\core\StaticObject { * @return mixed Array of all handlers, or the handler for a specific type. */ protected static function _handlers($type = null) { - $format = array('view' => false, 'layout' => false); - $json = array('encode' => 'json_encode', 'decode' => function($data) { - return json_decode($data, true); - }); - $paths = array( - 'template' => '{:library}/views/{:controller}/{:template}.{:type}.php', - 'layout' => '{:library}/views/layouts/{:layout}.{:type}.php', - ); - $handlers = static::$_handlers + array( - 'default' => compact('paths') + array( + 'default' => array( 'view' => 'lithium\template\View', 'encode' => false, 'decode' => false, 'cast' => false, + 'paths' => array( + 'template' => '{:library}/views/{:controller}/{:template}.{:type}.php', + 'layout' => '{:library}/views/layouts/{:layout}.{:type}.php', + 'element' => '{:library}/views/elements/{:template}.{:type}.php', + ) ), 'html' => array(), - 'json' => $format + $json + array('cast' => true), - 'text' => $format + array('cast' => false, 'encode' => function($s) { return $s; }), - 'form' => $format + array('cast' => true, 'encode' => 'http_build_query'), + 'json' => array('cast' => true, 'encode' => 'json_encode', 'decode' => function($data) { + return json_decode($data, true); + }), + 'text' => array('cast' => false, 'encode' => function($s) { return $s; }), + 'form' => array('cast' => true, 'encode' => 'http_build_query'), ); if ($type) { diff --git a/libraries/lithium/net/http/MediaException.php b/libraries/lithium/net/http/MediaException.php index c4223a8..d99d61c 100644 --- a/libraries/lithium/net/http/MediaException.php +++ b/libraries/lithium/net/http/MediaException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/net/http/Message.php b/libraries/lithium/net/http/Message.php index 00a95bb..98c866d 100644 --- a/libraries/lithium/net/http/Message.php +++ b/libraries/lithium/net/http/Message.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -108,10 +108,9 @@ class Message extends \lithium\net\Message { } if (strpos($type, '/')) { $media = $this->_classes['media']; - $data = $media::type($type); - if (isset($data['content'])) { - $type = $data['content']; + if ($data = $media::type($type)) { + $type = is_array($data) ? reset($data) : $data; } } return $this->_type = $type; diff --git a/libraries/lithium/net/http/Request.php b/libraries/lithium/net/http/Request.php index 50176cb..b563be3 100644 --- a/libraries/lithium/net/http/Request.php +++ b/libraries/lithium/net/http/Request.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -121,31 +121,67 @@ class Request extends \lithium\net\http\Message { /** * Converts the data in the record set to a different format, i.e. an array. Available - * options: array, url, context, or string. + * options: array, URL, stream context configuration, or string. * - * @param string $format Format to convert to. - * @param array $options - * @return mixed + * @see lithium\net\Message::to() + * @param string $format Format to convert to. Should be either `'url'`, which returns a string + * representation of the URL that this request points to, or `'context'`, which + * returns an array usable with PHP's `stream_context_create()` function. For + * more available formats, see the parent method, `lithium\net\Message::to()`. + * @param array $options Allows overriding of specific portions of the URL, as follows. These + * options should only be specified if you intend to replace the values that are + * already in the `Request` object. + * - `'scheme'` _string_: The protocol scheme of the URL. + * - `'method'` _string_: If applicable, the HTTP method to use in the request. + * Mainly applies to the `'context'` format. + * - `'host'` _string_: The host name the request is pointing at. + * - `'port'` _string_: The host port, if any. If specified, should be prefixed + * with `':'`. + * - `'path'` _string_: The URL path. + * - `'query'` _mixed_: The query string of the URL as a string or array. If passed + * as a string, should be prefixed with `'?'`. + * - `'auth'` _string_: Authentication information. See the constructor for + * details. + * - `'content'` _string_: The body of the request. + * - `'headers'` _array_: The request headers. + * - `'version'` _string_: The HTTP version of the request, where applicable. + * @return mixed Varies; see the `$format` parameter for possible return values. */ public function to($format, array $options = array()) { + $defaults = array( + 'method' => $this->method, + 'scheme' => $this->scheme, + 'host' => $this->host, + 'port' => $this->port ? ":{$this->port}" : '', + 'path' => $this->path, + 'query' => $this->queryString(), + 'auth' => $this->_config['auth'], + 'content' => $this->body(), + 'version' => $this->version, + ); + $options += $defaults; + + if ($options['query'] && is_array($options['query'])) { + $options['query'] = $this->queryString($options['query']); + } + switch ($format) { case 'url': - $query = $this->queryString(); - $host = $this->host . ($this->port ? ":{$this->port}" : ''); - return "{$this->scheme}://{$host}{$this->path}{$query}"; + return String::insert("{:scheme}://{:host}{:port}{:path}{:query}", $options); case 'context': - if ($this->_config['auth']) { + if ($options['auth']) { $auth = base64_encode("{$this->username}:{$this->password}"); - $this->headers('Authorization', "{$this->_config['auth']} {$auth}"); + $this->headers('Authorization', "{$options['auth']} {$auth}"); } - $content = $this->body(); - $this->headers('Content-Length', strlen($content)); - $defaults = array( - 'method' => $this->method, - 'header' => $this->headers(), 'content' => $content, - 'protocol_version' => $this->version, 'ignore_errors' => true + $this->headers('Content-Length', strlen($options['content'])); + $base = array( + 'content' => $options['content'], + 'method' => $options['method'], + 'header' => $this->headers(), + 'protocol_version' => $options['version'], + 'ignore_errors' => true ); - return array('http' => $options + $defaults); + return array('http' => array_diff_key($options, $defaults) + $base); default: return parent::to($format, $options); } diff --git a/libraries/lithium/net/http/Response.php b/libraries/lithium/net/http/Response.php index 8898dd9..10e0b38 100644 --- a/libraries/lithium/net/http/Response.php +++ b/libraries/lithium/net/http/Response.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -106,20 +106,26 @@ class Response extends \lithium\net\http\Message { } if (isset($this->headers['Content-Type'])) { - preg_match('/^(.*?);charset=(.+)/i', $this->headers['Content-Type'], $match); + preg_match('/^(.*?);\s*?charset=(.+)/i', $this->headers['Content-Type'], $match); if ($match) { $this->type = trim($match[1]); $this->encoding = strtoupper(trim($match[2])); } } - if (isset($this->headers['Transfer-Encoding'])) { $body = $this->_decode($body); } $this->body = $this->body ?: $body; } + /** + * Accepts an entire HTTP message including headers and body, and parses it into a message body + * an array of headers, and the HTTP status. + * + * @param string $body The full body of the message. + * @return After parsing out other message components, returns just the message body. + */ protected function _parseMessage($body) { if (!($parts = explode("\r\n\r\n", $body, 2)) || count($parts) == 1) { return $body; @@ -143,11 +149,11 @@ class Response extends \lithium\net\http\Message { } /** - * Set and get the status for the response + * Set and get the status for the response. * * @param string $key * @param string $data - * @return string + * @return string Returns the full HTTP status, with version, code and message. */ public function status($key = null, $data = null) { if ($data === null) { @@ -190,43 +196,21 @@ class Response extends \lithium\net\http\Message { } /** - * Decodes based on transfer encoding body. + * Decodes content bodies transferred with HTTP chunked encoding. * - * @todo replace with stream wrapper dechunk - * @param string $body - * @return string + * @link http://en.wikipedia.org/wiki/Chunked_transfer_encoding Wikipedia: Chunked encoding + * @param string $body A chunked HTTP message body. + * @return string Returns the value of `$body` with chunks decoded, but only if the value of the + * `Transfer-Encoding` header is set to `'chunked'`. Otherwise, returns `$body` + * unmodified. */ protected function _decode($body) { - if (stripos($this->headers['Transfer-Encoding'], 'chunked') !== false) { - $decoded = null; - while($body != '') { - $left = strpos($body, "\012"); - if($left === false) { - $decoded .= $body; - break; - } - $chunk = substr($body, 0, $left); - $next = strpos($chunk, ';'); - if($next !== false) { - $chunk = substr($chunk, 0, $next); - } - if($chunk == '') { - $decoded .= substr($body, 0, $left); - $body = substr($body, $left + 1); - continue; - } - $length = hexdec($chunk); - if($length) { - $decoded .= substr($body, $left + 1, $length); - $body = substr($body, $left + 2 + $length); - } else { - $body = ''; - } - } - return $decoded; + if (stripos($this->headers['Transfer-Encoding'], 'chunked') === false) { + return $body; } - - return $body; + $stream = fopen('data://text/plain,' . $body, 'r'); + stream_filter_append($stream, 'dechunk'); + return stream_get_contents($stream); } } diff --git a/libraries/lithium/net/http/Route.php b/libraries/lithium/net/http/Route.php index 7133a09..d3f9d98 100644 --- a/libraries/lithium/net/http/Route.php +++ b/libraries/lithium/net/http/Route.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/net/http/Router.php b/libraries/lithium/net/http/Router.php index 6e20627..4ff7a71 100644 --- a/libraries/lithium/net/http/Router.php +++ b/libraries/lithium/net/http/Router.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -189,12 +189,12 @@ class Router extends \lithium\core\StaticObject { * prefixed with the base URL of the application. */ public static function match($url = array(), $context = null, array $options = array()) { - if (is_string($path = $url)) { - if (strpos($path, '#') === 0 || strpos($path, 'mailto') === 0 || strpos($path, '://')) { - return $path; + if (is_string($url)) { + if (strpos($url, '#') === 0 || strpos($url, 'mailto') === 0 || strpos($url, '://')) { + return $url; } if (is_string($url = static::_parseString($url, $context))) { - return $url; + return static::_prefix($url, $context, $options); } } if (isset($url[0]) && is_array($params = static::_parseString($url[0], $context))) { @@ -217,7 +217,10 @@ class Router extends \lithium\core\StaticObject { $path = ($options) ? static::_prefix($path, $context, $options) : $path; return $path ?: '/'; } - throw new RoutingException("No parameter match found for routes."); + $match = array("\n", 'array (', ',)', '=> NULL', '( \'', ', '); + $replace = array('', '(', ')', '=> null', '(\'', ', '); + $url = str_replace($match, $replace, var_export($url, true)); + throw new RoutingException("No parameter match found for URL `{$url}`."); } /** diff --git a/libraries/lithium/net/http/RoutingException.php b/libraries/lithium/net/http/RoutingException.php index 51a8e7a..ac472ff 100644 --- a/libraries/lithium/net/http/RoutingException.php +++ b/libraries/lithium/net/http/RoutingException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/net/http/Service.php b/libraries/lithium/net/http/Service.php index 08b3d9a..c98acad 100644 --- a/libraries/lithium/net/http/Service.php +++ b/libraries/lithium/net/http/Service.php @@ -2,9 +2,10 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ + namespace lithium\net\http; use lithium\core\Libraries; @@ -177,7 +178,7 @@ class Service extends \lithium\core\Object { $response = $conn->send($request, $options); $conn->close(); $this->last = (object) compact('request', 'response'); - return ($options['return'] == 'body') ? $response->body() : $response; + return ($options['return'] == 'body' && $response) ? $response->body() : $response; } /** diff --git a/libraries/lithium/net/socket/Context.php b/libraries/lithium/net/socket/Context.php index b7570c9..8c5d43f 100644 --- a/libraries/lithium/net/socket/Context.php +++ b/libraries/lithium/net/socket/Context.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -105,14 +105,16 @@ class Context extends \lithium\net\Socket { * @param string $data Data to write. * @return boolean Success */ - public function write($data) { - $this->_content = $data; - if (is_object($data)) { - return stream_context_set_option( - $this->_resource, $data->to('context', array('timeout' => $this->_timeout)) - ); + public function write($data = null) { + if (!is_resource($this->_resource)) { + return false; } - return true; + if (!is_object($data)) { + $data = $this->_instance($this->_classes['request'], (array) $data + $this->_config); + } + return stream_context_set_option( + $this->_resource, $data->to('context', array('timeout' => $this->_timeout)) + ); } /** @@ -137,22 +139,6 @@ class Context extends \lithium\net\Socket { public function encoding($charset = null) { return false; } - - /** - * Send request and return response data - * - * @param object $message - * @param array $options - * @return string - */ - public function send($message, array $options = array()) { - $defaults = array('response' => $this->_classes['response']); - $options += $defaults; - - if ($this->write($message)) { - return $this->_instance($options['response'], array('message' => $this->read())); - } - } } ?> \ No newline at end of file diff --git a/libraries/lithium/net/socket/Curl.php b/libraries/lithium/net/socket/Curl.php index 2064360..9dd6c1b 100644 --- a/libraries/lithium/net/socket/Curl.php +++ b/libraries/lithium/net/socket/Curl.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -35,6 +35,16 @@ class Curl extends \lithium\net\Socket { public $options = array(); /** + * Constructor + * + * @param array $config + */ + public function __construct(array $config = array()) { + $defaults = array('ignoreExpect' => true); + parent::__construct($config + $defaults); + } + + /** * Opens a curl connection and initializes the internal resource handle. * * @return mixed Returns `false` if the socket configuration does not contain the @@ -103,25 +113,36 @@ class Curl extends \lithium\net\Socket { if (!is_resource($this->_resource)) { return false; } - curl_setopt_array($this->_resource, $this->options); return curl_exec($this->_resource); } /** - * Reads data from the curl connection. - * The `read` method will utilize the curl options that have been set. + * Writes data to curl options * - * @link http://php.net/manual/en/function.curl-exec.php PHP Manual: curl_exec() - * @param array $data - * @return mixed Boolean `false` if the resource handle is unavailable, and the result - * of `curl_exec()` otherwise. + * @param object $data a `lithium\net\Message` object or array + * @return boolean */ - public function write($data) { + public function write($data = null) { if (!is_resource($this->_resource)) { return false; } - curl_setopt_array($this->_resource, $this->options); - return curl_exec($this->_resource); + if (!is_object($data)) { + $data = $this->_instance($this->_classes['request'], (array) $data + $this->_config); + } + $this->set(CURLOPT_URL, $data->to('url')); + + if (is_a($data, 'lithium\net\http\Message')) { + if (!empty($this->_config['ignoreExpect'])) { + $data->headers('Expect', ' '); + } + if (isset($data->headers)) { + $this->set(CURLOPT_HTTPHEADER, $data->headers()); + } + if (isset($data->method) && $data->method == 'POST') { + $this->set(array(CURLOPT_POST => true, CURLOPT_POSTFIELDS => $data->body())); + } + } + return (boolean) curl_setopt_array($this->_resource, $this->options); } /** @@ -148,9 +169,7 @@ class Curl extends \lithium\net\Socket { * @todo implement Curl::encoding($charset) * @param string $charset */ - public function encoding($charset) { - } - + public function encoding($charset) {} /** * Sets the options to be used in subsequent curl requests. * @@ -168,31 +187,6 @@ class Curl extends \lithium\net\Socket { } $this->options += $flags; } - - /** - * Aggregates read and write methods into a coherent request response - * - * @param mixed $message a request object based on `\lithium\net\Message` - * @param array $options - * - '`response`': a fully-namespaced string for the response object - * @return object a response object based on `\lithium\net\Message` - */ - public function send($message, array $options = array()) { - $defaults = array('response' => $this->_classes['response']); - $options += $defaults; - $this->set(CURLOPT_URL, $message->to('url')); - - if (isset($message->headers)) { - $this->set(CURLOPT_HTTPHEADER, $message->headers()); - } - if (isset($message->method) && $message->method == 'POST') { - $this->set(array(CURLOPT_POST => true, CURLOPT_POSTFIELDS => $message->body())); - } - if ($message = $this->write($message)) { - $message = $message ?: $this->read(); - return $this->_instance($options['response'], compact('message')); - } - } } ?> \ No newline at end of file diff --git a/libraries/lithium/net/socket/Stream.php b/libraries/lithium/net/socket/Stream.php index 9bd52f4..881d968 100644 --- a/libraries/lithium/net/socket/Stream.php +++ b/libraries/lithium/net/socket/Stream.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -30,11 +30,11 @@ class Stream extends \lithium\net\Socket { public function open() { $config = $this->_config; - if (empty($config['scheme']) || empty($config['host'])) { + if (!$config['scheme'] || !$config['host']) { return false; } $scheme = ($config['scheme'] !== 'udp') ? 'tcp' : 'udp'; - $port = ($config['port']) ?: 80; + $port = $config['port'] ?: 80; $host = "{$scheme}://{$config['host']}:{$port}"; $flags = STREAM_CLIENT_CONNECT; @@ -45,7 +45,7 @@ class Stream extends \lithium\net\Socket { $host, $errorCode, $errorMessage, $config['timeout'], $flags ); - if (!empty($errorCode) || !empty($errorMessage)) { + if ($errorCode || $errorMessage) { throw new NetworkException($errorMessage); } $this->timeout($config['timeout']); @@ -53,7 +53,6 @@ class Stream extends \lithium\net\Socket { if (!empty($config['encoding'])) { $this->encoding($config['encoding']); } - return $this->_resource; } @@ -67,6 +66,7 @@ class Stream extends \lithium\net\Socket { return true; } fclose($this->_resource); + if (is_resource($this->_resource)) { $this->close(); } @@ -76,13 +76,10 @@ class Stream extends \lithium\net\Socket { /** * Determines if the socket resource is at EOF. * - * @return boolean True if resource pointer is at EOF, false otherwise. + * @return boolean Returns `true` if resource pointer is at its EOF, `false` otherwise. */ public function eof() { - if (!is_resource($this->_resource)) { - return true; - } - return feof($this->_resource); + return is_resource($this->_resource) ? feof($this->_resource) : true; } /** @@ -97,9 +94,10 @@ class Stream extends \lithium\net\Socket { if (!is_resource($this->_resource)) { return false; } - return is_null($length) ? stream_get_contents($this->_resource) : stream_get_contents( - $this->_resource, $length, $offset - ); + if (!$length) { + return stream_get_contents($this->_resource); + } + return stream_get_contents($this->_resource, $length, $offset); } /** @@ -108,11 +106,14 @@ class Stream extends \lithium\net\Socket { * @param string $data The string to be written. * @return mixed False on error, number of bytes written otherwise. */ - public function write($data) { + public function write($data = null) { if (!is_resource($this->_resource)) { return false; } - return fwrite($this->_resource, (string) $data, strlen($data)); + if (!is_object($data)) { + $data = $this->_instance($this->_classes['request'], (array) $data + $this->_config); + } + return fwrite($this->_resource, (string) $data, strlen((string) $data)); } /** @@ -144,29 +145,7 @@ class Stream extends \lithium\net\Socket { if (!function_exists('stream_encoding')) { return false; } - return is_resource($this->_resource) - ? stream_encoding($this->_resource, $charset) : false; - } - - /** - * Aggregates read and write methods into a coherent request response - * - * @param mixed $message array or object like `\lithium\net\http\Request` - * @param array $options - * - path: path for the current request - * - classes: array of classes to use - * - response: a class to use for the response - * @return boolean response string or object like `\lithium\net\http\Response` - */ - public function send($message, array $options = array()) { - $defaults = array('response' => $this->_classes['response']); - $options += $defaults; - - if ($this->write($message)) { - $body = $this->read(); - $response = new $options['classes']['response'](compact('body')); - return $response; - } + return is_resource($this->_resource) ? stream_encoding($this->_resource, $charset) : false; } } diff --git a/libraries/lithium/readme.wiki b/libraries/lithium/readme.wiki new file mode 100755 index 0000000..b3e561d --- /dev/null +++ b/libraries/lithium/readme.wiki @@ -0,0 +1,29 @@ +#### You asked for a better framework. Here it is. + +Lithium is the fast, flexible and most RAD development framework for PHP 5.3 and up. + +##### A framework of firsts + +Lithium is the first and only major PHP framework built from the ground up for PHP 5.3+, and the first to break ground into major new technologies, including bridging the gap between relational and non-relational databases through a single, unified API. + +##### Promiscuously opinionated + +Some frameworks give you a solid set of classes, but little or no default project organization, leaving you to fend for yourself on each project you create, and spend time wiring up framework classes that should just work together. Others provide you with great organizational conventions, but no way to break out of those conventions if you need to, and too often, no way to override or replace core framework classes. + +Lithium is the first framework to give you the best of both worlds, without compromising either. In fact, Lithium's API is intentionally designed to allow you to "grow out of" the framework and into your own custom code over the course of your application's lifecycle, if your needs require. + +#### Technology + +Lithium takes full advantage of the latest PHP 5.3 features, including namespaces, late static binding and closures. Lithium's innovative [method filter system](/docs/lithium/util/collection/Filters) makes extensive use of closures and anonymous functions to allow application developers to "wrap" framework method calls, intercepting parameters before, and return values after. + +Lithium also complies with the PHP 5.3 namespacing standard, allowing you to easily integrate other PHP 5.3 standard libraries and frameworks with Lithium applications, and vice-versa. + +Lithium integrates the latest storage technologies, including MongoDB, CouchDB and Redis, with plugin support for Cassandra, ElasticSearch and others. + +#### Flexibility + +Lithium gives you full control over your application, from filters to dynamically modify framework internals, to dynamic dependencies to extend and replace core classes with application or plugin classes, to heavy use of adapter-oriented configurations, to make it seamless to move between different technologies and options. + +Every component of the Lithium framework stack is replaceable through the robust plugin architecture. Swap out the default ORM / ODM implementation for [Doctrine 2](http://dev.lithify.me/li3_doctrine/) or [PHP ActiveRecord](http://dev.lithify.me/li3_activerecord). Don't like the templating? Use [ Twig](http://dev.lithify.me/li3_twig), [ Mustache](https://github.com/bobthecow/mustache.php), or roll your own. + +If you don't even need to write a full application, build a micro-app in a single file using the routing system, without giving up the maintainability of the framework's structure. \ No newline at end of file diff --git a/libraries/lithium/security/Auth.php b/libraries/lithium/security/Auth.php index c3b2ab8..9c76f4a 100644 --- a/libraries/lithium/security/Auth.php +++ b/libraries/lithium/security/Auth.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -102,6 +102,7 @@ class Auth extends \lithium\core\Adaptable { * @return array After a successful credential check against the adapter (or a successful * lookup against the current session), returns an array of user information from the * storage backend used by the configured adapter. + * @filter */ public static function check($name, $credentials = null, array $options = array()) { $defaults = array('checkSession' => true, 'writeSession' => true); @@ -113,7 +114,7 @@ class Auth extends \lithium\core\Adaptable { $config = $self::invokeMethod('_config', array($name)); if ($config === null) { - throw new ConfigException("Configuration '{$name}' has not been defined."); + throw new ConfigException("Configuration `{$name}` has not been defined."); } $session = $config['session']; @@ -145,6 +146,7 @@ class Auth extends \lithium\core\Adaptable { * set by the default session configuration for `$name`. * @return array Returns the array of data written to the session, or `false` if the adapter * rejects the data. + * @filter */ public static function set($name, $data, array $options = array()) { $params = compact('name', 'data', 'options'); @@ -174,6 +176,7 @@ class Auth extends \lithium\core\Adaptable { * - `'clearSession'` _boolean_: If `true` (the default), session data for the * specified configuration is removed, otherwise it is retained. * @return void + * @filter */ public static function clear($name, array $options = array()) { $defaults = array('clearSession' => true); diff --git a/libraries/lithium/security/auth/adapter/Form.php b/libraries/lithium/security/auth/adapter/Form.php index a57f503..fa9cd91 100644 --- a/libraries/lithium/security/auth/adapter/Form.php +++ b/libraries/lithium/security/auth/adapter/Form.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -19,17 +19,17 @@ use lithium\core\Libraries; * apply any filters as appropriate (see the `'filters'` configuration setting below), and * query a model class using using the filtered data. * - * By default, the adapter uses a model called `User`, and lookup fields called `'username'` and + * By default, the adapter uses a model called `Users`, and lookup fields called `'username'` and * `'password'`. These can be customized by setting the `'model'` and `'fields'` configuration keys, - * respectively. The `'model'` key accepts either a model name (i.e. `Customer`), or a - * fully-namespaced path to a model class (i.e. `\app\models\Customer`). The `'fields'` setting + * respectively. The `'model'` key accepts either a model name (i.e. `Customers`), or a + * fully-namespaced path to a model class (i.e. `app\models\Customers`). The `'fields'` setting * accepts an array of field names to use when looking up a user. An example configuration, * including a custom model class and lookup fields might look like the following: * {{{ * Auth::config(array( * 'customer' => array( * 'adapter' => 'Form', - * 'model' => 'Customer', + * 'model' => 'Customers', * 'fields' => array('email', 'password') * ) * )); @@ -44,7 +44,7 @@ use lithium\core\Libraries; * Auth::config(array( * 'customer' => array( * 'adapter' => 'Form', - * 'model' => 'Customer', + * 'model' => 'Customers', * 'fields' => array('username' => 'login.username', 'password' => 'login.password'), * 'scope' => array('active' => true) * ) @@ -76,7 +76,7 @@ class Form extends \lithium\core\Object { /** * The name of the model class to query against. This can either be a model name (i.e. - * `'User'`), or a fully-namespaced class reference (i.e. `'app\models\User'`). When + * `'Users'`), or a fully-namespaced class reference (i.e. `'app\models\Users'`). When * authenticating users, the magic `first()` method is invoked against the model to return the * first record found when combining the conditions in the `$_scope` property with the * authentication data yielded from the `Request` object in `Form::check()`. (Note that the @@ -141,7 +141,7 @@ class Form extends \lithium\core\Object { * which model method to call, and this method will receive the authentication query. In return, * the `Form` adapter expects a `Record` object which implements the `data()` method. See the * constructor for more information on setting this property. Defaults to `'first'`, which - * calls, for example, `User::first()`. + * calls, for example, `Users::first()`. * * @see lithium\security\auth\adapter\Form::__construct() * @see lithium\data\entity\Record::data() @@ -176,7 +176,7 @@ class Form extends \lithium\core\Object { */ public function __construct(array $config = array()) { $defaults = array( - 'model' => 'User', 'query' => 'first', 'filters' => array(), 'fields' => array( + 'model' => 'Users', 'query' => 'first', 'filters' => array(), 'fields' => array( 'username', 'password' ) ); @@ -198,7 +198,7 @@ class Form extends \lithium\core\Object { public function check($credentials, array $options = array()) { $model = $this->_model; $query = $this->_query; - $conditions = $this->_scope + $this->_filters($credentials->data); + $conditions = $this->_scope + $this->_filters(array_map('strval', $credentials->data)); $user = $model::$query(compact('conditions')); return $user ? $user->data() : false; } @@ -246,9 +246,10 @@ class Form extends \lithium\core\Object { foreach ($this->_fields as $key => $field) { $result[$field] = isset($data[$key]) ? $data[$key] : null; - if (isset($this->_filters[$key])) { - $result[$field] = call_user_func($this->_filters[$key], $result[$field]); + if (!isset($this->_filters[$key])) { + continue; } + $result[$field] = call_user_func($this->_filters[$key], $result[$field]); } return isset($this->_filters[0]) ? call_user_func($this->_filters[0], $result) : $result; } diff --git a/libraries/lithium/security/auth/adapter/Http.php b/libraries/lithium/security/auth/adapter/Http.php index 493c48c..f5dcd96 100644 --- a/libraries/lithium/security/auth/adapter/Http.php +++ b/libraries/lithium/security/auth/adapter/Http.php @@ -2,14 +2,12 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\security\auth\adapter; -use lithium\core\Libraries; - /** * The `Http` adapter provides basic and digest authentication based on the HTTP protocol. * By default, the adapter uses Http Digest based authentication. @@ -133,7 +131,7 @@ class Http extends \lithium\core\Object { $nonce = uniqid(); $opaque = md5($realm); - $message = "WWW-Authenticate: Digest realm=\"{$realm}\" qop=\"auth\","; + $message = "WWW-Authenticate: Digest realm=\"{$realm}\",qop=\"auth\","; $message .= "nonce=\"{$nonce}\",opaque=\"{$opaque}\""; $this->_writeHeader($message); return; diff --git a/libraries/lithium/storage/Cache.php b/libraries/lithium/storage/Cache.php index 582f58a..1d3c3fe 100644 --- a/libraries/lithium/storage/Cache.php +++ b/libraries/lithium/storage/Cache.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -51,9 +51,9 @@ class Cache extends \lithium\core\Adaptable { /** * Stores configurations for cache adapters * - * @var object Collection of cache configurations + * @var array */ - protected static $_configurations = null; + protected static $_configurations = array(); /** * Libraries::locate() compatible path to adapters for this class. diff --git a/libraries/lithium/storage/Session.php b/libraries/lithium/storage/Session.php index 3baa386..a89fac9 100644 --- a/libraries/lithium/storage/Session.php +++ b/libraries/lithium/storage/Session.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -77,19 +77,6 @@ class Session extends \lithium\core\Adaptable { } /** - * Checks the validity of a previously-started session by running several checks, including - * comparing the session start time to the expiration time set in the configuration, and any - * security settings. - * - * @todo Implement - * @param string $name Named session configuration. - * @return boolean Returns true if the current session is active and valid. - */ - public static function isValid($name = null) { - - } - - /** * Reads a value from a persistent session store. * * @param string $key Key to be read @@ -161,7 +148,7 @@ class Session extends \lithium\core\Adaptable { foreach ($methods as $name => $method) { $filters = $settings['filters']; - $result = $result || static::_filter(__FUNCTION__, $params, $method, $filters); + $result = static::_filter(__FUNCTION__, $params, $method, $filters) || $result; } return $result; } @@ -198,10 +185,11 @@ class Session extends \lithium\core\Adaptable { $key = static::applyStrategies(__FUNCTION__, $name, $key, $options); } $params = compact('key', 'options'); + foreach ($methods as $name => $method) { $settings = static::_config($name); $filters = $settings['filters']; - $result = $result || static::_filter(__FUNCTION__, $params, $method, $filters); + $result = static::_filter(__FUNCTION__, $params, $method, $filters) || $result; } return $result; } @@ -211,6 +199,7 @@ class Session extends \lithium\core\Adaptable { * session adapters. * * @param array $options Optional parameters that this method accepts. + * @filter */ public static function clear(array $options = array()) { $defaults = array('name' => null, 'strategies' => true); @@ -232,7 +221,7 @@ class Session extends \lithium\core\Adaptable { foreach ($methods as $name => $method) { $settings = static::_config($name); $filters = $settings['filters']; - $result = $result || static::_filter(__FUNCTION__, $params, $method, $filters); + $result = static::_filter(__FUNCTION__, $params, $method, $filters) || $result; } if ($options['strategies']) { $options += array('mode' => 'LIFO', 'class' => __CLASS__); @@ -270,7 +259,7 @@ class Session extends \lithium\core\Adaptable { foreach ($methods as $name => $method) { $settings = static::_config($name); $filters = $settings['filters']; - $result = $result || static::_filter(__FUNCTION__, $params, $method, $filters); + $result = static::_filter(__FUNCTION__, $params, $method, $filters) || $result; } if ($options['strategies']) { $options += array('key' => $key, 'mode' => 'LIFO', 'class' => __CLASS__); diff --git a/libraries/lithium/storage/cache/adapter/Apc.php b/libraries/lithium/storage/cache/adapter/Apc.php old mode 100755 new mode 100644 index 36c2091..37acf16 --- a/libraries/lithium/storage/cache/adapter/Apc.php +++ b/libraries/lithium/storage/cache/adapter/Apc.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/storage/cache/adapter/File.php b/libraries/lithium/storage/cache/adapter/File.php old mode 100755 new mode 100644 index 8660987..50ff6e0 --- a/libraries/lithium/storage/cache/adapter/File.php +++ b/libraries/lithium/storage/cache/adapter/File.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -11,6 +11,7 @@ namespace lithium\storage\cache\adapter; use SplFileInfo; use RecursiveIteratorIterator; use RecursiveDirectoryIterator; +use lithium\core\Libraries; /** * A minimal file-based cache. @@ -28,7 +29,7 @@ use RecursiveDirectoryIterator; * This adapter does *not* allow multi-key operations for any methods. * * The path that the cached files will be written to defaults to - * `LITHIUM_APP_PATH/resources/tmp/cache`, but is user-configurable on cache configuration. + * `<app>/resources/tmp/cache`, but is user-configurable on cache configuration. * * Note that the cache expiration time is stored within the first few bytes * of the cached data, and is transparently added and/or removed when values @@ -45,14 +46,14 @@ class File extends \lithium\core\Object { * @param array $config Configuration parameters for this cache adapter. These settings are * indexed by name and queryable through `Cache::config('name')`. * The defaults are: - * - 'path' : Path where cached entries live `LITHIUM_APP_PATH . '/resources/tmp/cache' + * - 'path' : Path where cached entries live `LITHIUM_APP_PATH . '/resources/tmp/cache'`. * - 'expiry' : Default expiry time used if none is explicitly set when calling * `Cache::write()`. * @return void */ public function __construct(array $config = array()) { $defaults = array( - 'path' => LITHIUM_APP_PATH . '/resources/tmp/cache', + 'path' => Libraries::get(true, 'resources') . '/tmp/cache', 'prefix' => '', 'expiry' => '+1 hour', ); @@ -76,10 +77,8 @@ class File extends \lithium\core\Object { $expiry = strtotime($expiry); $data = "{:expiry:{$expiry}}\n{$params['data']}"; $path = "{$path}/{$params['key']}"; - return file_put_contents($path, $data); }; - } /** @@ -109,9 +108,7 @@ class File extends \lithium\core\Object { return false; } return preg_replace('/^\{\:expiry\:\d+\}\\n/', '', $data, 1); - }; - } /** @@ -131,7 +128,6 @@ class File extends \lithium\core\Object { if ($file->isFile() && $file->isReadable()) { return unlink($path); } - return false; }; } @@ -181,7 +177,6 @@ class File extends \lithium\core\Object { } } return true; - } /** diff --git a/libraries/lithium/storage/cache/adapter/Memcache.php b/libraries/lithium/storage/cache/adapter/Memcache.php old mode 100755 new mode 100644 index 130b31a..57a5410 --- a/libraries/lithium/storage/cache/adapter/Memcache.php +++ b/libraries/lithium/storage/cache/adapter/Memcache.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/storage/cache/adapter/Memory.php b/libraries/lithium/storage/cache/adapter/Memory.php old mode 100755 new mode 100644 index efb7f2d..5d17379 --- a/libraries/lithium/storage/cache/adapter/Memory.php +++ b/libraries/lithium/storage/cache/adapter/Memory.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/storage/cache/adapter/Redis.php b/libraries/lithium/storage/cache/adapter/Redis.php old mode 100755 new mode 100644 index 37797fd..47b6ac8 --- a/libraries/lithium/storage/cache/adapter/Redis.php +++ b/libraries/lithium/storage/cache/adapter/Redis.php @@ -2,17 +2,19 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\storage\cache\adapter; +use Redis as RedisCore; + /** * A Redis (phpredis) cache adapter implementation. * * This adapter uses the `phpredis` PHP extension, which can be found here: - * http://github.com/owlient/phpredis + * https://github.com/nicolasff/phpredis * * The Redis cache adapter is meant to be used through the `Cache` interface, * which abstracts away key generation, adapter instantiation and filter @@ -27,12 +29,12 @@ namespace lithium\storage\cache\adapter; * Cache::config(array( * 'cache-config-name' => array( * 'adapter' => 'Redis', - * 'server' => '127.0.0.1:6379' + * 'host' => '127.0.0.1:6379' * ) * )); * }}} * - * The 'server' key accepts a string argument in the format of ip:port where the Redis + * The 'host' key accepts a string argument in the format of ip:port where the Redis * server can be found. * * This Redis adapter provides basic support for `write`, `read`, `delete` @@ -41,7 +43,7 @@ namespace lithium\storage\cache\adapter; * * @see lithium\storage\Cache::key() * @see lithium\storage\Cache::adapter() - * @link http://github.com/owlient/phpredis GitHub: PhpRedis Extension + * @link https://github.com/nicolasff/phpredis GitHub: PhpRedis Extension * */ class Redis extends \lithium\core\Object { @@ -51,24 +53,35 @@ class Redis extends \lithium\core\Object { * * @var object Redis object */ - public static $connection = null; + public $connection; /** * Object constructor * - * Instantiates the Redis object and connects it to the configured server. + * Instantiates the `Redis` object and connects it to the configured server. * * @todo Implement configurable & optional authentication * @see lithium\storage\Cache::config() + * @see lithium\storage\cache\adapter\Redis::_ttl() * @param array $config Configuration parameters for this cache adapter. - * These settings are indexed by name and queryable through `Cache::config('name')`. + * These settings are indexed by name and queryable through `Cache::config('name')`. The + * available settings for this adapter are as follows: + * - `'host'` _string_: A string in the form of `'host:port'` indicating the Redis server + * to connect to. Defaults to `'127.0.0.1:6379'`. + * - `'expiry'` _mixed_: Default expiration for cache values written through this + * adapter. Defaults to `'+1 hour'`. For acceptable values, see the `$expiry` parameter + * of `Redis::_ttl()`. + * - `'persistent'` _boolean_: Indicates whether the adapter should use a persistent + * connection when attempting to connect to the Redis server. If `true`, it will + * attempt to reuse an existing connection when connecting, and the connection will + * not close when the request is terminated. Defaults to `false`. * @return void */ public function __construct(array $config = array()) { $defaults = array( - 'prefix' => '', + 'host' => '127.0.0.1:6379', 'expiry' => '+1 hour', - 'server' => '127.0.0.1:6379', + 'persistent' => false, ); parent::__construct($config + $defaults); } @@ -79,22 +92,24 @@ class Redis extends \lithium\core\Object { * @return void */ protected function _init() { - if (!static::$connection) { - static::$connection = new \Redis(); + if (!$this->connection) { + $this->connection = new RedisCore(); } - list($IP, $port) = explode(':', $this->_config['server']); - static::$connection->connect($IP, $port); + list($ip, $port) = explode(':', $this->_config['host']); + $method = $this->_config['persistent'] ? 'pconnect' : 'connect'; + $this->connection->{$method}($ip, $port); } /** * Sets expiration time for cache keys * * @param string $key The key to uniquely identify the cached item - * @param string $expiry A strtotime() compatible cache time - * @return boolean True if expiry could be set for the given key, false otherwise + * @param mixed $expiry A `strtotime()`-compatible string indicating when the cached item + * should expire, or a Unix timestamp. + * @return boolean Returns `true` if expiry could be set for the given key, `false` otherwise. */ protected function _ttl($key, $expiry) { - return static::$connection->expireAt($key, strtotime($expiry)); + return $this->connection->expireAt($key, is_int($expiry) ? $expiry : strtotime($expiry)); } /** @@ -107,10 +122,11 @@ class Redis extends \lithium\core\Object { * @return boolean True on successful write, false otherwise */ public function write($key, $value = null, $expiry = null) { - $connection =& static::$connection; + $connection =& $this->connection; $expiry = ($expiry) ?: $this->_config['expiry']; + $_self =& $this; - return function($self, $params) use (&$connection, $expiry) { + return function($self, $params) use (&$_self, &$connection, $expiry) { if (is_array($params['key'])) { $expiry = $params['data']; @@ -119,7 +135,7 @@ class Redis extends \lithium\core\Object { if ($expiry) { foreach ($params['key'] as $k => $v) { - $ttl[$k] = $self->invokeMethod('_ttl', array($k, $expiry)); + $ttl[$k] = $_self->invokeMethod('_ttl', array($k, $expiry)); } } return $ttl; @@ -127,7 +143,7 @@ class Redis extends \lithium\core\Object { } if ($result = $connection->set($params['key'], $params['data'])){ if ($expiry) { - return $self->invokeMethod('_ttl', array($params['key'], $expiry)); + return $_self->invokeMethod('_ttl', array($params['key'], $expiry)); } return $result; } @@ -141,7 +157,7 @@ class Redis extends \lithium\core\Object { * @return mixed Cached value if successful, false otherwise */ public function read($key) { - $connection =& static::$connection; + $connection =& $this->connection; return function($self, $params) use (&$connection) { $key = $params['key']; @@ -160,7 +176,7 @@ class Redis extends \lithium\core\Object { * @return mixed True on successful delete, false otherwise */ public function delete($key) { - $connection =& static::$connection; + $connection =& $this->connection; return function($self, $params) use (&$connection) { return (boolean) $connection->delete($params['key']); @@ -179,7 +195,7 @@ class Redis extends \lithium\core\Object { * @return mixed Item's new value on successful decrement, false otherwise */ public function decrement($key, $offset = 1) { - $connection =& static::$connection; + $connection =& $this->connection; return function($self, $params) use (&$connection, $offset) { return $connection->decr($params['key'], $offset); @@ -198,7 +214,7 @@ class Redis extends \lithium\core\Object { * @return mixed Item's new value on successful increment, false otherwise */ public function increment($key, $offset = 1) { - $connection =& static::$connection; + $connection =& $this->connection; return function($self, $params) use (&$connection, $offset) { return $connection->incr($params['key'], $offset); @@ -211,7 +227,7 @@ class Redis extends \lithium\core\Object { * @return mixed True on successful clear, false otherwise */ public function clear() { - return static::$connection->flushdb(); + return $this->connection->flushdb(); } /** diff --git a/libraries/lithium/storage/cache/adapter/XCache.php b/libraries/lithium/storage/cache/adapter/XCache.php old mode 100755 new mode 100644 index 180f69b..b64d036 --- a/libraries/lithium/storage/cache/adapter/XCache.php +++ b/libraries/lithium/storage/cache/adapter/XCache.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/storage/cache/strategy/Base64.php b/libraries/lithium/storage/cache/strategy/Base64.php index eeb4c7e..73ba845 100644 --- a/libraries/lithium/storage/cache/strategy/Base64.php +++ b/libraries/lithium/storage/cache/strategy/Base64.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/storage/cache/strategy/Json.php b/libraries/lithium/storage/cache/strategy/Json.php index e32ac6d..16497b4 100644 --- a/libraries/lithium/storage/cache/strategy/Json.php +++ b/libraries/lithium/storage/cache/strategy/Json.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/storage/cache/strategy/Serializer.php b/libraries/lithium/storage/cache/strategy/Serializer.php index a389fd6..8b99693 100644 --- a/libraries/lithium/storage/cache/strategy/Serializer.php +++ b/libraries/lithium/storage/cache/strategy/Serializer.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/storage/session/adapter/Cookie.php b/libraries/lithium/storage/session/adapter/Cookie.php old mode 100755 new mode 100644 index 993fc4e..170ff07 --- a/libraries/lithium/storage/session/adapter/Cookie.php +++ b/libraries/lithium/storage/session/adapter/Cookie.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -114,14 +114,19 @@ class Cookie extends \lithium\core\Object { return function($self, $params) use (&$config) { $key = $params['key']; if (!$key) { - return $_COOKIE; + if (isset($_COOKIE[$config['name']])) { + return $_COOKIE[$config['name']]; + } + return array(); } if (strpos($key, '.') !== false) { $key = explode('.', $key); - $result = $_COOKIE[$config['name']]; + $result = (isset($_COOKIE[$config['name']])) ? $_COOKIE[$config['name']] : array(); foreach ($key as $k) { - $result = $result[$k]; + if (isset($result[$k])) { + $result = $result[$k]; + } } return ($result !== array()) ? $result : null; } @@ -142,38 +147,32 @@ class Cookie extends \lithium\core\Object { public function write($key, $value = null, array $options = array()) { $expire = (!isset($options['expire']) && empty($this->_config['expire'])); $config = $this->_config; + $cookieClass = __CLASS__; if ($expire && $key != $config['name']) { return null; } $expires = (isset($options['expire'])) ? $options['expire'] : $config['expire']; - return function($self, $params) use (&$config, &$expires) { + return function($self, $params) use (&$config, &$expires, $cookieClass) { $key = $params['key']; $value = $params['value']; - $key = is_array($key) ? Set::flatten($key) : array($key => $value); + $key = array($key => $value); + if (is_array($value)) { + $key = Set::flatten($key); + } foreach ($key as $name => $val) { - $name = explode('.', $name); - $name = $config['name'] ? array_merge(array($config['name']), $name) : $name; - - if (count($name) == 1) { - $name = current($name); - } else { - $name = (array_shift($name) . '[' . join('][', $name) . ']'); - } - if (is_array($val)) { - foreach ($val as $key => $v) { - setcookie($name . "[$key]", $v, strtotime($expires), $config['path'], - $config['domain'], $config['secure'], $config['httponly'] - ); - } - return true; - } - setcookie($name, $val, strtotime($expires), $config['path'], + $name = $cookieClass::keyFormat($name, $config); + $result = setcookie($name, $val, strtotime($expires), $config['path'], $config['domain'], $config['secure'], $config['httponly'] ); + + if (!$result) { + throw new RuntimeException("There was an error setting {$name} cookie."); + } } + return true; }; } @@ -186,26 +185,75 @@ class Cookie extends \lithium\core\Object { */ public function delete($key, array $options = array()) { $config = $this->_config; + $cookieClass = get_called_class(); - return function($self, $params) use (&$config) { + return function($self, $params) use (&$config, $cookieClass) { $key = $params['key']; - $key = is_array($key) ? Set::flatten($key) : array($key); + $path = '/' . str_replace('.', '/', $config['name'] . '.' . $key) . '/.'; + $cookies = current(Set::extract($_COOKIE, $path)); + if (is_array($cookies)) { + $cookies = array_keys(Set::flatten($cookies)); + foreach ($cookies as &$name) { + $name = $key . '.' . $name; + } + } else { + $cookies = array($key); + } + foreach ($cookies as &$name) { + $name = $cookieClass::keyFormat($name, $config); + $result = setcookie($name, "", 1, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + if (!$result) { + throw new RuntimeException("There was an error deleting {$name} cookie."); + } + } + return true; + }; + } - foreach ($key as $name) { - $name = explode('.', $name); - $name = $config['name'] ? array_merge(array($config['name']), $name) : $name; + /** + * Clears all cookies. + * + * @param array $options Options array. Not used fro this adapter method. + * @return boolean True on successful clear, false otherwise. + */ + public function clear(array $options = array()) { + $options += array('destroySession' => true); + $config = $this->_config; + $cookieClass = get_called_class(); - if (count($name) == 1) { - $name = current($name); - } else { - $name = (array_shift($name) . '[' . join('][', $name) . ']'); - } - setcookie($name, "", time() - 1, $config['path'], + return function($self, $params) use (&$config, $options, $cookieClass) { + if ($options['destroySession'] && session_id()) { + session_destroy(); + } + if (!isset($_COOKIE[$config['name']])) { + return true; + } + $cookies = array_keys(Set::flatten($_COOKIE[$config['name']])); + foreach ($cookies as $name) { + $name = $cookieClass::keyFormat($name, $config); + $result = setcookie($name, "", 1, $config['path'], $config['domain'], $config['secure'], $config['httponly'] ); + if (!$result) { + throw new RuntimeException("There was an error clearing {$cookie} cookie."); + } } + unset($_COOKIE[$config['name']]); + return true; }; } + + /** + * Formats the given `$name` argument for use in the cookie adapter. + * + * @param string $name The key to be formatted, e.g. `foo.bar.baz`. + * @return string The formatted key. + */ + public static function keyFormat($name, $config) { + return $config['name'] . '[' . str_replace('.', '][', $name) . ']'; + } } ?> \ No newline at end of file diff --git a/libraries/lithium/storage/session/adapter/Memory.php b/libraries/lithium/storage/session/adapter/Memory.php old mode 100755 new mode 100644 index a3cd4c4..0478a08 --- a/libraries/lithium/storage/session/adapter/Memory.php +++ b/libraries/lithium/storage/session/adapter/Memory.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -25,15 +25,12 @@ class Memory extends \lithium\core\Object { /** * Obtain the session key. * - * For this adapter, it is a UUID based on the SERVER_ADDR variable. + * For this adapter, it is a UUID. * * @return string UUID. */ public static function key() { - $context = function ($value) use (&$config) { - return (isset($_SERVER['SERVER_ADDR'])) ? $_SERVER['SERVER_ADDR'] : '127.0.0.1'; - }; - return String::uuid($context); + return String::uuid(); } /** diff --git a/libraries/lithium/storage/session/adapter/Php.php b/libraries/lithium/storage/session/adapter/Php.php old mode 100755 new mode 100644 index 5a56c61..7599da8 --- a/libraries/lithium/storage/session/adapter/Php.php +++ b/libraries/lithium/storage/session/adapter/Php.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -94,11 +94,15 @@ class Php extends \lithium\core\Object { } /** - * Obtain the session id. + * Sets or obtains the session ID. * - * @return mixed Session id, or null if the session has not been started. + * @param string $key Optional. If specified, sets the session ID to the value of `$key`. + * @return mixed Session ID, or `null` if the session has not been started. */ - public static function key() { + public static function key($key = null) { + if ($key) { + return session_id($key); + } return session_id() ?: null; } diff --git a/libraries/lithium/storage/session/strategy/Hmac.php b/libraries/lithium/storage/session/strategy/Hmac.php index cb3bdb4..5a606c0 100644 --- a/libraries/lithium/storage/session/strategy/Hmac.php +++ b/libraries/lithium/storage/session/strategy/Hmac.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,20 +10,20 @@ namespace lithium\storage\session\strategy; use RuntimeException; use lithium\core\ConfigException; +use lithium\storage\session\strategy\MissingSignatureException; /** - * This strategy allows you to sign your `Session` and/or `Cookie` data with a passphrase + * This strategy allows you to sign your `Session` and / or `Cookie` data with a passphrase * to ensure that it has not been tampered with. * * Example configuration: * * {{{ - * Session::config(array( - * 'default' => array( - * 'adapter' => 'Cookie', - * 'strategies' => array('Hmac' => array('secret' => 'foobar')) - * ) - * )); + * Session::config(array('default' => array( + * 'adapter' => 'Cookie', + * 'strategies' => array('Hmac' => array('secret' => 'foobar')) + * ))); + * }}} * * This will configure the `HMAC` strategy to be used for all `Session` operations with the * `default` named configuration. A hash-based message authentication code (HMAC) will be @@ -91,6 +91,11 @@ class Hmac extends \lithium\core\Object { * Validates the HMAC signature of the stored data. If the signatures match, then * the data is safe, and the 'valid' key in the returned data will be * + * If the store being read does not contain a `__signature` field, a `MissingSignatureException` + * is thrown. When catching this exception, you may choose to handle it by either writing + * out a signature (e.g. in cases where you know that no pre-existing signature may exist), or + * you can blackhole it as a possible tampering attempt. + * * @param array $data the Data being read. * @param array $options Options for this method. * @return array validated data @@ -98,20 +103,16 @@ class Hmac extends \lithium\core\Object { public function read($data, array $options = array()) { $class = $options['class']; - $futureData = $class::read(null, array('strategies' => false)); - unset($futureData['__signature']); + $currentData = $class::read(null, array('strategies' => false)); - if (!isset($futureData['__signature'])) { - $signature = hash_hmac('sha1', serialize($futureData), static::$_secret); - $class::write('__signature', $signature, array('strategies' => false) + $options); - return $data; + if (!isset($currentData['__signature'])) { + throw new MissingSignatureException('HMAC signature not found.'); } - - $currentSignature = $futureData['__signature']; - $signature = static::_signature($futureData); + $currentSignature = $currentData['__signature']; + $signature = static::_signature($currentData); if ($signature !== $currentSignature) { - $message = "Possible data tampering - HMAC signature does not match data."; + $message = "Possible data tampering: HMAC signature does not match data."; throw new RuntimeException($message); } return $data; @@ -131,7 +132,7 @@ class Hmac extends \lithium\core\Object { $class = $options['class']; $futureData = $class::read(null, array('strategies' => false)); - unset($futureData[$options['key']], $futureData['__signature']); + unset($futureData[$options['key']]); $signature = static::_signature($futureData); $class::write('__signature', $signature, array('strategies' => false) + $options); @@ -146,6 +147,7 @@ class Hmac extends \lithium\core\Object { * @return string HMAC signature. */ protected static function _signature($data, $secret = null) { + unset($data['__signature']); $secret = ($secret) ?: static::$_secret; return hash_hmac('sha1', serialize($data), $secret); } diff --git a/libraries/lithium/storage/session/strategy/MissingSignatureException.php b/libraries/lithium/storage/session/strategy/MissingSignatureException.php new file mode 100644 index 0000000..a80a97b --- /dev/null +++ b/libraries/lithium/storage/session/strategy/MissingSignatureException.php @@ -0,0 +1,20 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\storage\session\strategy; + +/** + * A `MissingSignatureException` may be thrown when reading data from a session-based storage that + * is expecting an HMAC signature, but none is found.. + */ +class MissingSignatureException extends \RuntimeException { + + protected $code = 403; +} + +?> \ No newline at end of file diff --git a/libraries/lithium/template/Helper.php b/libraries/lithium/template/Helper.php index 666d2f3..339a465 100644 --- a/libraries/lithium/template/Helper.php +++ b/libraries/lithium/template/Helper.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -42,6 +42,13 @@ abstract class Helper extends \lithium\core\Object { protected $_context = null; /** + * This property can be overwritten with any class dependencies a helper subclass has. + * + * @var array + */ + protected $_classes = array(); + + /** * Auto configuration properties. * * @var array @@ -55,7 +62,7 @@ abstract class Helper extends \lithium\core\Object { */ protected $_minimized = array( 'compact', 'checked', 'declare', 'readonly', 'disabled', 'selected', 'defer', 'ismap', - 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize' + 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize', 'async' ); public function __construct(array $config = array()) { @@ -133,7 +140,9 @@ abstract class Helper extends \lithium\core\Object { if ($this->_context) { foreach ($params as $key => $value) { - $params[$key] = $this->_context->applyHandler($this, $method, $key, $value, $options); + $params[$key] = $this->_context->applyHandler( + $this, $method, $key, $value, $options + ); } $strings = $this->_context->strings(); } diff --git a/libraries/lithium/template/TemplateException.php b/libraries/lithium/template/TemplateException.php index 1199acd..bd9ae62 100644 --- a/libraries/lithium/template/TemplateException.php +++ b/libraries/lithium/template/TemplateException.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/template/View.php b/libraries/lithium/template/View.php index 95bf695..7a92d7f 100644 --- a/libraries/lithium/template/View.php +++ b/libraries/lithium/template/View.php @@ -2,42 +2,53 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\template; -use RuntimeException; use lithium\core\Libraries; +use lithium\template\TemplateException; /** * As one of the three pillars of the Model-View-Controller design pattern, the `View` class * (along with other supporting classes) is responsible for taking the data passed from the - * request and/or controller, inserting this into the requested view/layout, and then presenting - * the rendered content in the appropriate content-type. + * request and/or controller, inserting this into the requested template/layout, and then returning + * the rendered content. * * The `View` class interacts with a variety of other classes in order to achieve maximum * flexibility and configurability at all points in the view rendering and presentation * process. The `Loader` class is tasked with locating and reading template files which are then * passed to the `Renderer` adapter subclass. * - * It is also possible to instantiate and call `View` directly, in cases where you wish to bypass - * all other parts of the framework and simply return rendered content. + * In the default configuration, the `File` adapter acts as both renderer and loader, loading files + * from paths defined in _process steps_ (described below) and rendering them as plain PHP files, + * augmented with [special syntax](../template). + * + * The `View` class operates on _processes_, which define the steps to render a completed view. For + * example, the default process, which renders a template wrapped in a layout, is comprised of two + * _steps_: the first step renders the main template and captures it to the rendering context, where + * it is embedded in the layout in the second step. See the `$_steps` and `$_processes` properties + * for more information. + * + * Using steps and processes, you can create rendering scenarios to suit very complex needs. + * + * By default, the `View` class is called during the course of the framework's dispatch cycle by the + * `Media` class. However, it is also possible to instantiate and call `View` directly, in cases + * where you wish to bypass all other parts of the framework and simply return rendered content. * * A simple example, using the `Simple` renderer/loader for string templates: * * {{{ * $view = new View(array('loader' => 'Simple', 'renderer' => 'Simple')); - * echo $view->render(array('element' => 'Hello, {:name}!'), array( - * 'name' => "Robert" - * )); + * echo $view->render('element', array('name' => "Robert"), array('element' => 'Hello, {:name}!')); * * // Output: * "Hello, Robert!"; * }}} * - * (note: This is easily adapted for XML templating). + * _Note_: This is easily adapted for XML templating. * * Another example, this time of something that could be used in an appliation * error handler: @@ -50,15 +61,17 @@ use lithium\core\Libraries; * ) * )); * - * echo $View->render('all', array('content' => $info), array( + * $page = $View->render('all', array('content' => $info), array( * 'template' => '404', - * 'type' => 'html', * 'layout' => 'error' * )); * }}} * - * @see lithium\view\Renderer - * @see lithium\view\adapter + * To learn more about processes and process steps, see the `$_processes` and `$_steps` properties, + * respectively. + * + * @see lithium\template\view\Renderer + * @see lithium\template\view\adapter * @see lithium\net\http\Media */ class View extends \lithium\core\Object { @@ -105,21 +118,81 @@ class View extends \lithium\core\Object { protected $_renderer = null; /** + * View processes are aggregated lists of steps taken to to create a complete, rendered view. + * For example, the default process, `'all'`, renders a template, then renders a layout, using + * the rendered template content. A process can be defined using one or more steps defined in + * the `$_steps` property. Each process definition is a simple array of ordered values, where + * each value is a key in the `$_steps` array. + * + * @see lithium\template\View::$_steps + * @see lithium\template\View::render() + * @var array + */ + protected $_processes = array( + 'all' => array('template', 'layout'), + 'template' => array('template'), + 'element' => array('element') + ); + + /** + * The list of available rendering steps. Each step contains instructions for how to render one + * piece of a multi-step view rendering. The `View` class combines multiple steps into + * _processes_ to create the final output. + * + * Each step is named by its key in the `$_steps` array, and can have the following options: + * + * - `'path'` _string_: Indicates the set of paths to use when loading templates. + * + * - `'conditions'` _mixed_: Make the step dependent on a value being present, or on some other + * arbitrary condition. If a `'conditions'` is a string, it indicates that a key with that + * name must be present in the `$options` passed to `render()`, and must be set to a + * non-empty value. If a closure, it will be executed with the rendering parameters, and must + * return `true` or `false`. In either case, if the condition is satisfied, the step is + * processed. Otherwise, it is skipped. See the `_conditions()` method for more information. + * + * - `'capture'` _array_: If specified, allows the results of this rendering step to be assigned + * to a template variable used in subsequent steps, or to the templating context for use in + * subsequent steps. If can be specified in the form of `array('context' => '<var-name>')` or + * `array('data' => '<var-name>')`. If the `'context'` key is used, the results are captured + * to the rendering context. Likewise with the `'data'` key, results are captured to a + * template variable. + * + * - `'multi'` _boolean_: If set to `true`, the rendering parameter matching the name of this + * step can be an array containing multiple values, in which case this step is executed + * multiple times, once for each value of the array. + * + * @see lithium\template\View::$_processes + * @see lithium\template\View::render() + * @var array + */ + protected $_steps = array( + 'template' => array('path' => 'template', 'capture' => array('context' => 'content')), + 'layout' => array( + 'path' => 'layout', 'conditions' => 'layout', 'multi' => true, 'capture' => array( + 'context' => 'content' + ) + ), + 'element' => array('path' => 'element') + ); + + /** * Auto-configuration parameters. * * @var array Objects to auto-configure. */ - protected $_autoConfig = array('request', 'response'); + protected $_autoConfig = array( + 'request', 'response', 'processes' => 'merge', 'steps' => 'merge' + ); /** * Constructor. * * @param array $config Configuration parameters. * The available options are: - * - `loader`: For locating/reading view, layout and element - * templates. Defaults to `File`. - * - `renderer`: Populates the view/layout with the data set from the controller. - * Defaults to `File`. + * - `'loader'` _mixed_: For locating/reading view, layout and element + * templates. Defaults to `File`. + * - `'renderer'` _mixed_: Populates the view/layout with the data set from the + * controller. Defaults to `'File'`. * - `request`: The request object to be made available in the view. Defalts to `null`. * - `vars`: Defaults to `array()`. * @return void @@ -131,6 +204,8 @@ class View extends \lithium\core\Object { 'vars' => array(), 'loader' => 'File', 'renderer' => 'File', + 'steps' => array(), + 'processes' => array(), 'outputFilters' => array() ); parent::__construct($config + $defaults); @@ -140,7 +215,6 @@ class View extends \lithium\core\Object { * Perform initialization of the View. * * @return void - * @throws RuntimeException when template adapter cannot be found. */ protected function _init() { parent::_init(); @@ -166,104 +240,122 @@ class View extends \lithium\core\Object { $this->outputFilters += compact('h') + $this->_config['outputFilters']; } - /** - * Render a layout, template, view or element. - * - * @param string|array $type The view type. Possible values are `element`, `template`, - * `layout` and `all`. - * @param array $data The data to be made available in the rendered view. - * @param array $options Rendering options: - * - `context`: Render context - * - `type`: The media type to render. Defaults to `html`. - * - `layout`: The layout in which the rendered view should be wrapped in. - * @return string The rendered view that was requested. - */ - public function render($type, $data = null, array $options = array()) { - $defaults = array('context' => array(), 'type' => 'html', 'layout' => null); + public function render($process, array $data = array(), array $options = array()) { + $defaults = array( + 'type' => 'html', + 'layout' => null, + 'template' => null, + 'context' => array(), + ); $options += $defaults; - $data = $data ?: array(); - $template = null; + $data += isset($options['data']) ? (array) $options['data'] : array(); + $paths = isset($options['paths']) ? (array) $options['paths'] : array(); + unset($options['data'], $options['paths']); + $params = array_filter($options, function($val) { return $val && is_string($val); }); + $result = null; - if (is_array($type)) { - list($type, $template) = each($type); + foreach ($this->_process($process, $params) as $name => $step) { + if (!$this->_conditions($step, $params, $data, $options)) { + continue; + } + if ($step['multi'] && isset($options[$name])) { + foreach ((array) $options[$name] as $value) { + $params[$name] = $value; + $result = $this->_step($step, $params, $data, $options); + } + continue; + } + $result = $this->_step((array) $step, $params, $data, $options); } - return $this->{"_" . $type}($template, $data, $options); + return $result; } - /** - * The 'all' render type handler. - * - * @param string $template Not used in this handler. Can be specified as null. - * @param array $data Template data. - * @param array $options Layout rendering options. - */ - protected function _all($template, $data, array $options = array()) { - $content = $this->render('template', $data, $options); - - if (!$options['layout']) { - return $content; + protected function _conditions($step, $params, $data, $options) { + if (!$conditions = $step['conditions']) { + return true; + } + if (is_callable($conditions) && !$conditions($params, $data, $options)) { + return false; } - $options['context'] += array('content' => $content); - return $this->_layout($template, $data, $options); + if (is_string($conditions) && !(isset($options[$conditions]) && $options[$conditions])) { + return false; + } + return true; } /** - * The 'element' render type handler. - * - * @param string $template Template to be rendered. - * @param array $data Template data. - * @param array $options Renderer options. - * @filter This method can be filtered. + * @filter */ - protected function _element($template, $data, array $options = array()) { - $options += array('controller' => 'elements', 'template' => $template); - $template = $this->_loader->template('template', $options); - $data = $data + $this->outputFilters; - $params = compact('template', 'data', 'options'); + protected function _step(array $step, array $params, array &$data, array &$options = array()) { + $step += array('path' => null, 'capture' => null); $_renderer = $this->_renderer; - $filter = function($self, $params, $chain) use (&$_renderer) { - return $_renderer->render($params['template'], $params['data'], $params['options']); + $_loader = $this->_loader; + $filters = $this->outputFilters; + $params = compact('step', 'params', 'options') + array('data' => $data + $filters); + + $filter = function($self, $params) use (&$_renderer, &$_loader) { + $template = $_loader->template($params['step']['path'], $params['params']); + return $_renderer->render($template, $params['data'], $params['options']); }; - return $this->_filter(__METHOD__, $params, $filter); + $result = $this->_filter(__METHOD__, $params, $filter); + + if (is_array($step['capture'])) { + switch (key($step['capture'])) { + case 'context': + $options['context'][current($step['capture'])] = $result; + break; + case 'data': + $data[current($step['capture'])] = $result; + break; + } + } + return $result; } /** - * The 'template' render type handler. + * Converts a process name to an array containing the rendering steps to be executed for each + * process. * - * @param string $template Template to be rendered. - * @param array $data Template data. - * @param array $options Renderer options. - * @filter This method can be filtered. + * @param string $process A named set of rendering steps. + * @param array $params + * @return array A 2-dimensional array that defines the rendering process. The first dimension + * is a numerically-indexed array containing each rendering step. The second dimension + * represents the parameters for each step. */ - protected function _template($template, $data, array $options = array()) { - $template = $this->_loader->template('template', $options); - $data = $data + $this->outputFilters; - $params = compact('template', 'data', 'options'); - $_renderer = $this->_renderer; - $filter = function($self, $params, $chain) use (&$_renderer) { - return $_renderer->render($params['template'], $params['data'], $params['options']); - }; - return $this->_filter(__METHOD__, $params, $filter); + protected function _process($process, &$params) { + $defaults = array('conditions' => null, 'multi' => false); + + if (!is_array($process)) { + if (!isset($this->_processes[$process])) { + throw new TemplateException("Undefined rendering process '{$process}'."); + } + $process = $this->_processes[$process]; + } + if (is_string(key($process))) { + return $this->_convertSteps($process, $params, $defaults); + } + $result = array(); + + foreach ($process as $step) { + if (is_array($step)) { + $result[] = $step + $defaults; + continue; + } + if (!isset($this->_steps[$step])) { + throw new TemplateException("Undefined rendering step '{$step}'."); + } + $result[$step] = $this->_steps[$step] + $defaults; + } + return $result; } - /** - * The 'layout' render type handler. - * - * @param string $template Not used in this handler. - * @param array $data Template data. - * @param array $options Renderer options. - * @filter This method can be filtered. - */ - protected function _layout($template, $data, array $options = array()) { - $template = $this->_loader->template('layout', $options); - $data = (array) $data + $this->outputFilters; - $params = compact('template', 'data', 'options'); - $_renderer = $this->_renderer; - $filter = function($self, $params, $chain) use (&$_renderer) { - return $_renderer->render($params['template'], $params['data'], $params['options']); - }; - return $this->_filter(__METHOD__, $params, $filter); + protected function _convertSteps($command, &$params, $defaults) { + if (count($command) == 1) { + $params['template'] = current($command); + return array(array('path' => key($command)) + $defaults); + } + return $command; } } diff --git a/libraries/lithium/template/helper/Form.php b/libraries/lithium/template/helper/Form.php old mode 100755 new mode 100644 index 5cfbd2d..e94c1c3 --- a/libraries/lithium/template/helper/Form.php +++ b/libraries/lithium/template/helper/Form.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -17,10 +17,10 @@ use UnexpectedValueException; * will simply generate HTML forms and widgets, but by creating a form with a _binding object_, * the helper can pre-fill form input values, render error messages, and introspect column types. * - * For example, assuming you have created a `Post` model in your application: + * For example, assuming you have created a `Posts` model in your application: * {{{// In controller code: - * use \app\models\Post; - * $post = Post::find(1); + * use app\models\Posts; + * $post = Posts::find(1); * return compact('post'); * * // In view code: @@ -144,7 +144,7 @@ class Form extends \lithium\template\Helper { if (in_array($method, array('create', 'end', 'label', 'error'))) { return; } - if (!$name || ($method == 'hidden' && $name = '_method')) { + if (!$name || ($method == 'hidden' && $name == '_method')) { return; } $id = Inflector::camelize(Inflector::slug($name)); @@ -164,7 +164,10 @@ class Form extends \lithium\template\Helper { */ protected function _init() { parent::_init(); - $this->_context->handlers(array('wrap' => '_attributes')); + + if ($this->_context) { + $this->_context->handlers(array('wrap' => '_attributes')); + } } /** @@ -248,10 +251,13 @@ class Form extends \lithium\template\Helper { * the `'action'` or `'url'` options (defaulting to the current page if none is * specified), the HTTP method is defined by the `'method'` option, and any HTML * attributes passed in `$options`. + * @filter */ public function create($binding = null, array $options = array()) { + $request = $this->_context ? $this->_context->request() : null; + $defaults = array( - 'url' => $this->_context->request()->params, + 'url' => $request ? $request->params : array(), 'type' => null, 'action' => null, 'method' => $binding ? ($binding->exists() ? 'put' : 'post') : 'post' @@ -299,6 +305,7 @@ class Form extends \lithium\template\Helper { * object used to generate the corresponding form. * * @return string Returns a closing `</form>` tag. + * @filter */ public function end() { list(, $options, $template) = $this->_defaults(__FUNCTION__, null, array()); @@ -357,7 +364,7 @@ class Form extends \lithium\template\Helper { * echo $this->form->field('name'); * echo $this->form->field('present', array('type' => 'checkbox')); * echo $this->form->field(array('email' => 'Enter a valid email')); - * echo $this->form->field(array('name','email','phone'),array('div' => false)); + * echo $this->form->field(array('name','email','phone'), array('div' => false)); * }}} * @param mixed $name The name of the field to render. If the form was bound to an object * passed in `create()`, `$name` should be the name of a field in that object. @@ -387,15 +394,7 @@ class Form extends \lithium\template\Helper { */ public function field($name, array $options = array()) { if (is_array($name)) { - $return = ''; - foreach ($name as $field => $label) { - if (is_numeric($field)) { - $field = $label; - unset($label); - } - $return .= $this->field($field, compact('label') + $options); - } - return $return; + return $this->_fields($name, $options); } $defaults = array( 'label' => null, @@ -410,7 +409,7 @@ class Form extends \lithium\template\Helper { $defaults['template'] = 'field-' . $type; } list($options, $fieldOptions) = $this->_options($defaults, $options); - list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options); + list(, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options); if ($options['template'] != $defaults['template']) { $template = $options['template']; @@ -419,10 +418,11 @@ class Form extends \lithium\template\Helper { $type = $options['type']; $label = $input = null; - if ($options['label'] === null || $options['label']) { - $for = isset($options['id']) ? $options['id'] : ''; - $label = $options['label'] ?: $options['label'] = Inflector::humanize($name); - $label = $this->label($for, $label); + if (($options['label'] === null || $options['label']) && $options['type'] != 'hidden') { + if (!$options['label']) { + $options['label'] = Inflector::humanize(preg_replace('/[\[\]\.]/', '_', $name)); + } + $label = $this->label(isset($options['id']) ? $options['id'] : '', $options['label']); } switch (true) { @@ -438,6 +438,28 @@ class Form extends \lithium\template\Helper { } /** + * Helper method used by `Form::field()` for iterating over an array of multiple fields. + * + * @see lithium\template\helper\Form::field() + * @param array $fields An array of fields to render. + * @param array $options The array of options to apply to all fields in the `$fields` array. See + * the `$options` parameter of the `field` method for more information. + * @return string Returns the fields rendered by `field()`, each separated by a newline. + */ + protected function _fields(array $fields, array $options = array()) { + $result = array(); + + foreach ($fields as $field => $label) { + if (is_numeric($field)) { + $field = $label; + unset($label); + } + $result[] = $this->field($field, compact('label') + $options); + } + return join("\n", $result); + } + + /** * Generates an HTML `<input type="submit" />` object. * * @param string $title The title of the submit button. @@ -555,7 +577,7 @@ class Form extends \lithium\template\Helper { } } if ($scope['hidden']) { - $out = $this->hidden($name, array('value' => '')); + $out = $this->hidden($name, array('value' => '', 'id' => false)); } $options['value'] = $scope['value']; return $out . $this->_render(__METHOD__, $template, compact('name', 'options')); @@ -589,7 +611,7 @@ class Form extends \lithium\template\Helper { /** * Generates an HTML `<label></label>` object. * - * @param string $name The name of the field that the label is for. + * @param string $name The DOM ID of the field that the label is for. * @param string $title The content inside the `<label></label>` object. * @param array $options Besides HTML attributes, this parameter allows one additional flag: * - `'escape'` _boolean_: Defaults to `true`. Indicates whether the title of the @@ -623,7 +645,7 @@ class Form extends \lithium\template\Helper { */ public function error($name, $key = null, array $options = array()) { $defaults = array('class' => 'error'); - list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options); + list(, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options); $options += $defaults; $result = ''; @@ -661,14 +683,7 @@ class Form extends \lithium\template\Helper { protected function _defaults($method, $name, $options) { $methodConfig = isset($this->_config[$method]) ? $this->_config[$method] : array(); $options += $methodConfig + $this->_config['base']; - - foreach ($this->_config['attributes'] as $key => $generator) { - if (!isset($options[$key]) && $generator) { - if (($attr = $generator($method, $name, $options)) !== null) { - $options[$key] = $attr; - } - } - } + $options = $this->_generators($method, $name, $options); $hasValue = ( (!isset($options['value']) || $options['value'] === null) && @@ -691,6 +706,36 @@ class Form extends \lithium\template\Helper { $template = isset($this->_templateMap[$tplKey]) ? $this->_templateMap[$tplKey] : $tplKey; return array($name, $options, $template); } + + /** + * Iterates over the configured attribute generators, and modifies the settings for a tag. + * + * @param string $method The name of the helper method which was called, i.e. `'text'`, + * `'select'`, etc. + * @param string $name The name of the field whose attributes are being generated. Some helper + * methods, such as `create()` and `end()`, are not field-based, and therefore + * will have no name. + * @param array $options The options and HTML attributes that will be used to generate the + * helper output. + * @return array Returns the value of the `$options` array, modified by the attribute generators + * added in the `'attributes'` key of the helper's configuration. Note that if a + * generator is present for a field whose value is `false`, that field will be removed + * from the array. + */ + protected function _generators($method, $name, $options) { + foreach ($this->_config['attributes'] as $key => $generator) { + if ($generator && !isset($options[$key])) { + if (($attr = $generator($method, $name, $options)) !== null) { + $options[$key] = $attr; + } + continue; + } + if ($generator && $options[$key] === false) { + unset($options[$key]); + } + } + return $options; + } } ?> \ No newline at end of file diff --git a/libraries/lithium/template/helper/Html.php b/libraries/lithium/template/helper/Html.php index 4a243fd..5350335 100644 --- a/libraries/lithium/template/helper/Html.php +++ b/libraries/lithium/template/helper/Html.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -25,7 +25,7 @@ class Html extends \lithium\template\Helper { 'block' => '<div{:options}>{:content}</div>', 'block-end' => '</div>', 'block-start' => '<div{:options}>', - 'charset' => '<meta http-equiv="Content-Type" content="{:type}; charset={:charset}" />', + 'charset' => '<meta charset="{:encoding}" />', 'image' => '<img src="{:path}"{:options} />', 'js-block' => '<script type="text/javascript"{:options}>{:content}</script>', 'js-end' => '</script>', @@ -83,15 +83,35 @@ class Html extends \lithium\template\Helper { ); /** - * Returns a charset meta-tag. + * Returns a charset meta-tag for declaring the encoding of the document. * - * @param string $charset The character set to be used in the meta tag. Example: `"utf-8"`. - * @return string A meta tag containing the specified character set. + * The terms character set (here: charset) and character encoding (here: + * encoding) were historically synonymous. The terms now have related but + * distinct meanings. Whenever possible Lithium tries to use precise + * terminology. Since HTML uses the term `charset` we expose this method + * under the exact same name. This caters to the expectation towards a HTML + * helper. However the rest of the framework will use the term `encoding` + * when talking about character encoding. + * + * It is suggested that uppercase letters should be used when specifying + * the encoding. HTML specs don't require it to be uppercase and sites in + * the wild most often use the lowercase variant. On the other hand must + * XML parsers (those may not be relevant in this context anyway) not + * support lowercase encodings. This and the fact that IANA lists only + * encodings with uppercase characters led to the above suggestion. + * + * @see lithium\net\http\Response::$encoding + * @link http://www.iana.org/assignments/character-sets + * @param string $encoding The character encoding to be used in the meta tag. + * Defaults to the encoding of the `Response` object attached to the + * current context. The default encoding of that object is `UTF-8`. + * The string given here is not manipulated in any way, so that + * values are rendered literally. Also see above note about casing. + * @return string A meta tag containing the specified encoding (literally). */ - public function charset($charset = null) { - $options = array('type' => 'text/html'); - $options['charset'] = $charset ?: 'utf-8'; - return $this->_render(__METHOD__, 'charset', $options); + public function charset($encoding = null) { + $encoding = $encoding ?: $this->_context->response()->encoding; + return $this->_render(__METHOD__, 'charset', compact('encoding')); } /** @@ -194,6 +214,7 @@ class Html extends \lithium\template\Helper { $this->_context->styles($style); } } + /** * Creates a tag for the ```<head>``` section of your document. * diff --git a/libraries/lithium/template/view/Compiler.php b/libraries/lithium/template/view/Compiler.php index e04dab4..add24a5 100644 --- a/libraries/lithium/template/view/Compiler.php +++ b/libraries/lithium/template/view/Compiler.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\template\view; +use lithium\core\Libraries; use lithium\template\TemplateException; /** @@ -35,6 +36,7 @@ class Compiler extends \lithium\core\StaticObject { */ protected static $_processors = array( '/\<\?=\s*\$this->(.+?)\s*;?\s*\?>/msx' => '<?php echo $this->$1; ?>', + '/\<\?=\s*(\$h\(.+?)\s*;?\s*\?>/msx' => '<?php echo $1; ?>', '/\<\?=\s*(.+?)\s*;?\s*\?>/msx' => '<?php echo $h($1); ?>' ); @@ -50,7 +52,7 @@ class Compiler extends \lithium\core\StaticObject { * @return string The compiled template. */ public static function template($file, array $options = array()) { - $cachePath = LITHIUM_APP_PATH . '/resources/tmp/cache/templates'; + $cachePath = Libraries::get(true, 'resources') . '/tmp/cache/templates'; $defaults = array('path' => $cachePath, 'fallback' => true); $options += $defaults; @@ -76,7 +78,7 @@ class Compiler extends \lithium\core\StaticObject { if ($options['fallback']) { return $file; } - throw new TemplateException("Could not write compiled template {$template} to cache"); + throw new TemplateException("Could not write compiled template `{$template}` to cache."); } /** diff --git a/libraries/lithium/template/view/Renderer.php b/libraries/lithium/template/view/Renderer.php index 31a732e..0eb6ac7 100644 --- a/libraries/lithium/template/view/Renderer.php +++ b/libraries/lithium/template/view/Renderer.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,6 +10,7 @@ namespace lithium\template\view; use RuntimeException; use lithium\core\Libraries; +use lithium\core\ClassNotFoundException; /** * The `Renderer` abstract class serves as a base for all concrete `Renderer` adapters. @@ -33,7 +34,7 @@ abstract class Renderer extends \lithium\core\Object { * @var array */ protected $_autoConfig = array( - 'request', 'context', 'strings', 'handlers', 'view', 'classes' => 'merge' + 'request', 'response', 'context', 'strings', 'handlers', 'view', 'classes' => 'merge' ); /** @@ -92,6 +93,13 @@ abstract class Renderer extends \lithium\core\Object { protected $_request = null; /** + * The `Response` object instance, if applicable. + * + * @var object The response object. + */ + protected $_response = null; + + /** * Automatically matches up template strings by name to output handlers. A handler can either * be a string, which represents a method name of the helper, or it can be a closure or callable * object. A handler takes 3 parameters: the value to be filtered, the name of the helper @@ -144,6 +152,7 @@ abstract class Renderer extends \lithium\core\Object { * - `handlers`: An array of output handlers for string template inputs. * - `request`: The `Request` object associated with this renderer and passed to the * defined handlers. + * - `response`: The `Response` object associated with this renderer. * - `context`: An array of the current rendering context data, including `content`, * `title`, `scripts`, `head` and `styles`. * @@ -156,6 +165,7 @@ abstract class Renderer extends \lithium\core\Object { 'strings' => array(), 'handlers' => array(), 'request' => null, + 'response' => null, 'context' => array( 'content' => '', 'title' => '', 'scripts' => array(), 'styles' => array(), 'head' => array() @@ -182,13 +192,16 @@ abstract class Renderer extends \lithium\core\Object { }, 'path' => function($path, $ref, array $options = array()) use (&$classes, &$request) { $defaults = array('base' => $request ? $request->env('base') : ''); - list($helper, $methodRef) = $ref; - list($class, $method) = explode('::', $methodRef); - $type = $helper->contentMap[$method]; + $type = 'generic'; + + if (is_array($ref) && $ref[0] && $ref[1]) { + list($helper, $methodRef) = $ref; + list($class, $method) = explode('::', $methodRef); + $type = $helper->contentMap[$method]; + } return $classes['media']::asset($path, $type, $options + $defaults); }, 'options' => '_attributes', - 'content' => 'escape', 'title' => 'escape', 'scripts' => function($scripts) use (&$context) { return "\n\t" . join("\n\t", $context['scripts']) . "\n"; @@ -216,6 +229,13 @@ abstract class Renderer extends \lithium\core\Object { return isset($this->_context[$property]); } + /** + * Returns a helper object or context value by name. + * + * @param string $property The name of the helper or context value to return. + * @return mixed + * @filter + */ public function __get($property) { $context = $this->_context; $helpers = $this->_helpers; @@ -277,11 +297,16 @@ abstract class Renderer extends \lithium\core\Object { * @param array $config * @return object */ - public function helper($name, $config = array()) { - if ($class = Libraries::locate('helper', ucfirst($name))) { - return $this->_helpers[$name] = new $class($config + array('context' => $this)); + public function helper($name, array $config = array()) { + if (isset($this->_helpers[$name])) { + return $this->_helpers[$name]; + } + try { + $config += array('context' => $this); + return $this->_helpers[$name] = Libraries::instance('helper', ucfirst($name), $config); + } catch (ClassNotFoundException $e) { + throw new RuntimeException("Helper `{$name}` not found."); } - throw new RuntimeException("Helper {$name} not found"); } /** @@ -312,7 +337,7 @@ abstract class Renderer extends \lithium\core\Object { * @return mixed A string or array, depending on whether `$property` is specified. */ public function context($property = null) { - if (!empty($property)) { + if ($property) { return isset($this->_context[$property]) ? $this->_context[$property] : null; } return $this->_context; @@ -370,7 +395,10 @@ abstract class Renderer extends \lithium\core\Object { if (!(isset($this->_handlers[$name]) && $handler = $this->_handlers[$name])) { return $value; } + switch (true) { + case is_string($handler) && !$helper: + $helper = $this->helper('html'); case is_string($handler) && is_object($helper): return $helper->invokeMethod($handler, array($value, $method, $options)); case is_array($handler) && is_object($handler[0]): @@ -394,6 +422,16 @@ abstract class Renderer extends \lithium\core\Object { } /** + * Returns the `Response` object associated with this rendering context. + * + * @return object Returns an instance of `lithium\action\Response`, which provides the i.e. + * the encoding for the document being the result of templates rendered by this context. + */ + public function response() { + return $this->_response; + } + + /** * Retuns the `View` object that controls this rendering context's instance. This can be used, * for example, to render view elements, i.e. `<?=$this->view()->render('element' $name); ?>`. * @@ -426,6 +464,28 @@ abstract class Renderer extends \lithium\core\Object { $this->_data = $data + $this->_data; $this->_vars = $data + $this->_vars; } + + /** + * Shortcut method used to render elements and other nested templates from inside the templating + * layer. + * + * @see lithium\template\View::$_processes + * @see lithium\template\View::render() + * @param string $type The type of template to render, usually either `'element'` or + * `'template'`. Indicates the process used to render the content. See + * `lithium\template\View::$_processes` for more info. + * @param string $template The template file name. For example, if `'header'` is passed, and + * `$type` is set to `'element'`, then the template rendered will be + * `views/elements/header.html.php` (assuming the default configuration). + * @param array $data An array of any other local variables that should be injected into the + * template. By default, only the values used to render the current template will + * be sent. If `$data` is non-empty, both sets of variables will be merged. + * @param array $options Any options accepted by `template\View::render()`. + * @return string Returns a the rendered template content as a string. + */ + protected function _render($type, $template, array $data = array(), array $options = array()) { + return $this->_view->render($type, $data + $this->_data, compact('template') + $options); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/template/view/adapter/File.php b/libraries/lithium/template/view/adapter/File.php index b7d0bf3..ee5fede 100644 --- a/libraries/lithium/template/view/adapter/File.php +++ b/libraries/lithium/template/view/adapter/File.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -13,8 +13,9 @@ use lithium\core\Libraries; use lithium\template\TemplateException; /** - * The File adapter implements both template loading and rendering, and uses the `view\Stream` class - * to auto-escape template output with short tags (i.e. <?=). + * The File adapter implements both template loading and rendering, and uses the + * `lithium\template\view\Stream` class or `lithium\template\view\Compiler` class to auto-escape + * template output with short tags (i.e. `<?=`). * * For more information about implementing your own template loaders or renderers, see the * `lithium\template\View` class. @@ -31,7 +32,8 @@ class File extends \lithium\template\view\Renderer implements \ArrayAccess { * @var array */ protected $_autoConfig = array( - 'classes' => 'merge', 'request', 'context', 'strings', 'handlers', 'view', 'compile' + 'classes' => 'merge', 'request', 'response', 'context', + 'strings', 'handlers', 'view', 'compile', 'paths' ); /** @@ -60,6 +62,8 @@ class File extends \lithium\template\view\Renderer implements \ArrayAccess { */ protected $_vars = array(); + protected $_paths = array(); + /** * `File`'s dependencies. These classes are used by the output handlers to generate URLs * for dynamic resources and static assets, as well as compiling the templates. @@ -74,7 +78,9 @@ class File extends \lithium\template\view\Renderer implements \ArrayAccess { ); public function __construct(array $config = array()) { - $defaults = array('classes' => array(), 'compile' => true, 'extract' => true); + $defaults = array( + 'classes' => array(), 'compile' => true, 'extract' => true, 'paths' => array() + ); parent::__construct($config + $defaults); } @@ -110,18 +116,13 @@ class File extends \lithium\template\view\Renderer implements \ArrayAccess { * Returns a template file name * * @param string $type - * @param array $options + * @param array $params * @return string */ - public function template($type, $options) { - if (!isset($this->_config['paths'][$type])) { - return null; - } - $options = array_filter($options, function($item) { return is_string($item); }); - - $library = Libraries::get(isset($options['library']) ? $options['library'] : true); - $options['library'] = $library['path']; - $path = $this->_paths((array) $this->_config['paths'][$type], $options); + public function template($type, array $params) { + $library = Libraries::get(isset($params['library']) ? $params['library'] : true); + $params['library'] = $library['path']; + $path = $this->_paths($type, $params); if ($this->_compile) { $compiler = $this->_classes['compiler']; @@ -153,21 +154,26 @@ class File extends \lithium\template\view\Renderer implements \ArrayAccess { } /** - * Searches a series of path templates for a matching template file, and returns the file name. + * Searches one or more path templates for a matching template file, and returns the file name. * - * @param array $paths The array of path templates to search. - * @param array $options The set of options keys to be interpolated into the path templates + * @param string $type + * @param array $params The set of options keys to be interpolated into the path templates * when searching for the correct file to load. * @return string Returns the first template file found. Throws an exception if no templates * are available. */ - protected function _paths($paths, $options) { - foreach ($paths as $path) { - if (file_exists($path = String::insert($path, $options))) { - return $path; + protected function _paths($type, array $params) { + if (!isset($this->_paths[$type])) { + throw new TemplateException("Invalid template type '{$type}'."); + } + + foreach ((array) $this->_paths[$type] as $path) { + if (!file_exists($path = String::insert($path, $params))) { + continue; } + return $path; } - throw new TemplateException("Template not found at {$path}"); + throw new TemplateException("Template not found at path `{$path}`."); } } diff --git a/libraries/lithium/template/view/adapter/Simple.php b/libraries/lithium/template/view/adapter/Simple.php index ad9dbed..703f790 100644 --- a/libraries/lithium/template/view/adapter/Simple.php +++ b/libraries/lithium/template/view/adapter/Simple.php @@ -2,14 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\template\view\adapter; -use \Closure; -use \Exception; +use Closure; +use Exception; use lithium\util\Set; use lithium\util\String; @@ -50,7 +50,10 @@ class Simple extends \lithium\template\view\Renderer { * @return string */ public function template($type, $options) { - return isset($options[$type]) ? $options[$type] : ''; + if (isset($options[$type])) { + return $options[$type]; + } + return isset($options['template']) ? $options['template'] : ''; } protected function _toString($data) { diff --git a/libraries/lithium/test/Controller.php b/libraries/lithium/test/Controller.php index e9be2ea..4633016 100644 --- a/libraries/lithium/test/Controller.php +++ b/libraries/lithium/test/Controller.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -26,26 +26,27 @@ class Controller extends \lithium\core\Object { * @param array $dispatchParams Array of params after being parsed by router. * @param array $options Some basic options for this controller. * @return string + * @filter */ public function __invoke($request, $dispatchParams, array $options = array()) { $dispatchParamsDefaults = array('args' => array()); $dispatchParams += $dispatchParamsDefaults; - $defaults = array('reporter' => 'html', 'format' => 'html'); + $defaults = array('reporter' => 'html', 'format' => 'html', 'timeout' => 0); $options += (array) $request->query + $defaults; $params = compact('request', 'dispatchParams', 'options'); - set_time_limit(0); return $this->_filter(__METHOD__, $params, function($self, $params) { $request = $params['request']; $options = $params['options']; $params = $params['dispatchParams']; + set_time_limit((integer) $options['timeout']); $group = join('\\', (array) $params['args']); if ($group === "all") { $group = Group::all(); $options['title'] = 'All Tests'; } - $report = Dispatcher::run($group , $options); + $report = Dispatcher::run($group, $options); $filters = Libraries::locate('test.filter'); $menu = Libraries::locate('tests', null, array( 'filter' => '/cases|integration|functional/', diff --git a/libraries/lithium/test/Dispatcher.php b/libraries/lithium/test/Dispatcher.php index 7f10391..25ee708 100644 --- a/libraries/lithium/test/Dispatcher.php +++ b/libraries/lithium/test/Dispatcher.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -44,6 +44,7 @@ class Dispatcher extends \lithium\core\StaticObject { * @return array A compact array of the title, an array of the results, as well * as an additional array of the results after the $options['filters'] * have been applied. + * @filter */ public static function run($group = null, array $options = array()) { $defaults = array( @@ -59,7 +60,7 @@ class Dispatcher extends \lithium\core\StaticObject { $group = static::_group($items); $report = static::_report($group, $options); - return static::_filter(__METHOD__, compact('report'), function($self, $params, $chain) { + return static::_filter(__FUNCTION__, compact('report'), function($self, $params, $chain) { $environment = Environment::get(); Environment::set('test'); diff --git a/libraries/lithium/test/Filter.php b/libraries/lithium/test/Filter.php index b4d3f60..9ef30f9 100644 --- a/libraries/lithium/test/Filter.php +++ b/libraries/lithium/test/Filter.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/test/Group.php b/libraries/lithium/test/Group.php index ae22ce0..0cfaca6 100644 --- a/libraries/lithium/test/Group.php +++ b/libraries/lithium/test/Group.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -57,14 +57,11 @@ class Group extends \lithium\util\Collection { */ public static function all(array $options = array()) { $defaults = array( - 'library' => true, 'filter' => '/cases/', 'exclude' => '/mock/', 'recursive' => true, ); - $options += $defaults; - $classes = Libraries::locate('tests', null, $options); - return $classes; + return Libraries::locate('tests', null, $options + $defaults); } /** @@ -82,7 +79,7 @@ class Group extends \lithium\util\Collection { case is_object($test) && $test instanceof Unit: return array(get_class($test)); case is_string($test) && !file_exists(Libraries::path($test)): - return $self->invokeMethod('_unitClass', array($test)); + return $self->invokeMethod('_resolve', array($test)); default: return (array) $test; } @@ -108,7 +105,7 @@ class Group extends \lithium\util\Collection { foreach ($this->_data as $test) { if (!class_exists($test)) { - throw new Exception("Test case '{$test}' not found."); + throw new Exception("Test case `{$test}` not found."); } $tests[] = new $test; } @@ -116,18 +113,18 @@ class Group extends \lithium\util\Collection { } /** - * Gets a unit test class (or classes) from a class or namespace path string. + * Resolves a unit test class (or classes) from a class or namespace path string. * * @param string $test The path string in which to find the test case(s). This may be a - * namespace, a Lithium package name, or a fully-namespaced class reference. + * library, a namespace, or a fully-namespaced class reference. * @return array Returns an array containing one or more fully-namespaced class references to - * unit tests. + * unit tests. */ - protected function _unitClass($test) { - if ($test[0] != '\\' && strpos($test, 'lithium\\') === false) { - if (file_exists(Libraries::path($test = "lithium\\tests\cases\\{$test}"))) { - return array($test); - } + protected function _resolve($test) { + if (strpos($test, '\\') === false && Libraries::get($test)) { + return (array) Libraries::find($test, array( + 'recursive' => true, 'filter' => '/cases|integration|functional/', + )); } if (preg_match("/Test/", $test)) { return array($test); diff --git a/libraries/lithium/test/Integration.php b/libraries/lithium/test/Integration.php index a34a198..707f012 100644 --- a/libraries/lithium/test/Integration.php +++ b/libraries/lithium/test/Integration.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/test/Report.php b/libraries/lithium/test/Report.php index d08dbe3..f0b8741 100644 --- a/libraries/lithium/test/Report.php +++ b/libraries/lithium/test/Report.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -214,6 +214,7 @@ class Report extends \lithium\core\Object { * @param string $data array from `_data()` method * @param array $options Array of options (e.g. rendering type) * @return string + * @filter */ public function render($template, $data = array()) { $config = $this->_config; @@ -241,7 +242,7 @@ class Report extends \lithium\core\Object { $results = array(); foreach ($filters as $filter => $options) { if (!$class = Libraries::locate('test.filter', $filter)) { - throw new ClassNotFoundException("{$class} is not a valid test filter."); + throw new ClassNotFoundException("`{$class}` is not a valid test filter."); } $options['name'] = strtolower(join('', array_slice(explode("\\", $class), -1))); $results[$class] = $options + array('apply' => array(), 'analyze' => array()); diff --git a/libraries/lithium/test/Unit.php b/libraries/lithium/test/Unit.php index e1bef5d..988c039 100644 --- a/libraries/lithium/test/Unit.php +++ b/libraries/lithium/test/Unit.php @@ -2,20 +2,20 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\test; -use \Exception; +use Exception; use lithium\util\String; use lithium\core\Libraries; use lithium\util\Validator; use lithium\analysis\Debugger; use lithium\analysis\Inspector; -use \RecursiveDirectoryIterator; -use \RecursiveIteratorIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; /** * This is the base class for all test cases. Test are performed using an assertion method. If the @@ -107,8 +107,7 @@ class Unit extends \lithium\core\Object { * * @return void */ - public function skip() { - } + public function skip() {} /** * Skips test(s) if the condition is met. @@ -121,12 +120,10 @@ class Unit extends \lithium\core\Object { * @param string $message Message to pass if the condition is met. * @return mixed */ - public function skipIf($condition, $message = 'Skipped test {:class}::{:function}()') { - if (!$condition) { - return; + public function skipIf($condition, $message = false) { + if ($condition) { + throw new Exception(is_string($message) ? $message : null); } - $trace = Debugger::trace(array('start' => 2, 'depth' => 3, 'format' => 'array')); - throw new Exception(String::insert($message, $trace)); } /** @@ -212,7 +209,9 @@ class Unit extends \lithium\core\Object { $params['message'] = $this->_message($params); $message = String::insert($message, $params); } - $trace = Debugger::trace(array('start' => 1, 'format' => 'array')); + $trace = Debugger::trace(array( + 'start' => 1, 'depth' => 4, 'format' => 'array', 'closures' => !$expression + )); $methods = $this->methods(); $i = 1; @@ -230,7 +229,7 @@ class Unit extends \lithium\core\Object { 'method' => $trace[$i]['function'], 'assertion' => $trace[$i - 1]['function'], ); - $this->_result(($expression ? 'pass' : 'fail'), $result); + $this->_result($expression ? 'pass' : 'fail', $result); return $expression; } @@ -526,6 +525,48 @@ class Unit extends \lithium\core\Object { * @param array $headers When empty, value of `headers_list()` is used. */ public function assertCookie($expected, $headers = null) { + $matched = $this->_cookieMatch($expected, $headers); + if (!$matched['match']) { + $message = sprintf('%s - Cookie not found in headers.', $matched['pattern']); + $this->assert(false, $message, compact('expected', 'result')); + return false; + } + return $this->assert(true, '%s'); + } + + /** + * Assert Cookie data is *not* set in headers. + * + * The value passed to `exepected` is an array of the cookie data, with at least the key and + * value expected, but can support any of the following keys: + * - `key`: the expected key + * - `value`: the expected value + * - `path`: optionally specifiy a path + * - `name`: optionally specify the cookie name + * - `expires`: optionally assert a specific expire time + * + * @param array $expected + * @param array $headers When empty, value of `headers_list()` is used. + */ + public function assertNoCookie($expected, $headers = null) { + $matched = $this->_cookieMatch($expected, $headers); + if ($matched['match']) { + $message = sprintf('%s - Cookie not found in headers.', $matched['pattern']); + $this->assert(false, $message, compact('expected', 'result')); + return false; + } + return $this->assert(true, '%s'); + } + + /** + * Match an `$expected` cookie with the given headers. If no headers are provided, then + * the value of `headers_list()` will be used. + * + * @param array $expected + * @param array $headers When empty, value of `headers_list()` will be used. + * @return boolean True if cookie is found, false otherwise. + */ + protected function _cookieMatch($expected, $headers) { $defaults = array('path' => '/', 'name' => '[\w.-]+'); $expected += $defaults; @@ -553,15 +594,7 @@ class Unit extends \lithium\core\Object { continue; } } - - if (!$match) { - $this->assert(false, - sprintf('{:message} - Cookie %s not found in headers.', $pattern), - compact('expected', 'result') - ); - return false; - } - return $this->assert(true, '%s'); + return compact('match', 'pattern'); } /** @@ -606,6 +639,7 @@ class Unit extends \lithium\core\Object { * @param string $method The name of the test method to run. * @param array $options * @return void | false + * @filter */ protected function _runTestMethod($method, $options) { try { @@ -726,18 +760,23 @@ class Unit extends \lithium\core\Object { * @return array Data with the keys `trace'`, `'expected'` and `'result'`. */ protected function _compare($type, $expected, $result = null, $trace = null) { - $types = array('expected' => gettype($expected), 'result' => gettype($result)); + $compareTypes = function($expected, $result, $trace) { + $types = array('expected' => gettype($expected), 'result' => gettype($result)); - if ($types['expected'] !== $types['result']) { - return array('trace' => $trace, - 'expected' => trim("({$types['expected']}) " . print_r($expected, true)), - 'result' => trim("({$types['result']}) " . print_r($result, true)) - ); + if ($types['expected'] !== $types['result']) { + $expected = trim("({$types['expected']}) " . print_r($expected, true)); + $result = trim("({$types['result']}) " . print_r($result, true)); + return compact('trace', 'expected', 'result'); + } + }; + if ($types = $compareTypes($expected, $result, $trace)) { + return $types; } $data = array(); if (!is_scalar($expected)) { foreach ($expected as $key => $value) { + $newTrace = "{$trace}[{$key}]"; $isObject = false; if (is_object($expected)) { @@ -745,8 +784,13 @@ class Unit extends \lithium\core\Object { $expected = (array) $expected; $result = (array) $result; } - $check = array_key_exists($key, $result) ? $result[$key] : array(); - $newTrace = "{$trace}[{$key}]"; + if (!array_key_exists($key, $result)) { + $trace = (!$key) ? null : $newTrace; + $expected = (!$key) ? $expected : $value; + $result = ($key) ? null : $result; + return compact('trace', 'expected', 'result'); + } + $check = $result[$key]; if ($isObject) { $newTrace = ($trace) ? "{$trace}->{$key}" : $key; @@ -755,6 +799,9 @@ class Unit extends \lithium\core\Object { } if ($type === 'identical') { if ($value === $check) { + if ($types = $compareTypes($value, $check, $trace)) { + return $types; + } continue; } if ($check === array()) { @@ -769,6 +816,9 @@ class Unit extends \lithium\core\Object { } } else { if ($value == $check) { + if ($types = $compareTypes($value, $check, $trace)) { + return $types; + } continue; } if (!is_array($value)) { @@ -782,16 +832,25 @@ class Unit extends \lithium\core\Object { $data[] = $compare; } } - return $data; + if (!empty($data)) { + return $data; + } } - - if ($type === 'identical') { - if ($expected === $result) { - return true; + if (!is_scalar($result)) { + $data = $this->_compare($type, $result, $expected); + + if (!empty($data)) { + return array( + 'trace' => $data['trace'], + 'expected' => $data['result'], + 'result' => $data['expected'] + ); } - return compact('trace', 'expected', 'result'); } - if ($expected == $result) { + if ((($type === 'identical') ? $expected === $result : $expected == $result)) { + if ($types = $compareTypes($expected, $result, $trace)) { + return $types; + } return true; } return compact('trace', 'expected', 'result'); @@ -873,13 +932,15 @@ class Unit extends \lithium\core\Object { * @return void */ protected function _cleanUp($path = null) { - $path = $path ?: LITHIUM_APP_PATH . '/resources/tmp/tests'; - $path = $path[0] !== '/' ? LITHIUM_APP_PATH . '/resources/tmp/' . $path : $path; + $path = $path ?: Libraries::get(true, 'resources') . '/tmp/tests'; + $path = $path[0] !== '/' ? Libraries::get(true, 'resources') . '/tmp/' . $path : $path; + if (!is_dir($path)) { return; } $dirs = new RecursiveDirectoryIterator($path); $iterator = new RecursiveIteratorIterator($dirs, RecursiveIteratorIterator::CHILD_FIRST); + foreach ($iterator as $item) { if ($item->getPathname() === "{$path}/empty" || $iterator->isDot()) { continue; diff --git a/libraries/lithium/test/filter/Affected.php b/libraries/lithium/test/filter/Affected.php index b368476..b9ce1fa 100644 --- a/libraries/lithium/test/filter/Affected.php +++ b/libraries/lithium/test/filter/Affected.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -20,7 +20,6 @@ use lithium\analysis\Inspector; * 1. Looking at the subject of a test case. * 2. Searching the class tree for any classes that directly depend on that subject. * 3. Assigning test cases to those classes. - * */ class Affected extends \lithium\test\Filter { diff --git a/libraries/lithium/test/filter/Complexity.php b/libraries/lithium/test/filter/Complexity.php index f81cd11..8fb1360 100644 --- a/libraries/lithium/test/filter/Complexity.php +++ b/libraries/lithium/test/filter/Complexity.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/test/filter/Coverage.php b/libraries/lithium/test/filter/Coverage.php index 4122966..29fdc49 100644 --- a/libraries/lithium/test/filter/Coverage.php +++ b/libraries/lithium/test/filter/Coverage.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/test/filter/Profiler.php b/libraries/lithium/test/filter/Profiler.php index fe5500b..aba9247 100644 --- a/libraries/lithium/test/filter/Profiler.php +++ b/libraries/lithium/test/filter/Profiler.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/test/templates/console/affected.json.php b/libraries/lithium/test/templates/console/affected.json.php new file mode 100644 index 0000000..89e38ba --- /dev/null +++ b/libraries/lithium/test/templates/console/affected.json.php @@ -0,0 +1 @@ +<?php echo json_encode($data) ?> \ No newline at end of file diff --git a/libraries/lithium/test/templates/console/affected.txt.php b/libraries/lithium/test/templates/console/affected.txt.php index be85998..d2ed821 100644 --- a/libraries/lithium/test/templates/console/affected.txt.php +++ b/libraries/lithium/test/templates/console/affected.txt.php @@ -1,4 +1,4 @@ -{:heading2}Affected Tests{:end} +{:heading}Affected Tests{:end} <?php foreach ($data as $class => $test) { if ($test) { diff --git a/libraries/lithium/test/templates/console/complexity.json.php b/libraries/lithium/test/templates/console/complexity.json.php new file mode 100644 index 0000000..829936a --- /dev/null +++ b/libraries/lithium/test/templates/console/complexity.json.php @@ -0,0 +1,18 @@ +<?php + +$worstOffender = null; +$averages = array(); + +foreach (array_slice($data['max'], 0, 10) as $method => $count) { + if ($count <= 7) { + continue; + } + $worstOffender = compact('method', 'count'); +} +foreach (array_slice($data['class'], 0, 10) as $class => $count) { + $averages[$class] = $count; +} + +echo json_encode(compact('worstOffender', 'averages')); + +?> \ No newline at end of file diff --git a/libraries/lithium/test/templates/console/complexity.txt.php b/libraries/lithium/test/templates/console/complexity.txt.php index b980796..b2d19ed 100644 --- a/libraries/lithium/test/templates/console/complexity.txt.php +++ b/libraries/lithium/test/templates/console/complexity.txt.php @@ -1,4 +1,4 @@ -{:heading2}Cyclomatic Complexity{:end} +{:heading}Cyclomatic Complexity{:end} <?php foreach (array_slice($data['max'], 0, 10) as $method => $count) { if ($count <= 7) { @@ -7,7 +7,7 @@ foreach (array_slice($data['max'], 0, 10) as $method => $count) { echo "Worst Offender\n\t{$method} - {$count}\n"; } ?> -{:heading3}Class Averages{:end} +{:heading}Class Averages{:end} <?php foreach (array_slice($data['class'], 0, 10) as $class => $count) { echo "\t{$class} - "; diff --git a/libraries/lithium/test/templates/console/coverage.json.php b/libraries/lithium/test/templates/console/coverage.json.php new file mode 100644 index 0000000..923aca6 --- /dev/null +++ b/libraries/lithium/test/templates/console/coverage.json.php @@ -0,0 +1 @@ +<?php echo json_encode($data); ?> \ No newline at end of file diff --git a/libraries/lithium/test/templates/console/coverage.txt.php b/libraries/lithium/test/templates/console/coverage.txt.php index 069c207..23a9d5b 100644 --- a/libraries/lithium/test/templates/console/coverage.txt.php +++ b/libraries/lithium/test/templates/console/coverage.txt.php @@ -1,5 +1,12 @@ -{:heading2}Code Coverage{:end} +{:heading}Code Coverage{:end} <?php + + $colorMap = array( + 'ignored' => 'white', + 'covered' => 'success', + 'uncovered' => 'error', + ); + foreach ($data as $class => $coverage) { echo ($coverage['percentage'] >= 85 ? "{:success}" : "{:error}"); echo "{$class}{:end}: "; @@ -7,5 +14,21 @@ echo " lines covered ("; echo ($coverage['percentage'] >= 85 ? "{:success}" : "{:error}"); echo "{$coverage['percentage']}%{:end})\n"; + + if ($coverage['percentage'] == 100) { + continue; + } + echo "\n{:heading3}Coverage analysis{:end}\n"; + + foreach ($coverage['output'] as $file => $lines) { + echo "\n{$file}:\n"; + + foreach ($lines as $num => $line) { + $color = $colorMap[$line['class']]; + echo "{:{$color}}{$num} {$line['data']}{:end}\n"; + } + } + echo "\n"; } + ?> \ No newline at end of file diff --git a/libraries/lithium/test/templates/console/profiler.json.php b/libraries/lithium/test/templates/console/profiler.json.php new file mode 100644 index 0000000..923aca6 --- /dev/null +++ b/libraries/lithium/test/templates/console/profiler.json.php @@ -0,0 +1 @@ +<?php echo json_encode($data); ?> \ No newline at end of file diff --git a/libraries/lithium/test/templates/console/profiler.txt.php b/libraries/lithium/test/templates/console/profiler.txt.php index 51678d8..252a098 100644 --- a/libraries/lithium/test/templates/console/profiler.txt.php +++ b/libraries/lithium/test/templates/console/profiler.txt.php @@ -1,4 +1,4 @@ -{:heading2}Benchmarks{:end} +{:heading}Benchmarks{:end} <?php foreach ($data['totals'] as $title => $result) { echo "{$title}: {$result['formatter']($result['value'])}\n"; diff --git a/libraries/lithium/test/templates/console/stats.json.php b/libraries/lithium/test/templates/console/stats.json.php new file mode 100644 index 0000000..ea68004 --- /dev/null +++ b/libraries/lithium/test/templates/console/stats.json.php @@ -0,0 +1 @@ +<?php echo json_encode(compact('count', 'stats')); ?> \ No newline at end of file diff --git a/libraries/lithium/test/templates/html/complexity.html.php b/libraries/lithium/test/templates/html/complexity.html.php index d9cd6ad..766ed7e 100644 --- a/libraries/lithium/test/templates/html/complexity.html.php +++ b/libraries/lithium/test/templates/html/complexity.html.php @@ -7,7 +7,7 @@ } ?> <tr> - <td class="metric-name">Worst Offender</th> + <td class="metric-name">Worst Offender</td> <td class="metric"><?php echo $method . ' - ' . $count ?></td> </tr> <?php endforeach ?> @@ -16,7 +16,7 @@ </tr> <?php foreach (array_slice($data['class'], 0, 10) as $class => $count): ?> <tr> - <td class="metric-name"><?php echo $class ?></th> + <td class="metric-name"><?php echo $class ?></td> <td class="metric"><?php echo round($count, 2) ?></td> </tr> <?php endforeach ?> diff --git a/libraries/lithium/test/templates/html/coverage.html.php b/libraries/lithium/test/templates/html/coverage.html.php index 1b9bb2a..149b552 100644 --- a/libraries/lithium/test/templates/html/coverage.html.php +++ b/libraries/lithium/test/templates/html/coverage.html.php @@ -49,29 +49,29 @@ $summary = array( <h4>Summary</h4> <table class="metrics"><tbody> <tr> - <td class="metric-name">Classes Covered</th> + <td class="metric-name">Classes Covered</td> <td class="metric"><?php echo $summary['classes'] ?></td> </tr> <tr> - <td class="metric-name">Executable Lines</th> + <td class="metric-name">Executable Lines</td> <td class="metric"><?php echo $summary['executable'] ?></td> </tr> <tr> - <td class="metric-name">Lines Covered</th> + <td class="metric-name">Lines Covered</td> <td class="metric"><?php echo $summary['covered'] ?></td> </tr> <tr> - <td class="metric-name">Lines Uncovered</th> + <td class="metric-name">Lines Uncovered</td> <td class="metric"><?php echo $summary['uncovered'] ?></td> </tr> <tr> - <td class="metric-name">Total Coverage</th> + <td class="metric-name">Total Coverage</td> <td class="metric"> <?php echo round(($summary['covered'] / $summary['executable']) * 100, 2) ?>% </td> </tr> <tr> - <td class="metric-name">Average Per Class</th> + <td class="metric-name">Average Per Class</td> <td class="metric"> <?php echo round($summary['percentage'] / $summary['classes'], 2) ?>% </td> diff --git a/libraries/lithium/test/templates/html/fail.html.php b/libraries/lithium/test/templates/html/fail.html.php index 182810b..bbb8336 100644 --- a/libraries/lithium/test/templates/html/fail.html.php +++ b/libraries/lithium/test/templates/html/fail.html.php @@ -2,5 +2,7 @@ Assertion '<?php echo $error['assertion'] ?>' failed in <?php echo $error['class'] ?>::<?php echo $error['method']?>() on line <?php echo $error['line'] ?>: - <span class="content"><?php echo $error['message'] ?></span> -</div> + <span class="content"><?php echo htmlspecialchars( + $error['message'], ENT_QUOTES, 'UTF-8' + ) ?></span> +</div> \ No newline at end of file diff --git a/libraries/lithium/test/templates/html/layout.html.php b/libraries/lithium/test/templates/html/layout.html.php index b19d38d..9354302 100644 --- a/libraries/lithium/test/templates/html/layout.html.php +++ b/libraries/lithium/test/templates/html/layout.html.php @@ -1,11 +1,21 @@ <?php - use lithium\util\Inflector; - $base = $request->env('base'); +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +use lithium\util\Inflector; + ?> <!doctype html> <html> <head> + <!-- Title intentionally left blank, forcing user agents use the current URL as title. --> <title></title> + <?php $base = $request->env('base'); ?> + <meta charset="utf-8" /> <link rel="stylesheet" href="<?php echo $base; ?>/css/debug.css" /> <link href="<?php echo $base; ?>/favicon.ico" type="image/x-icon" rel="icon" /> <link href="<?php echo $base; ?>/favicon.ico" type="image/x-icon" rel="shortcut icon" /> diff --git a/libraries/lithium/tests/cases/action/ControllerTest.php b/libraries/lithium/tests/cases/action/ControllerTest.php index 11ec3fa..eb141da 100644 --- a/libraries/lithium/tests/cases/action/ControllerTest.php +++ b/libraries/lithium/tests/cases/action/ControllerTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -41,10 +41,8 @@ class ControllerTest extends \lithium\test\Unit { $result = $postsController->__invoke(null, array('action' => 'index', 'args' => array())); $this->assertTrue(is_a($result, 'lithium\action\Response')); - $this->assertEqual($result->body(), 'List of posts'); - - $headers = array('Content-type' => 'text/plain'); - $this->assertEqual($result->headers, $headers); + $this->assertEqual('List of posts', $result->body()); + $this->assertEqual(array('Content-type' => 'text/plain; charset=UTF-8'), $result->headers); $result2 = $postsController(null, array('action' => 'index', 'args' => array())); $this->assertEqual($result2, $result); @@ -56,7 +54,7 @@ class ControllerTest extends \lithium\test\Unit { $this->assertTrue(is_a($result, 'lithium\action\Response')); $this->assertEqual($result->body, ''); - $headers = array('Content-type' => 'text/html'); + $headers = array('Content-type' => 'text/html; charset=UTF-8'); $this->assertEqual($result->headers, $headers); $result = $postsController->access('_render'); @@ -68,7 +66,7 @@ class ControllerTest extends \lithium\test\Unit { $this->assertTrue(is_a($result, 'lithium\action\Response')); $this->assertEqual($result->body, "Array\n(\n [0] => This is a post\n)\n"); - $headers = array('status' => 200, 'Content-type' => 'text/plain'); + $headers = array('status' => 200, 'Content-type' => 'text/plain; charset=UTF-8'); $this->assertEqual($result->headers(), $headers); $result = $postsController->access('_render'); @@ -153,6 +151,29 @@ class ControllerTest extends \lithium\test\Unit { } /** + * Verifies that data array is passed on to controller's response. + * + * @return void + */ + public function testRenderWithDataArray() { + $request = new Request(); + $request->params['controller'] = 'lithium\tests\mocks\action\MockPostsController'; + + $controller = new MockPostsController(compact('request') + array('classes' => array( + 'media' => 'lithium\tests\mocks\action\MockMediaClass' + ))); + + $controller->set(array('set' => 'data')); + $controller->render(array('data' => array('render' => 'data'))); + + $expected = array( + 'set' => 'data', + 'render' => 'data' + ); + $this->assertEqual($expected, $controller->response->data); + } + + /** * Verifies that protected methods (i.e. prefixed with '_'), and methods declared in the * Controller base class cannot be accessed. * @@ -208,18 +229,16 @@ class ControllerTest extends \lithium\test\Unit { $expected = array( 'type' => 'json', 'data' => array('data' => 'test'), 'auto' => true, - 'layout' => 'default', 'template' => 'type', 'hasRendered' => true + 'layout' => 'default', 'template' => 'type', 'hasRendered' => true, 'negotiate' => false ); $result = $postsController->access('_render'); $this->assertEqual($expected, $result); - $expected = 'application/json'; $result = $postsController->response->headers('Content-type'); - $this->assertEqual($expected, $result); + $this->assertEqual('application/json; charset=UTF-8', $result); - $expected = array('data' => 'test'); $result = json_decode($postsController->response->body(), true); - $this->assertEqual($expected, $result); + $this->assertEqual(array('data' => 'test'), $result); } public function testResponseTypeBasedOnRequestParamsType() { @@ -238,14 +257,13 @@ class ControllerTest extends \lithium\test\Unit { $expected = array( 'type' => 'json', 'data' => array('data' => 'test'), 'auto' => true, - 'layout' => 'default', 'template' => 'type', 'hasRendered' => true + 'layout' => 'default', 'template' => 'type', 'hasRendered' => true, 'negotiate' => false ); $result = $postsController->access('_render'); $this->assertEqual($expected, $result); - $expected = 'application/json'; $result = $postsController->response->headers('Content-type'); - $this->assertEqual($expected, $result); + $this->assertEqual('application/json; charset=UTF-8', $result); $expected = array('data' => 'test'); $result = json_decode($postsController->response->body(), true); @@ -274,7 +292,8 @@ class ControllerTest extends \lithium\test\Unit { $postsController = new MockPostsController(array( 'request' => $request, - 'classes' => array('response' => 'lithium\tests\mocks\action\MockControllerResponse') + 'classes' => array('response' => 'lithium\tests\mocks\action\MockControllerResponse'), + 'render' => array('negotiate' => true) )); $this->assertFalse($postsController->stopped); @@ -282,18 +301,16 @@ class ControllerTest extends \lithium\test\Unit { $expected = array( 'type' => 'json', 'data' => array('data' => 'test'), 'auto' => true, - 'layout' => 'default', 'template' => 'type', 'hasRendered' => true + 'layout' => 'default', 'template' => 'type', 'hasRendered' => true, 'negotiate' => true ); $result = $postsController->access('_render'); $this->assertEqual($expected, $result); - $expected = 'application/json'; $result = $postsController->response->headers('Content-type'); - $this->assertEqual($expected, $result); + $this->assertEqual('application/json; charset=UTF-8', $result); - $expected = array('data' => 'test'); $result = json_decode($postsController->response->body(), true); - $this->assertEqual($expected, $result); + $this->assertEqual(array('data' => 'test'), $result); } /** @@ -315,7 +332,7 @@ class ControllerTest extends \lithium\test\Unit { public function testNonExistentFunction() { $postsController = new MockPostsController(); - $this->expectException("Action 'foo' not found."); + $this->expectException("Action `foo` not found."); $postsController(new Request(), array('action' => 'foo')); } } diff --git a/libraries/lithium/tests/cases/action/DispatcherTest.php b/libraries/lithium/tests/cases/action/DispatcherTest.php index e38ec0e..63f9f09 100644 --- a/libraries/lithium/tests/cases/action/DispatcherTest.php +++ b/libraries/lithium/tests/cases/action/DispatcherTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -46,65 +46,52 @@ class DispatcherTest extends \lithium\test\Unit { } public function testApplyRulesControllerCasing() { - $result = Dispatcher::applyRules(array('controller' => 'test', 'action' => 'test')); + $params = array('controller' => 'test', 'action' => 'test'); $expected = array('controller' => 'Test', 'action' => 'test'); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, Dispatcher::applyRules($params)); - $result = Dispatcher::applyRules(array('controller' => 'Test', 'action' => 'test')); - $expected = array('controller' => 'Test', 'action' => 'test'); - $this->assertEqual($expected, $result); + $params = array('controller' => 'Test', 'action' => 'test'); + $this->assertEqual($params, Dispatcher::applyRules($params)); - $result = Dispatcher::applyRules(array('controller' => 'test_one', 'action' => 'test')); + $params = array('controller' => 'test_one', 'action' => 'test'); $expected = array('controller' => 'TestOne', 'action' => 'test'); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, Dispatcher::applyRules($params)); } public function testApplyRulesWithNamespacedController() { - $result = Dispatcher::applyRules(array( - 'controller' => 'li3_test\\Test', 'action' => 'test' - )); - $expected = array( - 'controller' => 'li3_test\\Test', 'action' => 'test' - ); - $this->assertEqual($expected, $result); + $params = array('controller' => 'li3_test\\Test', 'action' => 'test'); + $expected = array('controller' => 'li3_test\\Test', 'action' => 'test'); + $this->assertEqual($expected, Dispatcher::applyRules($params)); } public function testApplyRulesDotNamespacing() { - $result = Dispatcher::applyRules(array( - 'controller' => 'li3_test.test', 'action' => 'test' - )); + $params = array('controller' => 'li3_test.test', 'action' => 'test'); $expected = array( - 'controller' => 'li3_test.Test', 'action' => 'test' + 'library' => 'li3_test', 'controller' => 'li3_test.Test', 'action' => 'test' ); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, Dispatcher::applyRules($params)); } public function testApplyRulesLibraryKeyNamespacing() { - $result = Dispatcher::applyRules(array( - 'library' => 'li3_test', 'controller' => 'test', 'action' => 'test' - )); + $params = array('library' => 'li3_test', 'controller' => 'test', 'action' => 'test'); $expected = array( 'library' => 'li3_test', 'controller' => 'li3_test.Test', 'action' => 'test' ); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, Dispatcher::applyRules($params)); } public function testApplyRulesNamespacingCollision() { - $result = Dispatcher::applyRules(array( - 'library' => 'li3_one', 'controller' => 'li3_two.test', 'action' => 'test' - )); + $params = array('library' => 'li3_one', 'controller' => 'li3_two.test', 'action' => 'test'); $expected = array( 'library' => 'li3_one', 'controller' => 'li3_two.Test', 'action' => 'test' ); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, Dispatcher::applyRules($params)); - $result = Dispatcher::applyRules(array( - 'library' => 'li3_one', 'controller' => 'li3_two\\Test', 'action' => 'test' - )); + $params = array('library' => 'li3_one', 'controller' => 'li3_two\Test', 'action' => 'test'); $expected = array( - 'library' => 'li3_one', 'controller' => 'li3_two\\Test', 'action' => 'test' + 'library' => 'li3_one', 'controller' => 'li3_two\Test', 'action' => 'test' ); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, Dispatcher::applyRules($params)); } public function testConfigManipulation() { @@ -127,14 +114,14 @@ class DispatcherTest extends \lithium\test\Unit { public function testControllerLookupFail() { Dispatcher::config(array('classes' => array('router' => __CLASS__))); - $this->expectException("/Controller 'SomeNonExistentController' not found/"); + $this->expectException("/Controller `SomeNonExistentController` not found/"); Dispatcher::run(new Request(array('url' => '/'))); } public function testPluginControllerLookupFail() { Dispatcher::config(array('classes' => array('router' => __CLASS__))); - $this->expectException("/Controller 'some_invalid_plugin.Controller' not found/"); + $this->expectException("/Controller `some_invalid_plugin.Controller` not found/"); Dispatcher::run(new Request(array('url' => '/plugin'))); } diff --git a/libraries/lithium/tests/cases/action/RequestTest.php b/libraries/lithium/tests/cases/action/RequestTest.php index df9c03e..51ac522 100644 --- a/libraries/lithium/tests/cases/action/RequestTest.php +++ b/libraries/lithium/tests/cases/action/RequestTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -99,83 +99,57 @@ class RequestTest extends \lithium\test\Unit { } public function testScriptName() { - $_SERVER['SCRIPT_NAME'] = 'index.php'; - $request = new Request(); - - $expected = 'index.php'; - $result = $request->env('SCRIPT_NAME'); - $this->assertEqual($expected, $result); + $request = new Request(array( + 'env' => array('HTTPS' => true, 'SCRIPT_NAME' => 'index.php') + )); + $this->assertEqual('index.php', $request->env('SCRIPT_NAME')); } public function testHttps() { - $_SERVER['HTTPS'] = true; - $request = new Request(); - - $expected = true; - $result = $request->env('HTTPS'); - $this->assertEqual($expected, $result); + $request = new Request(array('env' => array('HTTPS' => true))); + $this->assertTrue($request->env('HTTPS')); } public function testHttpsFromScriptUri() { - $_SERVER['SCRIPT_URI'] = 'https://lithium.com'; - unset($_SERVER['HTTPS']); - $request = new Request(); - - $expected = true; - $result = $request->env('HTTPS'); - $this->assertEqual($expected, $result); + $request = new Request(array('env' => array( + 'SCRIPT_URI' => 'https://lithium.com', + 'HTTPS' => null + ))); + $this->assertTrue($request->env('HTTPS')); } public function testRemoteAddr() { - $_SERVER['REMOTE_ADDR'] = '123.456.789.000'; - $request = new Request(); - - $expected = '123.456.789.000'; - $result = $request->env('REMOTE_ADDR'); - $this->assertEqual($expected, $result); + $request = new Request(array('env' => array('REMOTE_ADDR' => '123.456.789.000'))); + $this->assertEqual('123.456.789.000', $request->env('REMOTE_ADDR')); } public function testRemoteAddrFromHttpPcRemoteAddr() { $request = new MockIisRequest(); - - $expected = '123.456.789.000'; - $result = $request->env('REMOTE_ADDR'); - $this->assertEqual($expected, $result); + $this->assertEqual('123.456.789.000', $request->env('REMOTE_ADDR')); } public function testBase() { - $_SERVER['PHP_SELF'] = '/index.php'; - $request = new Request(); - - $expected = null; - $result = $request->env('base'); - $this->assertEqual($expected, $result); + $request = new Request(array('env' => array('PHP_SELF' => '/index.php'))); + $this->assertFalse($request->env('base')); } public function testBaseWithDirectory() { - $_SERVER['PHP_SELF'] = '/lithium.com/app/webroot/index.php'; - $request = new Request(); - - $expected = '/lithium.com'; - $result = $request->env('base'); - $this->assertEqual($expected, $result); + $request = new Request(array('env' => array( + 'PHP_SELF' => '/lithium.com/app/webroot/index.php' + ))); + $this->assertEqual('/lithium.com', $request->env('base')); } public function testBaseWithAppAndOtherDirectory() { - $_SERVER['PHP_SELF'] = '/lithium.com/app/other/webroot/index.php'; - $request = new Request(); - - $expected = '/lithium.com/app/other'; - $result = $request->env('base'); - $this->assertEqual($expected, $result); + $request = new Request(array('env' => array( + 'PHP_SELF' => '/lithium.com/app/other/webroot/index.php' + ))); + $this->assertEqual('/lithium.com/app/other', $request->env('base')); } public function testPhpSelfTranslatedForIIS() { $request = new MockIisRequest(); - - $expected = '/index.php'; - $result = $request->env('PHP_SELF'); - $this->assertEqual($expected, $result); + $this->assertEqual('/index.php', $request->env('PHP_SELF')); } public function testServerHttpBase() { @@ -190,9 +164,8 @@ class RequestTest extends \lithium\test\Unit { public function testCgiPlatform() { $request = new MockCgiRequest(); - $expected = true; $result = $request->env('CGI_MODE'); - $this->assertEqual($expected, $result); + $this->assertTrue($result); } public function testCgiScriptUrl() { @@ -215,9 +188,8 @@ class RequestTest extends \lithium\test\Unit { $result = $request->get('data:Article'); $this->assertEqual($expected, $result); - $expected = null; $result = $request->get('not:Post'); - $this->assertEqual($expected, $result); + $this->assertNull($result); $expected = '/lithium.com'; $result = $request->get('env:base'); @@ -234,6 +206,14 @@ class RequestTest extends \lithium\test\Unit { $this->assertTrue($request->is('cool')); $this->assertFalse($request->is('foo')); + + $request = new Request(array('env' => array( + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (iPhone; U; XXXXX like Mac OS X; en) AppleWebKit/420+' + ))); + + $request->detect('iPhone', array('HTTP_USER_AGENT', '/iPhone/')); + $isiPhone = $request->is('iPhone'); // returns true if 'iPhone' appears anywhere in the UA + $this->assertTrue($isiPhone); } public function testDetectWithClosure() { @@ -251,18 +231,16 @@ class RequestTest extends \lithium\test\Unit { return true; })); - $expected = true; $result = $request->is('cool'); - $this->assertEqual($expected, $result); + $this->assertTrue($result); } public function testDetectWithArrayRegex() { $request = new Request(array('env' => array('SOME_COOL_DETECTION' => 'this is cool'))); $request->detect('cool', array('SOME_COOL_DETECTION', '/cool/')); - $expected = true; $result = $request->is('cool'); - $this->assertEqual($expected, $result); + $this->assertTrue($result); } public function testDetectSsl() { @@ -861,6 +839,25 @@ class RequestTest extends \lithium\test\Unit { $this->assertEqual('html', $request->accepts()); } + /** + * Tests that accepted content-types without a `q` value are sorted in the order they appear in + * the `HTTP_ACCEPT` header. + */ + public function testAcceptTypeOrder() { + $request = new Request(array('env' => array( + 'HTTP_ACCEPT' => 'application/xhtml+xml,text/html' + ))); + $expected = array('application/xhtml+xml', 'text/html'); + $this->assertEqual($expected, $request->accepts(true)); + + $request = new Request(array('env' => array( + 'HTTP_USER_AGENT' => 'Safari', + 'HTTP_ACCEPT' => 'application/xhtml+xml,text/html,text/plain;q=0.9' + ))); + $expected = array('application/xhtml+xml', 'text/html', 'text/plain'); + $this->assertEqual($expected, $request->accepts(true)); + } + public function testParsingAcceptHeader() { $chrome = array( 'application/xml', @@ -894,6 +891,15 @@ class RequestTest extends \lithium\test\Unit { 'image/x-xbitmap', '*/*;q=0.1' ); + $android = array( + 'application/xml', + 'application/xhtml+xml', + 'text/html;q=0.9', + 'text/plain;q=0.8', + 'image/png', + '*/*;q=0.5', + 'application/youtube-client' + ); $request = new Request(array('env' => array('HTTP_ACCEPT' => join(',', $chrome)))); $this->assertEqual('html', $request->accepts()); $this->assertTrue(array_search('text/plain', $request->accepts(true)), 4); @@ -912,6 +918,42 @@ class RequestTest extends \lithium\test\Unit { $result = $request->accepts(true); $this->assertEqual('text/plain', $result[0]); + + $request = new Request(array('env' => array('HTTP_ACCEPT' => join(',', $android)))); + $this->assertEqual('html', $request->accepts()); + } + + /** + * Tests that `Accept` headers with only one listed content type are parsed property, and tests + * that `'* /*'` is still parsed as `'text/html'`. + */ + public function testAcceptSingleContentType() { + $request = new Request(array('env' => array('HTTP_ACCEPT' => 'application/json,text/xml'))); + $this->assertEqual(array('application/json', 'text/xml'), $request->accepts(true)); + $this->assertEqual('json', $request->accepts()); + + $request = new Request(array('env' => array('HTTP_ACCEPT' => 'application/json'))); + $this->assertEqual(array('application/json'), $request->accepts(true)); + $this->assertEqual('json', $request->accepts()); + + $request = new Request(array('env' => array('HTTP_ACCEPT' => '*/*'))); + $this->assertEqual(array('text/html'), $request->accepts(true)); + $this->assertEqual('html', $request->accepts()); + } + + /** + * Tests that `action\Request` correctly inherits the functionality of the `to()` method + * inherited from `lithium\net\http\Request`. + */ + public function testConvertToUrl() { + $request = new Request(array( + 'env' => array('HTTP_HOST' => 'foo.com', 'HTTPS' => 'on'), + 'base' => '/the/base/path', + 'url' => '/the/url', + 'query' => array('some' => 'query', 'parameter' => 'values') + )); + $expected = 'https://foo.com/the/base/path/the/url?some=query&parameter=values'; + $this->assertEqual($expected, $request->to('url')); } } diff --git a/libraries/lithium/tests/cases/action/ResponseTest.php b/libraries/lithium/tests/cases/action/ResponseTest.php index cbe9ca9..39023ab 100644 --- a/libraries/lithium/tests/cases/action/ResponseTest.php +++ b/libraries/lithium/tests/cases/action/ResponseTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -53,6 +53,7 @@ class ResponseTest extends \lithium\test\Unit { 'HTTP/1.1 200 OK', 'Expires: ' . gmdate('D, d M Y H:i:s', $expires) . ' GMT', 'Cache-Control: max-age=' . ($expires - time()), + 'Pragma: cache' ); $this->assertEqual($headers, $this->response->testHeaders); @@ -65,6 +66,7 @@ class ResponseTest extends \lithium\test\Unit { 'HTTP/1.1 200 OK', 'Expires: ' . gmdate('D, d M Y H:i:s', strtotime($expires)) . ' GMT', 'Cache-Control: max-age=' . (strtotime($expires) - time()), + 'Pragma: cache' ); $this->assertEqual($headers, $this->response->testHeaders); @@ -89,7 +91,7 @@ class ResponseTest extends \lithium\test\Unit { ); $this->assertEqual($headers, $this->response->testHeaders); - $this->expectException('/^Request::disableCache\(\)/'); + $this->expectException('/^`Request::disableCache\(\)`.+`Request::cache\(false\)`/'); $this->response->disableCache(); } @@ -111,12 +113,12 @@ class ResponseTest extends \lithium\test\Unit { $result = ob_get_clean(); $this->assertEqual(array('HTTP/1.1 303 See Other'), $this->response->testHeaders); - $this->expectException('/Invalid status code/'); $this->response->status('foobar'); ob_start(); $this->response->render(); $result = ob_get_clean(); - $this->assertFalse($this->response->testHeaders); + $expected = array('HTTP/1.1 500 Internal Server Error'); + $this->assertEqual($expected, $this->response->testHeaders); } /** @@ -157,11 +159,18 @@ class ResponseTest extends \lithium\test\Unit { $headers = array('HTTP/1.1 301 Moved Permanently', 'Location: /'); $this->assertEqual($headers, $this->response->testHeaders); - $this->response = new Response(array('location' => array( - 'controller' => 'foo_bar', 'action' => 'index' - ))); + $this->response = new Response(array( + 'classes' => array('router' => __CLASS__), + 'location' => array('controller' => 'foo_bar', 'action' => 'index') + )); $this->assertEqual(array('location: /foo_bar'), $this->response->headers()); } + + public static function match($url) { + if ($url == array('controller' => 'foo_bar', 'action' => 'index')) { + return '/foo_bar'; + } + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/analysis/DebuggerTest.php b/libraries/lithium/tests/cases/analysis/DebuggerTest.php index ca1b2eb..44c8637 100644 --- a/libraries/lithium/tests/cases/analysis/DebuggerTest.php +++ b/libraries/lithium/tests/cases/analysis/DebuggerTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/analysis/DocblockTest.php b/libraries/lithium/tests/cases/analysis/DocblockTest.php index a326df5..f98848e 100644 --- a/libraries/lithium/tests/cases/analysis/DocblockTest.php +++ b/libraries/lithium/tests/cases/analysis/DocblockTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -108,7 +108,7 @@ class DocblockTest extends \lithium\test\Unit { /** * This docblock has an extra * in the closing element. * - **/ + */ public function testBadlyClosedDocblock() { $info = Inspector::info(__METHOD__ . '()'); $description = 'This docblock has an extra * in the closing element.'; diff --git a/libraries/lithium/tests/cases/analysis/InspectorTest.php b/libraries/lithium/tests/cases/analysis/InspectorTest.php index ba6586e..d1d45fd 100644 --- a/libraries/lithium/tests/cases/analysis/InspectorTest.php +++ b/libraries/lithium/tests/cases/analysis/InspectorTest.php @@ -2,14 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\analysis; -use \ReflectionClass; -use \ReflectionMethod; +use ReflectionClass; +use ReflectionMethod; use lithium\core\Libraries; use lithium\analysis\Inspector; @@ -39,7 +39,10 @@ class InspectorTest extends \lithium\test\Unit { ))); $this->assertEqual($expected, $result); - $result = Inspector::methods($class, 'ranges'); + $this->assertNull(Inspector::methods('\lithium\core\Foo')); + + $result = Inspector::methods('stdClass', 'extents'); + $this->assertEqual(array(), $result); } public function testMethodInspection() { @@ -182,6 +185,8 @@ class InspectorTest extends \lithium\test\Unit { $this->assertEqual(array('modifiers', 'namespace'), array_keys($result)); $this->assertNull(Inspector::info('\lithium\analysis\Inspector::$foo')); + + $this->assertNull(Inspector::info('\lithium\core\Foo::$foo')); } public function testClassDependencies() { @@ -268,6 +273,8 @@ class InspectorTest extends \lithium\test\Unit { $this->assertTrue(in_array('response', $result)); $this->assertTrue(in_array('_render', $result)); $this->assertTrue(in_array('_classes', $result)); + + $this->assertNull(Inspector::properties('\lithium\core\Foo')); } } diff --git a/libraries/lithium/tests/cases/analysis/LoggerTest.php b/libraries/lithium/tests/cases/analysis/LoggerTest.php index 74f3be5..1f12e2e 100644 --- a/libraries/lithium/tests/cases/analysis/LoggerTest.php +++ b/libraries/lithium/tests/cases/analysis/LoggerTest.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\analysis; +use lithium\core\Libraries; use lithium\analysis\Logger; use lithium\util\Collection; use lithium\tests\mocks\analysis\MockLoggerAdapter; @@ -18,7 +19,7 @@ use lithium\tests\mocks\analysis\MockLoggerAdapter; class LoggerTest extends \lithium\test\Unit { public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not readable."); } @@ -64,10 +65,12 @@ class LoggerTest extends \lithium\test\Unit { } public function testIntegrationWriteFile() { - $base = LITHIUM_APP_PATH . '/resources/tmp/logs'; + $base = Libraries::get(true, 'resources') . '/tmp/logs'; $this->skipIf(!is_writable($base), "{$base} is not writable."); - $config = array('default' => array('adapter' => 'File', 'timestamp' => false)); + $config = array('default' => array( + 'adapter' => 'File', 'timestamp' => false, 'format' => "{:message}\n" + )); Logger::config($config); $result = Logger::write('info', 'Message line 1'); @@ -88,16 +91,19 @@ class LoggerTest extends \lithium\test\Unit { } public function testWriteWithInvalidPriority() { - $this->expectException("Attempted to write log message with invalid priority 'foo'."); + $this->expectException("Attempted to write log message with invalid priority `foo`."); Logger::foo("Test message"); } public function testWriteByName() { - $base = LITHIUM_APP_PATH . '/resources/tmp/logs'; + $base = Libraries::get(true, 'resources') . '/tmp/logs'; $this->skipIf(!is_writable($base), "{$base} is not writable."); Logger::config(array('default' => array( - 'adapter' => 'File', 'timestamp' => false, 'priority' => false + 'adapter' => 'File', + 'timestamp' => false, + 'priority' => false, + 'format' => "{:message}\n" ))); $this->assertFalse(file_exists($base . '/info.log')); diff --git a/libraries/lithium/tests/cases/analysis/ParserTest.php b/libraries/lithium/tests/cases/analysis/ParserTest.php index 835cee1..01814f6 100644 --- a/libraries/lithium/tests/cases/analysis/ParserTest.php +++ b/libraries/lithium/tests/cases/analysis/ParserTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/analysis/logger/adapter/CacheTest.php b/libraries/lithium/tests/cases/analysis/logger/adapter/CacheTest.php index 4903628..8af3bb7 100644 --- a/libraries/lithium/tests/cases/analysis/logger/adapter/CacheTest.php +++ b/libraries/lithium/tests/cases/analysis/logger/adapter/CacheTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/analysis/logger/adapter/FileTest.php b/libraries/lithium/tests/cases/analysis/logger/adapter/FileTest.php index a809cbe..5980806 100644 --- a/libraries/lithium/tests/cases/analysis/logger/adapter/FileTest.php +++ b/libraries/lithium/tests/cases/analysis/logger/adapter/FileTest.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\analysis\logger\adapter; +use lithium\core\Libraries; use lithium\util\collection\Filters; use lithium\analysis\logger\adapter\File; @@ -15,8 +16,13 @@ class FileTest extends \lithium\test\Unit { public $subject; + public function skip() { + $path = realpath(Libraries::get(true, 'resources') . '/tmp/logs'); + $this->skipIf(!is_writable($path), "Path `{$path}` is not writable."); + } + public function setUp() { - $this->path = LITHIUM_APP_PATH . '/resources/tmp/logs'; + $this->path = Libraries::get(true, 'resources') . '/tmp/logs'; $this->tearDown(); } @@ -39,7 +45,9 @@ class FileTest extends \lithium\test\Unit { } public function testWithoutTimestamp() { - $this->subject = new File(array('path' => $this->path, 'timestamp' => false)); + $this->subject = new File(array( + 'path' => $this->path, 'timestamp' => false, 'format' => "{:message}\n" + )); $priority = 'debug'; $message = 'This is a debug message'; $function = $this->subject->write($priority, $message); diff --git a/libraries/lithium/tests/cases/analysis/logger/adapter/GrowlTest.php b/libraries/lithium/tests/cases/analysis/logger/adapter/GrowlTest.php index 48de8c3..8d3204c 100644 --- a/libraries/lithium/tests/cases/analysis/logger/adapter/GrowlTest.php +++ b/libraries/lithium/tests/cases/analysis/logger/adapter/GrowlTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/analysis/logger/adapter/SyslogTest.php b/libraries/lithium/tests/cases/analysis/logger/adapter/SyslogTest.php index 361bea9..f636a18 100644 --- a/libraries/lithium/tests/cases/analysis/logger/adapter/SyslogTest.php +++ b/libraries/lithium/tests/cases/analysis/logger/adapter/SyslogTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/console/CommandTest.php b/libraries/lithium/tests/cases/console/CommandTest.php index d489f73..4109c12 100644 --- a/libraries/lithium/tests/cases/console/CommandTest.php +++ b/libraries/lithium/tests/cases/console/CommandTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -155,11 +155,9 @@ class CommandTest extends \lithium\test\Unit { $this->assertTrue($return); - $expected = "li3 mock-command --case=CASE --face=FACE "; - $expected .= "--mace=MACE --race=RACE -lace [ARGS]"; - $expected = preg_quote($expected); + $expected = "DESCRIPTION.*This is the Mock Command"; $result = $command->response->output; - $this->assertPattern("/{$expected}/", $result); + $this->assertPattern("/{$expected}/s", $result); $command = new MockCommand(array('request' => $this->request)); $return = $command->__invoke('_help'); @@ -169,16 +167,6 @@ class CommandTest extends \lithium\test\Unit { $this->assertPattern("/{$expected}/m", $result); } - public function testAdvancedHelp() { - $command = new MockCommandHelp(array('request' => $this->request)); - $return = $command->__invoke('_help'); - - $expected = "li3 mock-command-help --long=LONG -s [ARGS]"; - $expected = preg_quote($expected); - $result = $command->response->output; - $this->assertPattern("/{$expected}/", $result); - } - public function testIn() { $command = new MockCommand(array('request' => $this->request)); fwrite($command->request->input, 'nada mucho'); diff --git a/libraries/lithium/tests/cases/console/DispatcherTest.php b/libraries/lithium/tests/cases/console/DispatcherTest.php index 43eb3cc..86e73d5 100644 --- a/libraries/lithium/tests/cases/console/DispatcherTest.php +++ b/libraries/lithium/tests/cases/console/DispatcherTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -57,10 +57,7 @@ class DispatcherTest extends \lithium\test\Unit { public function testRunWithPassed() { $response = Dispatcher::run(new Request(array( - 'args' => array( - 'lithium\tests\mocks\console\MockDispatcherCommand', - 'with param' - ) + 'args' => array('lithium\tests\mocks\console\MockDispatcherCommand', 'with param') ))); $expected = 'run'; @@ -74,10 +71,7 @@ class DispatcherTest extends \lithium\test\Unit { public function testRunWithAction() { $response = Dispatcher::run(new Request(array( - 'args' => array( - 'lithium\tests\mocks\console\MockDispatcherCommand', - 'testAction' - ) + 'args' => array('lithium\tests\mocks\console\MockDispatcherCommand', 'testAction') ))); $expected = 'testAction'; $result = $response->testAction; @@ -85,7 +79,7 @@ class DispatcherTest extends \lithium\test\Unit { } public function testInvalidCommand() { - $expected = (object) array('status' => "Command `\\this\\command\\is\\fake` not found\n"); + $expected = (object) array('status' => "Command `\\this\\command\\is\\fake` not found.\n"); $result = Dispatcher::run(new Request(array( 'args' => array( '\this\command\is\fake', @@ -97,7 +91,7 @@ class DispatcherTest extends \lithium\test\Unit { } public function testRunWithCamelizingCommand() { - $expected = (object) array('status' => "Command `FooBar` not found\n"); + $expected = (object) array('status' => "Command `FooBar` not found.\n"); $result = Dispatcher::run(new Request(array( 'args' => array( 'foo-bar', @@ -105,7 +99,7 @@ class DispatcherTest extends \lithium\test\Unit { ))); $this->assertEqual($expected, $result); - $expected = (object) array('status' => "Command `FooBar` not found\n"); + $expected = (object) array('status' => "Command `FooBar` not found.\n"); $result = Dispatcher::run(new Request(array( 'args' => array('foo_bar') ))); diff --git a/libraries/lithium/tests/cases/console/RequestTest.php b/libraries/lithium/tests/cases/console/RequestTest.php index 141dace..5188dcf 100644 --- a/libraries/lithium/tests/cases/console/RequestTest.php +++ b/libraries/lithium/tests/cases/console/RequestTest.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\console; +use lithium\core\Libraries; use lithium\console\Request; class RequestTest extends \lithium\test\Unit { @@ -18,7 +19,7 @@ class RequestTest extends \lithium\test\Unit { public function setUp() { $this->streams = array( - 'input' => LITHIUM_APP_PATH . '/resources/tmp/tests/input.txt', + 'input' => Libraries::get(true, 'resources') . '/tmp/tests/input.txt' ); $this->_backups['cwd'] = getcwd(); @@ -52,13 +53,13 @@ class RequestTest extends \lithium\test\Unit { } public function testEnvWorking() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_readable($base), "{$base} is not readable."); - chdir(LITHIUM_APP_PATH . '/resources/tmp/tests'); + chdir(Libraries::get(true, 'resources') . '/tmp/tests'); $request = new Request(); - $expected = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $expected = realpath(Libraries::get(true, 'resources') . '/tmp/tests'); $result = $request->env('working'); $this->assertEqual($expected, $result); } @@ -77,18 +78,14 @@ class RequestTest extends \lithium\test\Unit { } public function testConstructWithConfigArgv() { - $request = new Request(array( - 'args' => array('/path/to/lithium.php', 'wrong') - )); + $request = new Request(array('args' => array('/path/to/lithium.php', 'wrong'))); $expected = array('/path/to/lithium.php', 'wrong'); $result = $request->argv; $this->assertEqual($expected, $result); $_SERVER['argv'] = array('/path/to/lithium.php'); - $request = new Request(array( - 'args' => array('one', 'two') - )); + $request = new Request(array('args' => array('one', 'two'))); $expected = '/path/to/lithium.php'; $result = $request->env('script'); @@ -120,13 +117,11 @@ class RequestTest extends \lithium\test\Unit { } public function testConstructWithEnv() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_readable($base), "{$base} is not writable."); - chdir(LITHIUM_APP_PATH . '/resources/tmp'); - $request = new Request(array( - 'env' => array('working' => '/some/other/path') - )); + chdir(Libraries::get(true, 'resources') . '/tmp'); + $request = new Request(array('env' => array('working' => '/some/other/path'))); $expected = '/some/other/path'; $result = $request->env('working'); @@ -134,37 +129,26 @@ class RequestTest extends \lithium\test\Unit { } public function testInput() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); $stream = fopen($this->streams['input'], 'w+'); - $request = new Request(array( - 'input' => $stream - )); + $request = new Request(array('input' => $stream)); $this->assertTrue(is_resource($request->input)); $this->assertEqual($stream, $request->input); - - $expected = 2; - $result = fwrite($request->input, 'ok'); - $this->assertEqual($expected, $result); + $this->assertEqual(2, fwrite($request->input, 'ok')); rewind($request->input); - $expected = 'ok'; - $result = $request->input(); - $this->assertEqual($expected, $result); + $this->assertEqual('ok', $request->input()); } public function testArgs() { $request = new Request(); $request->params = array( - 'command' => 'one', 'action' => 'two', - 'args' => array('three', 'four', 'five') + 'command' => 'one', 'action' => 'two', 'args' => array('three', 'four', 'five') ); - - $expected = 'five'; - $result = $request->args(2); - $this->assertEqual($expected, $result); + $this->assertEqual('five', $request->args(2)); } public function testShiftDefaultOne() { @@ -176,8 +160,7 @@ class RequestTest extends \lithium\test\Unit { $request->shift(); $expected = array('command' => 'two', 'action' => 'three', 'args' => array('four', 'five')); - $result = $request->params; - $this->assertEqual($expected, $result); + $this->assertEqual($expected, $request->params); } public function testShiftTwo() { diff --git a/libraries/lithium/tests/cases/console/ResponseTest.php b/libraries/lithium/tests/cases/console/ResponseTest.php index f2d1e23..572764b 100644 --- a/libraries/lithium/tests/cases/console/ResponseTest.php +++ b/libraries/lithium/tests/cases/console/ResponseTest.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\console; +use lithium\core\Libraries; use lithium\console\Response; use lithium\console\Request; @@ -17,8 +18,8 @@ class ResponseTest extends \lithium\test\Unit { public function setUp() { $this->streams = array( - 'output' => LITHIUM_APP_PATH . '/resources/tmp/tests/output.txt', - 'error' => LITHIUM_APP_PATH . '/resources/tmp/tests/error.txt' + 'output' => Libraries::get(true, 'resources') . '/tmp/tests/output.txt', + 'error' => Libraries::get(true, 'resources') . '/tmp/tests/error.txt' ); } @@ -33,15 +34,14 @@ class ResponseTest extends \lithium\test\Unit { public function testConstructWithoutConfig() { $response = new Response(); $this->assertTrue(is_resource($response->output)); - $this->assertTrue(is_resource($response->error)); } public function testConstructWithConfigOutput() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); - $stream = fopen($this->streams['output'], 'w'); + $response = new Response(array( 'output' => $stream )); @@ -50,53 +50,36 @@ class ResponseTest extends \lithium\test\Unit { } - public function testConstructWithConfigErrror() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + public function testConstructWithConfigError() { + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); $stream = fopen($this->streams['error'], 'w'); - $response = new Response(array( - 'error' => $stream - )); + $response = new Response(array('error' => $stream)); $this->assertTrue(is_resource($response->error)); $this->assertEqual($stream, $response->error); - } public function testOutput() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); - $response = new Response(array( - 'output' => fopen($this->streams['output'], 'w+') - )); + $response = new Response(array('output' => fopen($this->streams['output'], 'w+'))); $this->assertTrue(is_resource($response->output)); - $expected = 2; - $result = $response->output('ok'); - $this->assertEqual($expected, $result); - $expected = 'ok'; - $result = file_get_contents($this->streams['output']); - $this->assertEqual($expected, $result); + $this->assertEqual(2, $response->output('ok')); + $this->assertEqual('ok', file_get_contents($this->streams['output'])); } public function testError() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); - $response = new Response(array( - 'error' => fopen($this->streams['error'], 'w+') - )); + $response = new Response(array('error' => fopen($this->streams['error'], 'w+'))); $this->assertTrue(is_resource($response->error)); - - $expected = 2; - $result = $response->error('ok'); - $this->assertEqual($expected, $result); - - $expected = 'ok'; - $result = file_get_contents($this->streams['error']); - $this->assertEqual($expected, $result); + $this->assertEqual(2, $response->error('ok')); + $this->assertEqual('ok', file_get_contents($this->streams['error'])); } } diff --git a/libraries/lithium/tests/cases/console/RouterTest.php b/libraries/lithium/tests/cases/console/RouterTest.php index 941bf2e..20a4d26 100644 --- a/libraries/lithium/tests/cases/console/RouterTest.php +++ b/libraries/lithium/tests/cases/console/RouterTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -24,7 +24,7 @@ class RouterTest extends \lithium\test\Unit { $_SERVER = $this->_backup; } - public function testParseNoOptions() { + public function testParseNoArgumentsNoOptions() { $expected = array( 'command' => null, 'action' => 'run', 'args' => array() ); @@ -32,7 +32,7 @@ class RouterTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); } - public function testParseWithPassed() { + public function testParseArguments() { $expected = array( 'command' => 'test', 'action' => 'action', 'args' => array('param') @@ -43,21 +43,7 @@ class RouterTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); } - public function testParseWithNamed() { - $expected = array( - 'command' => 'test', 'action' => 'lithium.tests.cases.console.RouterTest', - 'args' => array(), - 'case' => true - ); - $result = Router::parse(new Request(array( - 'args' => array( - 'test', '-case', 'lithium.tests.cases.console.RouterTest' - ) - ))); - $this->assertEqual($expected, $result); - } - - public function testParseWithDoubleNamed() { + public function testParseGnuStyleLongOptions() { $expected = array( 'command' => 'test', 'action' => 'run', 'args' => array(), 'case' => 'lithium.tests.cases.console.RouterTest' @@ -85,10 +71,10 @@ class RouterTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); } - public function testParseWithParam() { + public function testParseShortOption() { $expected = array( 'command' => 'test', 'action' => 'action', 'args' => array(), - 'i' => true, + 'i' => true ); $result = Router::parse(new Request(array( 'args' => array('test', 'action', '-i') @@ -97,13 +83,44 @@ class RouterTest extends \lithium\test\Unit { $expected = array( 'command' => 'test', 'action' => 'action', 'args' => array('something'), - 'i' => true, + 'i' => true ); $result = Router::parse(new Request(array( 'args' => array('test', 'action', '-i', 'something') ))); $this->assertEqual($expected, $result); } + + public function testParseShortOptionAsFirst() { + $expected = array( + 'command' => 'test', 'action' => 'action', 'args' => array(), + 'i' => true + ); + $result = Router::parse(new Request(array( + 'args' => array('-i', 'test', 'action') + ))); + $this->assertEqual($expected, $result); + + $expected = array( + 'command' => 'test', 'action' => 'action', 'args' => array('something'), + 'i' => true + ); + $result = Router::parse(new Request(array( + 'args' => array('-i', 'test', 'action', 'something') + ))); + $this->assertEqual($expected, $result); + } + + public function testParseGnuStyleLongOptionAsFirst() { + $expected = array( + 'command' => 'test', 'action' => 'action', 'long' => 'something', 'i' => true, + 'args' => array() + ); + $result = Router::parse(new Request(array( + 'args' => array('--long=something', 'test', 'action', '-i') + ))); + $this->assertEqual($expected, $result); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/console/command/CreateTest.php b/libraries/lithium/tests/cases/console/command/CreateTest.php index f362397..77ad23e 100644 --- a/libraries/lithium/tests/cases/console/command/CreateTest.php +++ b/libraries/lithium/tests/cases/console/command/CreateTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -22,7 +22,7 @@ class CreateTest extends \lithium\test\Unit { protected $_testPath = null; public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not writable."); } @@ -63,9 +63,8 @@ class CreateTest extends \lithium\test\Unit { $this->request->params['args'] = array('does_not_exist', 'anywhere'); $create = new MockCreate(array('request' => $this->request)); - $expected = false; $result = $create->run('does_not_exist'); - $this->assertEqual($expected, $result); + $this->assertFalse($result); $expected = "does_not_exist could not be created.\n"; $result = $create->response->error; @@ -91,7 +90,7 @@ class CreateTest extends \lithium\test\Unit { $create = new MockCreate(array('request' => $this->request)); $result = $create->save(array( 'namespace' => 'create_test\tests\cases\models', - 'use' => 'create_test\models\Post', + 'use' => 'create_test\models\Posts', 'class' => 'PostTest', 'methods' => "\tpublic function testCreate() {\n\n\t}\n", )); @@ -106,9 +105,8 @@ class CreateTest extends \lithium\test\Unit { public function testRunWithoutCommand() { $create = new MockCreate(array('request' => $this->request)); - $expected = false; $result = $create->run(); - $this->assertEqual($expected, $result); + $this->assertFalse($result); $expected = "What would you like to create? (model/view/controller/test/mock) \n > "; $result = $create->response->output; @@ -118,13 +116,12 @@ class CreateTest extends \lithium\test\Unit { public function testRunNotSaved() { $this->request->params = array( 'library' => 'not_here', 'command' => 'create', 'action' => 'model', - 'args' => array('model', 'Post') + 'args' => array('model', 'Posts') ); $create = new MockCreate(array('request' => $this->request)); - $expected = false; $result = $create->run('model'); - $this->assertEqual($expected, $result); + $this->assertFalse($result); $expected = "model could not be created.\n"; $result = $create->response->error; @@ -134,7 +131,7 @@ class CreateTest extends \lithium\test\Unit { public function testRunWithModelCommand() { $this->request->params = array( 'library' => 'create_test', 'command' => 'create', 'action' => 'model', - 'args' => array('Post') + 'args' => array('Posts') ); $create = new MockCreate(array('request' => $this->request)); @@ -145,14 +142,14 @@ class CreateTest extends \lithium\test\Unit { $result = $create->request->command; $this->assertEqual($expected, $result); - $result = $this->_testPath . '/create_test/models/Post.php'; + $result = $this->_testPath . '/create_test/models/Posts.php'; $this->assertTrue(file_exists($result)); } public function testRunWithTestModelCommand() { $this->request->params = array( 'library' => 'create_test', 'command' => 'create', 'action' => 'test', - 'args' => array('model', 'Post'), + 'args' => array('model', 'Posts'), ); $create = new MockCreate(array('request' => $this->request)); @@ -163,14 +160,14 @@ class CreateTest extends \lithium\test\Unit { $result = $create->request->command; $this->assertEqual($expected, $result); - $result = $this->_testPath . '/create_test/tests/cases/models/PostTest.php'; + $result = $this->_testPath . '/create_test/tests/cases/models/PostsTest.php'; $this->assertTrue(file_exists($result)); } public function testRunWithTestControllerCommand() { $this->request->params = array( 'library' => 'create_test', 'command' => 'create', 'action' => 'test', - 'args' => array('controller', 'Post'), + 'args' => array('controller', 'Posts'), ); $create = new MockCreate(array('request' => $this->request)); @@ -188,7 +185,7 @@ class CreateTest extends \lithium\test\Unit { public function testRunWithTestOtherCommand() { $this->request->params = array( 'library' => 'create_test', 'command' => 'create', 'action' => 'test', - 'args' => array('something', 'Post'), + 'args' => array('something', 'Posts'), ); $create = new MockCreate(array('request' => $this->request)); @@ -198,26 +195,26 @@ class CreateTest extends \lithium\test\Unit { $result = $create->request->command; $this->assertEqual($expected, $result); - $result = $this->_testPath . '/create_test/tests/cases/something/PostTest.php'; + $result = $this->_testPath . '/create_test/tests/cases/something/PostsTest.php'; $this->assertTrue(file_exists($result)); } public function testRunAll() { $this->request->params = array( - 'library' => 'create_test', 'command' => 'create', 'action' => 'Post', + 'library' => 'create_test', 'command' => 'create', 'action' => 'Posts', 'args' => array(), ); $create = new MockCreate(array('request' => $this->request)); - $create->run('Post'); + $create->run('Posts'); - $result = $this->_testPath . '/create_test/models/Post.php'; + $result = $this->_testPath . '/create_test/models/Posts.php'; $this->assertTrue(file_exists($result)); $result = $this->_testPath . '/create_test/controllers/PostsController.php'; $this->assertTrue(file_exists($result)); - $result = $this->_testPath . '/create_test/tests/cases/models/PostTest.php'; + $result = $this->_testPath . '/create_test/tests/cases/models/PostsTest.php'; $this->assertTrue(file_exists($result)); $result = $this->_testPath . '/create_test/tests/cases/controllers/PostsControllerTest.php'; diff --git a/libraries/lithium/tests/cases/console/command/HelpTest.php b/libraries/lithium/tests/cases/console/command/HelpTest.php index e8023b5..6aefb62 100644 --- a/libraries/lithium/tests/cases/console/command/HelpTest.php +++ b/libraries/lithium/tests/cases/console/command/HelpTest.php @@ -4,15 +4,14 @@ namespace lithium\tests\cases\console\command; use lithium\console\command\Help; use lithium\console\Request; -use lithium\tests\mocks\console\command\MockCommandHelp; class HelpTest extends \lithium\test\Unit { public $request; - protected $_backup = array(); + public $classes = array(); - protected $_testPath = null; + protected $_backup = array(); public function setUp() { $this->classes = array('response' => 'lithium\tests\mocks\console\MockResponse'); @@ -30,117 +29,119 @@ class HelpTest extends \lithium\test\Unit { } public function testRun() { - $help = new Help(array( - 'request' => $this->request, 'classes' => $this->classes - )); - $expected = true; - $result = $help->run(); - $this->assertEqual($expected, $result); + $command = new Help(array('request' => $this->request, 'classes' => $this->classes)); + $this->assertTrue($command->run()); - $expected = "COMMANDS\n"; + $expected = "COMMANDS via lithium\n"; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $pattern = "/\s+test\s+Runs a given set of tests and outputs the results\./ms"; $this->assertPattern($pattern, $result); - } public function testRunWithName() { - $help = new Help(array( + $command = new Help(array( 'request' => $this->request, 'classes' => $this->classes )); - $expected = true; - $result = $help->run('Test'); - $this->assertEqual($expected, $result); + $result = $command->run('Test'); + $this->assertTrue($result); - $expected = "li3 test --case=CASE --group=GROUP --filters=FILTERS [ARGS]"; + $expected = "li3 test [--filters=<string>] [--format=<string>] [<path>]"; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); - $expected = "OPTIONS\n --case=CASE\n"; + $expected = "OPTIONS\n <path>\n"; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); - $expected = "missing\n"; + $expected = "DESCRIPTION\n"; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); } public function testApiClass() { - $help = new Help(array( + $command = new Help(array( 'request' => $this->request, 'classes' => $this->classes )); - $expected = null; - $result = $help->api('lithium.util.Inflector'); - $this->assertEqual($expected, $result); + $result = $command->api('lithium.util.Inflector'); + $this->assertNull($result); $expected = "Utility for modifying format of words"; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); } public function testApiMethod() { - $help = new Help(array( + $command = new Help(array( 'request' => $this->request, 'classes' => $this->classes )); - $expected = null; - $result = $help->api('lithium.util.Inflector', 'method'); - $this->assertEqual($expected, $result); + $result = $command->api('lithium.util.Inflector', 'method'); + $this->assertNull($result); - $expected = "rules [type] [config]"; + $expected = "rules"; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); } public function testApiMethodWithName() { - $help = new Help(array( + $command = new Help(array( 'request' => $this->request, 'classes' => $this->classes )); - $expected = null; - $result = $help->api('lithium.util.Inflector', 'method', 'rules'); - $this->assertEqual($expected, $result); + $result = $command->api('lithium.util.Inflector', 'method', 'rules'); + $this->assertNull($result); - $expected = "rules [type] [config]"; + $expected = "rules"; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); } public function testApiProperty() { - $help = new Help(array( + $command = new Help(array( 'request' => $this->request, 'classes' => $this->classes )); - $expected = null; - $result = $help->api('lithium.net.Message', 'property'); - $this->assertEqual($expected, $result); + $result = $command->api('lithium.net.Message', 'property'); + $this->assertNull($result); - $expected = " --host=HOST\n The hostname for this endpoint."; + $expected = " --host=<string>\n The hostname for this endpoint."; $expected = preg_quote($expected); - $result = $help->response->output; + $result = $command->response->output; $this->assertPattern("/{$expected}/", $result); } public function testApiPropertyWithName() { + $command = new Help(array( + 'request' => $this->request, 'classes' => $this->classes + )); + $result = $command->api('lithium.net.Message', 'property'); + $this->assertNull($result); + + $expected = " --host=<string>\n The hostname for this endpoint."; + $expected = preg_quote($expected); + $result = $command->response->output; + $this->assertPattern("/{$expected}/", $result); + } + + public function testApiProperties() { $help = new Help(array( 'request' => $this->request, 'classes' => $this->classes )); $expected = null; - $result = $help->api('lithium.net.Message', 'property'); + $result = $help->api('lithium.tests.mocks.console.command.MockCommandHelp', 'property'); $this->assertEqual($expected, $result); - $expected = " --host=HOST\n The hostname for this endpoint."; - $expected = preg_quote($expected); + $expected = "\-\-long=<string>.*\-\-blong.*\-s"; $result = $help->response->output; - $this->assertPattern("/{$expected}/", $result); + $this->assertPattern("/{$expected}/s", $result); } } diff --git a/libraries/lithium/tests/cases/console/command/LibraryTest.php b/libraries/lithium/tests/cases/console/command/LibraryTest.php index f6c6a56..c7f8707 100644 --- a/libraries/lithium/tests/cases/console/command/LibraryTest.php +++ b/libraries/lithium/tests/cases/console/command/LibraryTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -22,7 +22,7 @@ class LibraryTest extends \lithium\test\Unit { protected $_testPath = null; public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not writable."); } @@ -63,9 +63,7 @@ class LibraryTest extends \lithium\test\Unit { $result = $this->library->config('server', 'lab.lithify.me'); $this->assertTrue($result); - $expected = array('servers' => array( - 'lab.lithify.me' => true - )); + $expected = array('servers' => array('lab.lithify.me' => true)); $result = json_decode(file_get_contents($this->testConf), true); $this->assertEqual($expected, $result); @@ -86,9 +84,8 @@ class LibraryTest extends \lithium\test\Unit { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->library->library = 'library_test'; - $expected = true; $result = $this->library->extract($this->_testPath . '/library_test'); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = "library_test created in {$this->_testPath} from "; $expected .= realpath(LITHIUM_LIBRARY_PATH) @@ -101,15 +98,14 @@ class LibraryTest extends \lithium\test\Unit { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - INI setting phar.readonly = On' + 'INI setting phar.readonly = On' ); $this->library->library = 'library_test'; - $expected = true; $testPath = "{$this->_testPath}/library_test"; $result = $this->library->archive($testPath, $testPath); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = "library_test.phar.gz created in {$this->_testPath} from "; $expected .= "{$this->_testPath}/library_test\n"; @@ -122,15 +118,14 @@ class LibraryTest extends \lithium\test\Unit { public function testExtractWithFullPaths() { $this->skipIf( !file_exists("{$this->_testPath}/library_test.phar.gz"), - 'Skipped test {:class}::{:function}() - depends on {:class}::testArchive()' + 'Depends on ' . __CLASS__ . '::testArchive()' ); $this->library->library = 'library_test'; - $expected = true; $result = $this->library->extract( $this->_testPath . '/library_test.phar.gz', $this->_testPath . '/new' ); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $this->assertTrue(file_exists($this->_testPath . '/new')); @@ -152,26 +147,24 @@ class LibraryTest extends \lithium\test\Unit { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - INI setting phar.readonly = On' + 'INI setting phar.readonly = On' ); chdir('new'); - $app = new Library(array( - 'request' => new Request(), 'classes' => $this->classes - )); + $app = new Library(array('request' => new Request(), 'classes' => $this->classes)); $app->library = 'does_not_exist'; - $expected = true; $result = $app->archive(); - $this->assertEqual($expected, $result); + $this->assertTrue($result); - $expected = "new.phar.gz created in {$this->_testPath} from "; - $expected .= "{$this->_testPath}/new\n"; + $path = realpath($this->_testPath); + $expected = "new.phar.gz created in {$path} from {$path}/new\n"; $result = $app->response->output; $this->assertEqual($expected, $result); Phar::unlinkArchive($this->_testPath . '/new.phar'); Phar::unlinkArchive($this->_testPath . '/new.phar.gz'); + $this->_cleanUp('tests/new'); rmdir($this->_testPath . '/new'); } @@ -179,20 +172,17 @@ class LibraryTest extends \lithium\test\Unit { public function testExtractWhenLibraryDoesNotExist() { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); chdir($this->_testPath); - $app = new Library(array( - 'request' => new Request(), 'classes' => $this->classes - )); + $app = new Library(array('request' => new Request(), 'classes' => $this->classes)); $app->library = 'does_not_exist'; - $expected = true; $result = $app->extract(); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $this->assertTrue(file_exists($this->_testPath . '/new')); - $expected = "new created in {$this->_testPath} from "; - $expected .= realpath(LITHIUM_LIBRARY_PATH) - . "/lithium/console/command/create/template/app.phar.gz\n"; + $path = realpath($this->_testPath); + $tplPath = realpath(LITHIUM_LIBRARY_PATH) . '/lithium/console/command/create/template'; + $expected = "new created in {$path} from {$tplPath}/app.phar.gz\n"; $result = $app->response->output; $this->assertEqual($expected, $result); @@ -204,9 +194,8 @@ class LibraryTest extends \lithium\test\Unit { $this->library->library = 'library_plugin_test'; $path = $this->_testPath; - $expected = true; $result = $this->library->extract('plugin', "{$path}/library_test_plugin"); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = "library_test_plugin created in {$path} from " . realpath(LITHIUM_LIBRARY_PATH); $expected .= "/lithium/console/command/create/template/plugin.phar.gz\n"; @@ -301,7 +290,7 @@ class LibraryTest extends \lithium\test\Unit { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - INI setting phar.readonly = On' + 'INI setting phar.readonly = On' ); $result = file_put_contents( @@ -351,7 +340,7 @@ class LibraryTest extends \lithium\test\Unit { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - relies on {:class}::testPush()' + 'Relies on ' . __CLASS__ . '::testPush()' ); $this->library->path = $this->_testPath; $result = $this->library->install('library_test_plugin'); @@ -391,7 +380,7 @@ class LibraryTest extends \lithium\test\Unit { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - relies on {:class}::testPush()' + 'Relies on ' . __CLASS__ . '::testPush()' ); $this->library->path = $this->_testPath; $result = $this->library->install('li3_lab'); @@ -457,7 +446,7 @@ test; $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - INI setting phar.readonly = On' + 'INI setting phar.readonly = On' ); $result = $this->library->extract('plugin', $this->_testPath . '/library_test_plugin'); $this->assertTrue($result); @@ -537,7 +526,7 @@ test; $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - INI setting phar.readonly = On' + 'INI setting phar.readonly = On' ); $result = $this->library->extract('plugin', $this->_testPath . '/library_test_plugin'); $this->assertTrue($result); @@ -597,19 +586,17 @@ test; $this->_cleanUp(); } - public function testPushNotValid() { $this->skipIf(!extension_loaded('zlib'), 'The zlib extension is not loaded.'); $this->skipIf( ini_get('phar.readonly') == '1', - 'Skipped test {:class}::{:function}() - INI setting phar.readonly = On' + 'INI setting phar.readonly = On' ); $this->library->library = 'library_plugin_test'; $path = $this->_testPath; - $expected = true; $result = $this->library->extract('plugin', "{$path}/library_test_plugin"); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $this->library->response->output = null; $file = $this->_testPath . '/library_test_plugin/config/library_test_plugin.json'; @@ -654,6 +641,10 @@ test; } public function testNoArchive() { + $this->skipIf( + ini_get('phar.readonly') == '1', + 'Skipped test {:class}::{:function}() - INI setting phar.readonly = On' + ); $result = $this->library->archive( $this->_testPath . '/library_test_plugin', $this->_testPath . '/library_test_plugin' diff --git a/libraries/lithium/tests/cases/console/command/RouteTest.php b/libraries/lithium/tests/cases/console/command/RouteTest.php new file mode 100644 index 0000000..f50d5e6 --- /dev/null +++ b/libraries/lithium/tests/cases/console/command/RouteTest.php @@ -0,0 +1,289 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\cases\console\command; + +use lithium\console\command\Route; +use lithium\console\Request; +use lithium\net\http\Router; + +/** + * The RouteTest class tests the "li3 route" command. + */ +class RouteTest extends \lithium\test\Unit { + + /** + * Holds config params. + * + * @var array + */ + protected $_config = array('routes_file' => ''); + + /** + * Holds the temporary test path. + * + * @var string + */ + protected $_testPath = null; + + /** + * Set the testPath and check if it is writable (skip if not). + */ + public function skip() { + $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not writable."); + } + + /** + * Create a temporary routes.php file for testing and reset the router. + */ + public function setUp() { + $this->_config['routes_file'] = "{$this->_testPath}/routes.php"; + + $testParams = 'array("controller" => "lithium\test\Controller")'; + $content = array( + '<?php', + 'use lithium\net\http\Router;', + 'use lithium\core\Environment;', + 'Router::connect("/", "Pages::view");', + 'Router::connect("/pages/{:args}", "Pages::view");', + 'if (!Environment::is("production")) {', + 'Router::connect("/test/{:args}", ' . $testParams . ');', + 'Router::connect("/test", ' . $testParams . ');', + '}', + '?>' + ); + file_put_contents($this->_config['routes_file'], join("\n", $content)); + + Router::reset(); + } + + /** + * Delete the temporary routes.php file. + */ + public function tearDown() { + if (file_exists($this->_config['routes_file'])) { + unlink($this->_config['routes_file']); + } + } + + /** + * Tests if the default environment is loaded correctly + * and if overriding works as expected. + */ + public function testEnvironment() { + $command = new Route(); + $expected = 'development'; + $this->assertEqual($expected, $command->env); + + $request = new Request(); + $request->params['env'] = 'production'; + $command = new Route(array('request' => $request)); + $expected = 'production'; + $this->assertEqual($expected, $command->env); + } + + /** + * Test if the routes.php file is loaded correctly and the + * routes are connected to the router. + */ + public function testRouteLoading() { + $this->assertFalse(Router::get()); + + $command = new Route(array('routes_file' => $this->_config['routes_file'])); + $this->assertEqual(4, count(Router::get())); + + Router::reset(); + + $request = new Request(); + $request->params['env'] = 'production'; + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'request' => $request + )); + $this->assertEqual(2, count(Router::get())); + } + + /** + * Tests the "all" command without an env param. + * + * Don't be confused if the expected output doesn't make sense here. We are + * stripping the whitespace away so that this source code is easier to read. + * Built-In methods are used for output formatting and are tested elsewhere. + */ + public function testAllWithoutEnvironment() { + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => new Request() + )); + + $command->all(); + + $expected = 'TemplateParams-------------- + /{"controller":"pages","action":"view"} + /pages/{:args}{"controller":"pages","action":"view"} + /test/{:args}{"controller":"lithium\\test\\\\Controller","action":"index"} + /test{"controller":"lithium\\test\\\\Controller","action":"index"}'; + $this->assertEqual($this->_strip($expected),$this->_strip($command->response->output)); + } + + /** + * Tests the "all" command with an env (production) param. + * + * Don't be confused if the expected output doesn't make sense here. We are + * stripping the whitespace away so that this source code is easier to read. + * Built-In methods are used for output formatting and are tested elsewhere. + */ + public function testAllWithEnvironment() { + $request = new Request(); + $request->params = array( + 'env' => 'production' + ); + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => $request + )); + + $command->all(); + + $expected = 'TemplateParams-------------- + /{"controller":"pages","action":"view"} + /pages/{:args}{"controller":"pages","action":"view"}'; + $this->assertEqual($this->_strip($expected),$this->_strip($command->response->output)); + } + + /** + * Test the alias method for "all". + */ + public function testRun() { + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => new Request() + )); + + $command->run(); + + $expected = 'TemplateParams-------------- + /{"controller":"pages","action":"view"} + /pages/{:args}{"controller":"pages","action":"view"} + /test/{:args}{"controller":"lithium\\test\\\\Controller","action":"index"} + /test{"controller":"lithium\\test\\\\Controller","action":"index"}'; + $this->assertEqual($this->_strip($expected),$this->_strip($command->response->output)); + } + + /** + * Test the show command with no route. + */ + public function testShowWithNoRoute() { + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => new Request() + )); + + $command->show(); + + $expected = "Please provide a valid URL\n"; + $this->assertEqual($expected, $command->response->error); + } + + /** + * Test the show command with an invalid route. + */ + public function testShowWithInvalidRoute() { + $request = new Request(); + $request->params = array( + 'args' => array('/foobar') + ); + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => $request + )); + $command->show(); + + $expected = "No route found.\n"; + $this->assertEqual($expected, $command->response->output); + } + + /** + * Test the show command with a valid route. + */ + public function testShowWithValidRoute() { + $request = new Request(); + $request->params = array('args' => array('/')); + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => $request + )); + $command->show(); + + $expected = "{\"controller\":\"pages\",\"action\":\"view\"}\n"; + $this->assertEqual($expected, $command->response->output); + } + + /** + * Test the show command with a env param. + */ + public function testShowWithEnvironment() { + $request = new Request(); + $request->params = array( + 'env' => 'production', + 'args' => array('/test') + ); + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => $request + )); + + $command->show(); + + $expected = "No route found.\n"; + $this->assertEqual($expected, $command->response->output); + } + + /** + * Test the show command with http method. + * + * This tests a call similar to "li3 route GET /". + */ + public function testShowWithHttpMethod() { + $request = new Request(); + $request->params = array( + 'args' => array('post', '/') + ); + $command = new Route(array( + 'routes_file' => $this->_config['routes_file'], + 'classes' => array('response' => '\lithium\tests\mocks\console\MockResponse'), + 'request' => $request + )); + + $command->show(); + + $expected = "{\"controller\":\"pages\",\"action\":\"view\"}\n"; + $this->assertEqual($expected, $command->response->output); + } + + /** + * Remove formatting whitespace, tabs and newlines for better sourcecode + * readability. + * + * @param string $str A string from which to strip spaces + * @return string Returns the value of `$str` with all whitespace removed. + */ + protected function _strip($str) { + return preg_replace('/\s/', '', $str); + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/console/command/TestTest.php b/libraries/lithium/tests/cases/console/command/TestTest.php new file mode 100644 index 0000000..7f74669 --- /dev/null +++ b/libraries/lithium/tests/cases/console/command/TestTest.php @@ -0,0 +1,139 @@ +<?php +/** +* Lithium: the most rad php framework +* +* @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) +* @license http://opensource.org/licenses/bsd-license.php The BSD License +*/ + +namespace lithium\tests\cases\console\command; + +use lithium\console\command\Test; +use lithium\console\Request; +use lithium\core\Libraries; + +class TestTest extends \lithium\test\Unit { + + public $request; + + public $classes = array(); + + protected $_backup = array(); + + public function setUp() { + Libraries::cache(false); + + $this->classes = array( + 'response' => 'lithium\tests\mocks\console\MockResponse' + ); + $this->_backup['cwd'] = getcwd(); + $this->_backup['_SERVER'] = $_SERVER; + $_SERVER['argv'] = array(); + + chdir(LITHIUM_LIBRARY_PATH . '/lithium'); + + $this->request = new Request(array('input' => fopen('php://temp', 'w+'))); + $this->request->params = array('library' => 'build_test'); + } + + public function tearDown() { + $_SERVER = $this->_backup['_SERVER']; + chdir($this->_backup['cwd']); + } + + public function testRunWithoutPath() { + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + $result = $command->run(); + $this->assertFalse($result); + } + + public function testRunSingleTestWithAbsolutePath() { + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + $path = LITHIUM_LIBRARY_PATH . '/lithium/tests/mocks/test/cases/MockTest.php'; + $command->run($path); + + $expected = "1 passes\n0 fails and 0 exceptions\n"; + $expected = preg_quote($expected); + $result = $command->response->output; + $this->assertPattern("/{$expected}/", $result); + } + + public function testRunSingleTestWithRelativePath() { + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + + $path = 'tests/mocks/test/cases/MockTest.php'; + $command->run($path); + + $expected = "1 passes\n0 fails and 0 exceptions\n"; + $expected = preg_quote($expected); + $result = $command->response->output; + $this->assertPattern("/{$expected}/", $result); + + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + + $current = basename(getcwd()); + $path = "../{$current}/tests/mocks/test/cases/MockTest.php"; + $command->run($path); + + $expected = "1 passes\n0 fails and 0 exceptions\n"; + $expected = preg_quote($expected); + $result = $command->response->output; + $this->assertPattern("/{$expected}/", $result); + } + + public function testRunMultipleTestsWithAbsolutePath() { + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + $path = LITHIUM_LIBRARY_PATH . '/lithium/tests/mocks/test/cases'; + $command->run($path); + + $expected = "1 / 1 passes\n0 fails and 2 exceptions\n"; + $expected = preg_quote($expected, '/'); + $result = $command->response->output; + $this->assertPattern("/{$expected}/", $result); + } + + public function testReturnRunTestPasses() { + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + $path = LITHIUM_LIBRARY_PATH . '/lithium/tests/mocks/test/cases/MockTest.php'; + $result = $command->run($path); + $this->assertTrue($result); + } + + public function testReturnRunTestFails() { + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + $path = LITHIUM_LIBRARY_PATH . '/lithium/tests/mocks/test/cases/MockTestErrorHandling.php'; + $result = $command->run($path); + $this->assertFalse($result); + } + + public function testJsonFormat() { + $command = new Test(array( + 'request' => $this->request, 'classes' => $this->classes + )); + $path = LITHIUM_LIBRARY_PATH . '/lithium/tests/mocks/test/cases/MockTest.php'; + $command->format = 'json'; + $command->run($path); + + $result = $command->response->output; + $result = json_decode($result, true); + + $this->assertTrue(isset($result['count'])); + $this->assertTrue(isset($result['stats'])); + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/console/command/create/ControllerTest.php b/libraries/lithium/tests/cases/console/command/create/ControllerTest.php index 5d5d286..1272875 100644 --- a/libraries/lithium/tests/cases/console/command/create/ControllerTest.php +++ b/libraries/lithium/tests/cases/console/command/create/ControllerTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -21,7 +21,7 @@ class ControllerTest extends \lithium\test\Unit { protected $_testPath = null; public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not writable."); } @@ -63,7 +63,7 @@ class ControllerTest extends \lithium\test\Unit { 'request' => $this->request, 'classes' => $this->classes )); - $expected = '\\create_test\\models\\Post'; + $expected = 'create_test\\models\\Posts'; $result = $model->invokeMethod('_use', array($this->request)); $this->assertEqual($expected, $result); } @@ -87,22 +87,22 @@ class ControllerTest extends \lithium\test\Unit { namespace create_test\controllers; -use \create_test\models\Post; +use create_test\models\Posts; class PostsController extends \lithium\action\Controller { public function index() { - $posts = Post::all(); + $posts = Posts::all(); return compact('posts'); } public function view() { - $post = Post::first($this->request->id); + $post = Posts::first($this->request->id); return compact('post'); } public function add() { - $post = Post::create(); + $post = Posts::create(); if (($this->request->data) && $post->save($this->request->data)) { $this->redirect(array('Posts::view', 'args' => array($post->id))); @@ -111,7 +111,7 @@ class PostsController extends \lithium\action\Controller { } public function edit() { - $post = Post::find($this->request->id); + $post = Posts::find($this->request->id); if (!$post) { $this->redirect('Posts::index'); @@ -121,6 +121,15 @@ class PostsController extends \lithium\action\Controller { } return compact('post'); } + + public function delete() { + if (!$this->request->is('post') && !$this->request->is('delete')) { + $msg = "Posts::delete can only be called with http:post or http:delete."; + throw new DispatchException($msg); + } + Posts::find($this->request->id)->delete(); + $this->redirect('Posts::index'); + } } diff --git a/libraries/lithium/tests/cases/console/command/create/MockTest.php b/libraries/lithium/tests/cases/console/command/create/MockTest.php index b391d7e..f023893 100644 --- a/libraries/lithium/tests/cases/console/command/create/MockTest.php +++ b/libraries/lithium/tests/cases/console/command/create/MockTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -22,7 +22,7 @@ class MockTest extends \lithium\test\Unit { protected $_testPath = null; public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not writable."); } @@ -46,14 +46,14 @@ class MockTest extends \lithium\test\Unit { public function testMockModel() { $this->request->params += array( 'command' => 'create', 'action' => 'mock', - 'args' => array('model', 'Post') + 'args' => array('model', 'Posts') ); $mock = new Mock(array( 'request' => $this->request, 'classes' => $this->classes )); $mock->path = $this->_testPath; $mock->run('mock'); - $expected = "MockPost created in create_test\\tests\\mocks\\models.\n"; + $expected = "MockPosts created in create_test\\tests\\mocks\\models.\n"; $result = $mock->response->output; $this->assertEqual($expected, $result); @@ -62,7 +62,7 @@ class MockTest extends \lithium\test\Unit { namespace create_test\tests\mocks\models; -class MockPost extends \create_test\models\Post { +class MockPosts extends \create_test\models\Posts { } @@ -71,7 +71,7 @@ class MockPost extends \create_test\models\Post { test; $replace = array("<?php", "?>"); $result = str_replace($replace, '', - file_get_contents($this->_testPath . '/create_test/tests/mocks/models/MockPost.php') + file_get_contents($this->_testPath . '/create_test/tests/mocks/models/MockPosts.php') ); $this->assertEqual($expected, $result); } diff --git a/libraries/lithium/tests/cases/console/command/create/ModelTest.php b/libraries/lithium/tests/cases/console/command/create/ModelTest.php index 2f9a34a..7b848dd 100644 --- a/libraries/lithium/tests/cases/console/command/create/ModelTest.php +++ b/libraries/lithium/tests/cases/console/command/create/ModelTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -21,7 +21,7 @@ class ModelTest extends \lithium\test\Unit { protected $_testPath = null; public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not readable."); } @@ -44,13 +44,13 @@ class ModelTest extends \lithium\test\Unit { public function testClass() { $this->request->params = array( - 'command' => 'model', 'action' => 'Post' + 'command' => 'model', 'action' => 'Posts' ); $model = new Model(array( 'request' => $this->request, 'classes' => $this->classes )); - $expected = 'Post'; + $expected = 'Posts'; $result = $model->invokeMethod('_class', array($this->request)); $this->assertEqual($expected, $result); } diff --git a/libraries/lithium/tests/cases/console/command/create/TestTest.php b/libraries/lithium/tests/cases/console/command/create/TestTest.php index 9dd5ba3..3c71fae 100644 --- a/libraries/lithium/tests/cases/console/command/create/TestTest.php +++ b/libraries/lithium/tests/cases/console/command/create/TestTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -22,7 +22,7 @@ class TestTest extends \lithium\test\Unit { protected $_testPath = null; public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not writable."); } @@ -47,14 +47,14 @@ class TestTest extends \lithium\test\Unit { public function testTestModel() { $this->request->params += array( 'command' => 'create', 'action' => 'test', - 'args' => array('model', 'Post') + 'args' => array('model', 'Posts') ); $test = new Test(array( 'request' => $this->request, 'classes' => $this->classes )); $test->path = $this->_testPath; $test->run('test'); - $expected = "PostTest created in create_test\\tests\\cases\\models.\n"; + $expected = "PostsTest created in create_test\\tests\\cases\\models.\n"; $result = $test->response->output; $this->assertEqual($expected, $result); @@ -63,9 +63,9 @@ class TestTest extends \lithium\test\Unit { namespace create_test\tests\cases\models; -use \create_test\models\Post; +use create_test\models\Posts; -class PostTest extends \lithium\test\Unit { +class PostsTest extends \lithium\test\Unit { public function setUp() {} @@ -78,7 +78,7 @@ class PostTest extends \lithium\test\Unit { test; $replace = array("<?php", "?>"); $result = str_replace($replace, '', - file_get_contents($this->_testPath . '/create_test/tests/cases/models/PostTest.php') + file_get_contents($this->_testPath . '/create_test/tests/cases/models/PostsTest.php') ); $this->assertEqual($expected, $result); } @@ -86,36 +86,35 @@ test; public function testTestModelWithMethods() { $this->_cleanUp(); mkdir($this->_testPath . '/create_test/models/', 0755, true); - file_put_contents($this->_testPath . '/create_test/models/Post.php', + $id = rand(); + $path = "create_test/models/Post{$id}s.php"; + file_put_contents("{$this->_testPath}/{$path}", "<?php namespace create_test\models; -class Post { +class Post{$id}s { public function someMethod() {} }" ); - $this->request->params += array( - 'command' => 'create', 'action' => 'test', - 'args' => array('model', 'Post') - ); - $test = new Test(array( - 'request' => $this->request, 'classes' => $this->classes + $this->request->params += array('command' => 'create', 'action' => 'test', 'args' => array( + 'model', "Post{$id}s" )); + $test = new Test(array('request' => $this->request, 'classes' => $this->classes)); $test->path = $this->_testPath; $test->run('test'); - $expected = "PostTest created in create_test\\tests\\cases\\models.\n"; + $expected = "Post{$id}sTest created in create_test\\tests\\cases\\models.\n"; $result = $test->response->output; $this->assertEqual($expected, $result); - $expected = <<<'test' + $expected = <<<test -namespace create_test\tests\cases\models; +namespace create_test\\tests\\cases\\models; -use \create_test\models\Post; +use create_test\\models\\Post{$id}s; -class PostTest extends \lithium\test\Unit { +class Post{$id}sTest extends \\lithium\\test\\Unit { public function setUp() {} @@ -127,9 +126,8 @@ class PostTest extends \lithium\test\Unit { test; $replace = array("<?php", "?>"); - $result = str_replace($replace, '', - file_get_contents($this->_testPath . '/create_test/tests/cases/models/PostTest.php') - ); + $path = "create_test/tests/cases/models/Post{$id}sTest.php"; + $result = str_replace($replace, '', file_get_contents("{$this->_testPath}/{$path}")); $this->assertEqual($expected, $result); } } diff --git a/libraries/lithium/tests/cases/console/command/create/ViewTest.php b/libraries/lithium/tests/cases/console/command/create/ViewTest.php index d30058f..af60183 100644 --- a/libraries/lithium/tests/cases/console/command/create/ViewTest.php +++ b/libraries/lithium/tests/cases/console/command/create/ViewTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -22,7 +22,7 @@ class ViewTest extends \lithium\test\Unit { protected $_testPath = null; public function skip() { - $this->_testPath = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_testPath = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_testPath), "{$this->_testPath} is not writable."); } @@ -57,9 +57,8 @@ class ViewTest extends \lithium\test\Unit { $result = $view->response->output; $this->assertEqual($expected, $result); - $expected = true; $result = file_exists($this->_testPath . '/create_test/views/posts/index.html.php'); - $this->assertEqual($expected, $result); + $this->assertTrue($result); } } diff --git a/libraries/lithium/tests/cases/console/command/g11n/ExtractTest.php b/libraries/lithium/tests/cases/console/command/g11n/ExtractTest.php index 80d26f9..d071719 100644 --- a/libraries/lithium/tests/cases/console/command/g11n/ExtractTest.php +++ b/libraries/lithium/tests/cases/console/command/g11n/ExtractTest.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\console\command\g11n; +use lithium\core\Libraries; use lithium\console\Request; use lithium\console\command\g11n\Extract; @@ -18,7 +19,7 @@ class ExtractTest extends \lithium\test\Unit { public $command; public function skip() { - $this->_path = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_path = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_path), "{$this->_path} is not writable."); } @@ -38,7 +39,7 @@ class ExtractTest extends \lithium\test\Unit { public function testInit() { $command = new Extract(); $this->assertEqual(LITHIUM_APP_PATH, $command->source); - $this->assertEqual(LITHIUM_APP_PATH . '/resources/g11n', $command->destination); + $this->assertEqual(Libraries::get(true, 'resources') . '/g11n', $command->destination); } public function testFailRead() { diff --git a/libraries/lithium/tests/cases/core/AdaptableTest.php b/libraries/lithium/tests/cases/core/AdaptableTest.php index f2bf019..ac0f827 100644 --- a/libraries/lithium/tests/cases/core/AdaptableTest.php +++ b/libraries/lithium/tests/cases/core/AdaptableTest.php @@ -2,18 +2,20 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\core; +use SplDoublyLinkedList; use lithium\util\Collection; use lithium\core\Adaptable; use lithium\storage\cache\adapter\Memory; use lithium\tests\mocks\core\MockAdapter; use lithium\tests\mocks\core\MockStrategy; -use \SplDoublyLinkedList; +use lithium\tests\mocks\storage\cache\strategy\MockSerializer; +use lithium\tests\mocks\storage\cache\strategy\MockConfigurizer; class AdaptableTest extends \lithium\test\Unit { @@ -25,7 +27,7 @@ class AdaptableTest extends \lithium\test\Unit { $this->assertFalse($this->adaptable->config()); $items = array(array( - 'adapter' => '\some\adapter', + 'adapter' => 'some\adapter', 'filters' => array('filter1', 'filter2') )); $result = $this->adaptable->config($items); @@ -36,7 +38,7 @@ class AdaptableTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); $items = array(array( - 'adapter' => '\some\adapter', + 'adapter' => 'some\adapter', 'filters' => array('filter1', 'filter2') )); $this->adaptable->config($items); @@ -62,17 +64,13 @@ class AdaptableTest extends \lithium\test\Unit { public function testNonExistentConfig() { $adapter = new MockAdapter(); - $this->expectException("Configuration 'non_existent_config' has not been defined."); - $result = $adapter::adapter('non_existent_config'); - $this->assertNull($result); + $this->expectException("Configuration `non_existent_config` has not been defined."); + $adapter::adapter('non_existent_config'); } public function testAdapter() { $adapter = new MockAdapter(); - $items = array('default' => array( - 'adapter' => 'Memory', - 'filters' => array() - )); + $items = array('default' => array('adapter' => 'Memory', 'filters' => array())); $adapter::config($items); $result = $adapter::config(); $expected = $items; @@ -85,10 +83,7 @@ class AdaptableTest extends \lithium\test\Unit { public function testConfigAndAdapter() { $adapter = new MockAdapter(); - $items = array('default' => array( - 'adapter' => 'Memory', - 'filters' => array() - )); + $items = array('default' => array('adapter' => 'Memory', 'filters' => array())); $adapter::config($items); $config = $adapter::config(); @@ -108,7 +103,7 @@ class AdaptableTest extends \lithium\test\Unit { public function testStrategy() { $strategy = new MockStrategy(); $items = array('default' => array( - 'strategies' => array('\lithium\tests\mocks\storage\cache\strategy\MockSerializer'), + 'strategies' => array('lithium\tests\mocks\storage\cache\strategy\MockSerializer'), 'filters' => array(), 'adapter' => null )); @@ -120,9 +115,7 @@ class AdaptableTest extends \lithium\test\Unit { $result = $strategy::strategies('default'); $this->assertTrue($result instanceof SplDoublyLinkedList); $this->assertEqual(count($result), 1); - $this->assertTrue( - $result->top() instanceof \lithium\tests\mocks\storage\cache\strategy\MockSerializer - ); + $this->assertTrue($result->top() instanceof MockSerializer); } public function testInvalidStrategy() { @@ -135,7 +128,7 @@ class AdaptableTest extends \lithium\test\Unit { $strategy::config($items); $class = 'lithium\tests\mocks\core\MockStrategy'; - $message = "Could not find strategy 'InvalidStrategy' in class {$class}."; + $message = "Could not find strategy `InvalidStrategy` in class `{$class}`."; $this->expectException($message); $result = $strategy::strategies('default'); @@ -146,7 +139,7 @@ class AdaptableTest extends \lithium\test\Unit { $strategy = new MockStrategy(); $items = array('default' => array( 'strategies' => array( - '\lithium\tests\mocks\storage\cache\strategy\MockConfigurizer' => array( + 'lithium\tests\mocks\storage\cache\strategy\MockConfigurizer' => array( 'key1' => 'value1', 'key2' => 'value2' ) ), @@ -161,21 +154,19 @@ class AdaptableTest extends \lithium\test\Unit { $result = $strategy::strategies('default'); $this->assertTrue($result instanceof SplDoublyLinkedList); $this->assertEqual(count($result), 1); - $this->assertTrue( - $result->top() instanceof \lithium\tests\mocks\storage\cache\strategy\MockConfigurizer - ); + $this->assertTrue($result->top() instanceof MockConfigurizer); } public function testNonExistentStrategyConfiguration() { $strategy = new MockStrategy(); - $this->expectException("Configuration 'non_existent_config' has not been defined."); + $this->expectException("Configuration `non_existent_config` has not been defined."); $result = $strategy::strategies('non_existent_config'); $this->assertNull($result); } public function testApplyStrategiesNonExistentConfiguration() { $strategy = new MockStrategy(); - $this->expectException("Configuration 'non_existent_config' has not been defined."); + $this->expectException("Configuration `non_existent_config` has not been defined."); $strategy::applyStrategies('method', 'non_existent_config', null); } @@ -184,7 +175,7 @@ class AdaptableTest extends \lithium\test\Unit { $items = array('default' => array( 'filters' => array(), 'adapter' => null, - 'strategies' => array('\lithium\tests\mocks\storage\cache\strategy\MockSerializer'), + 'strategies' => array('lithium\tests\mocks\storage\cache\strategy\MockSerializer'), )); $strategy::config($items); $result = $strategy::config(); @@ -203,7 +194,7 @@ class AdaptableTest extends \lithium\test\Unit { 'filters' => array(), 'adapter' => null, 'strategies' => array( - '\lithium\tests\mocks\storage\cache\strategy\MockConfigurizer' => $params + 'lithium\tests\mocks\storage\cache\strategy\MockConfigurizer' => $params ) )); $strategy::config($items); @@ -221,8 +212,7 @@ class AdaptableTest extends \lithium\test\Unit { 'filters' => array(), 'adapter' => null, 'strategies' => array( - '\lithium\tests\mocks\storage\cache\strategy\MockSerializer', - 'Base64' + 'lithium\tests\mocks\storage\cache\strategy\MockSerializer', 'Base64' ) )); $strategy::config($items); @@ -274,10 +264,7 @@ class AdaptableTest extends \lithium\test\Unit { public function testEnabled() { $adapter = new MockAdapter(); - $items = array('default' => array( - 'adapter' => 'Memory', - 'filters' => array() - )); + $items = array('default' => array('adapter' => 'Memory', 'filters' => array())); $adapter::config($items); $result = $adapter::config(); $expected = $items; @@ -294,16 +281,14 @@ class AdaptableTest extends \lithium\test\Unit { public function testNonExistentAdapter() { $adapter = new MockAdapter(); - $items = array('default' => array( - 'adapter' => 'NonExistent', 'filters' => array() - )); + $items = array('default' => array('adapter' => 'NonExistent', 'filters' => array())); $adapter::config($items); $result = $adapter::config(); $expected = $items; $this->assertEqual($expected, $result); - $message = 'Could not find adapter \'NonExistent\' in '; - $message .= 'class lithium\tests\mocks\core\MockAdapter.'; + $message = 'Could not find adapter `NonExistent` in '; + $message .= 'class `lithium\tests\mocks\core\MockAdapter`.'; $this->expectException($message); $result = $adapter::adapter('default'); @@ -336,7 +321,7 @@ class AdaptableTest extends \lithium\test\Unit { $adapter::config($items); $message = 'No adapter set for configuration in '; - $message .= 'class lithium\tests\mocks\core\MockAdapter.'; + $message .= 'class `lithium\tests\mocks\core\MockAdapter`.'; $this->expectException($message); $result = $adapter::adapter('default'); } diff --git a/libraries/lithium/tests/cases/core/EnvironmentTest.php b/libraries/lithium/tests/cases/core/EnvironmentTest.php index 984a08a..064f05c 100644 --- a/libraries/lithium/tests/cases/core/EnvironmentTest.php +++ b/libraries/lithium/tests/cases/core/EnvironmentTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/core/ErrorHandlerTest.php b/libraries/lithium/tests/cases/core/ErrorHandlerTest.php index b8df0c9..bd37732 100644 --- a/libraries/lithium/tests/cases/core/ErrorHandlerTest.php +++ b/libraries/lithium/tests/cases/core/ErrorHandlerTest.php @@ -8,9 +8,9 @@ namespace lithium\tests\cases\core; -use \Closure; -use \Exception; -use \UnexpectedValueException; +use Closure; +use Exception; +use UnexpectedValueException; use lithium\core\ErrorHandler; class ErrorHandlerTest extends \lithium\test\Unit { @@ -69,6 +69,8 @@ class ErrorHandlerTest extends \lithium\test\Unit { } public function testErrorCatching() { + $this->skipIf(true, 'Refactoring original error-handling iteration.'); + $self = $this; ErrorHandler::config(array(array( 'code' => E_WARNING | E_USER_WARNING, @@ -105,6 +107,45 @@ class ErrorHandlerTest extends \lithium\test\Unit { ErrorHandler::reset(); $this->assertEqual(array(), ErrorHandler::handlers()); } + + public function testApply() { + $subject = new ErrorHandlerTest(); + ErrorHandler::apply(array($subject, 'throwException'), array(), function($details) { + return $details['exception']->getMessage(); + }); + $this->assertEqual('foo', $subject->throwException()); + } + + public function throwException() { + return $this->_filter(__METHOD__, array(), function($self, $params) { + throw new Exception('foo'); + return 'bar'; + }); + } + + public function testTrace() { + $current = debug_backtrace(); + $results = ErrorHandler::trace($current); + $this->assertEqual(count($current), count($results)); + $this->assertEqual($results[0], 'lithium\tests\cases\core\ErrorHandlerTest::testTrace'); + } + + public function testRun() { + ErrorHandler::stop(); + $this->assertEqual(ErrorHandler::isRunning(), false); + ErrorHandler::run(); + $this->assertEqual(ErrorHandler::isRunning(), true); + ErrorHandler::stop(); + $this->assertEqual(ErrorHandler::isRunning(), false); + } + + public function testErrorTrapping() { + ErrorHandler::stop(); + ErrorHandler::run(array('trapErrors' => true)); + + // Undefined offset error shouldn't surface. + list($foo, $bar) = array('baz'); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/core/LibrariesTest.php b/libraries/lithium/tests/cases/core/LibrariesTest.php index 902e210..4e80613 100644 --- a/libraries/lithium/tests/cases/core/LibrariesTest.php +++ b/libraries/lithium/tests/cases/core/LibrariesTest.php @@ -2,23 +2,41 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\core; -use \SplFileInfo; +use stdClass; +use SplFileInfo; use lithium\util\Inflector; use lithium\core\Libraries; class LibrariesTest extends \lithium\test\Unit { + protected $_cache = array(); + + public function setUp() { + $this->_cache = Libraries::cache(); + Libraries::cache(false); + } + + public function tearDown() { + Libraries::cache(false); + Libraries::cache($this->_cache); + } + public function testNamespaceToFileTranslation() { $result = Libraries::path('\lithium\core\Libraries'); $this->assertTrue(strpos($result, '/lithium/core/Libraries.php')); $this->assertTrue(file_exists($result)); $this->assertFalse(strpos($result, '\\')); + + $result = Libraries::path('lithium\core\Libraries'); + $this->assertTrue(strpos($result, '/lithium/core/Libraries.php')); + $this->assertTrue(file_exists($result)); + $this->assertFalse(strpos($result, '\\')); } public function testPathTemplate() { @@ -134,7 +152,7 @@ class LibrariesTest extends \lithium\test\Unit { * @return void */ public function testAddInvalidLibrary() { - $this->expectException("Library 'invalid_foo' not found."); + $this->expectException("Library `invalid_foo` not found."); Libraries::add('invalid_foo'); } @@ -144,7 +162,7 @@ class LibrariesTest extends \lithium\test\Unit { * @return void */ public function testAddNonPrefixedLibrary() { - $tmpDir = realpath(LITHIUM_APP_PATH . '/resources/tmp'); + $tmpDir = realpath(Libraries::get(true, 'resources') . '/tmp'); $this->skipIf(!is_writable($tmpDir), "Can't write to resources directory."); $fakeDir = $tmpDir . '/fake'; @@ -211,7 +229,7 @@ class LibrariesTest extends \lithium\test\Unit { * @return void */ public function testLibraryLoad() { - $this->expectException('Failed to load SomeInvalidLibrary from '); + $this->expectException('Failed to load class `SomeInvalidLibrary` from path ``.'); Libraries::load('SomeInvalidLibrary', true); } @@ -316,19 +334,12 @@ class LibrariesTest extends \lithium\test\Unit { $this->assertTrue(in_array('lithium\test\filter\Complexity', $result)); $this->assertTrue(in_array('lithium\test\filter\Coverage', $result)); $this->assertTrue(in_array('lithium\test\filter\Profiler', $result)); - - foreach (Libraries::paths() as $type => $paths) { - if (count($paths) <= 1 || $type == 'libraries') { - continue; - } - $this->assertTrue(count(Libraries::locate($type)) > 1); - } } public function testServiceLocateInstantiation() { $result = Libraries::instance('adapter.template.view', 'Simple'); $this->assertTrue(is_a($result, 'lithium\template\view\adapter\Simple')); - $this->expectException("Class 'Foo' of type 'adapter.template.view' not found."); + $this->expectException("Class `Foo` of type `adapter.template.view` not found."); $result = Libraries::instance('adapter.template.view', 'Foo'); } @@ -371,7 +382,7 @@ class LibrariesTest extends \lithium\test\Unit { $expected = '\lithium\data\source\Database'; $this->assertEqual($expected, $result); - $expected = new \stdClass(); + $expected = new stdClass(); $result = Libraries::locate(null, $expected); $this->assertEqual($expected, $result); } @@ -481,12 +492,92 @@ class LibrariesTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); } - public function testLocatePreFilter() { - $result = Libraries::locate('command', null, array('recursive' => false)); - $this->assertFalse(preg_grep('/\.txt/', $result)); + public function testLocateCommandInLithium() { + $expected = array( + 'lithium\console\command\Create', + 'lithium\console\command\G11n', + 'lithium\console\command\Help', + 'lithium\console\command\Library', + 'lithium\console\command\Route', + 'lithium\console\command\Test' + ); + $result = Libraries::locate('command', null, array( + 'library' => 'lithium', 'recursive' => false + )); + $this->assertEqual($expected, $result); + } + + public function testLocateCommandInLithiumRecursiveTrue() { + $expected = array( + 'lithium\console\command\Create', + 'lithium\console\command\G11n', + 'lithium\console\command\Help', + 'lithium\console\command\Library', + 'lithium\console\command\Route', + 'lithium\console\command\Test', + 'lithium\console\command\g11n\Extract', + 'lithium\console\command\create\Controller', + 'lithium\console\command\create\Mock', + 'lithium\console\command\create\Model', + 'lithium\console\command\create\Test', + 'lithium\console\command\create\View' + ); + $result = Libraries::locate('command', null, array( + 'library' => 'lithium', 'recursive' => true + )); + $this->assertEqual($expected, $result); + } + + public function testLocateWithLibrary() { + $expected = array(); + $result = (array) Libraries::locate("tests", null, array('library' => 'doesntExist')); + $this->assertIdentical($expected, $result); + } + + public function testLocateWithLithiumLibrary() { + $expected = (array) Libraries::find('lithium', array( + 'path' => '/tests', + 'preFilter' => '/[A-Z][A-Za-z0-9]+\Test\./', + 'recursive' => true, + 'filter' => '/cases|integration|functional|mocks/', + )); + $result = (array) Libraries::locate("tests", null, array('library' => 'lithium')); + $this->assertEqual($expected, $result); + } + + public function testLocateWithTestAppLibrary() { + $testApp = Libraries::get(true, 'resources') . '/tmp/tests/test_app'; + mkdir($testApp); + Libraries::add('test_app', array('path' => $testApp)); + + mkdir($testApp . '/tests/cases/models', 0777, true); + file_put_contents($testApp . '/tests/cases/models/UserTest.php', + "<?php namespace test_app\\tests\\cases\\models;\n + class UserTest extends \\lithium\\test\\Unit { public function testMe() { + \$this->assertTrue(true); + }}" + ); + Libraries::cache(false); + + $expected = array('test_app\\tests\\cases\\models\\UserTest'); + $result = (array) Libraries::locate("tests", null, array('library' => 'test_app')); + $this->assertEqual($expected, $result); + + $this->_cleanUp(); + } + + /** + * Tests that `Libraries::realPath()` correctly resolves paths to files inside Phar archives. + * + * @return void + */ + public function testPathsInPharArchives() { + $base = Libraries::get('lithium', 'path'); + $path = "{$base}/console/command/create/template/app.phar.gz"; - $result = Libraries::locate('command', null, array('recursive' => true)); - $this->assertFalse(preg_grep('/\.txt/', $result)); + $expected = "phar://{$path}/controllers/HelloWorldController.php"; + $result = Libraries::realPath($expected); + $this->assertEqual($expected, $result); } } diff --git a/libraries/lithium/tests/cases/core/ObjectTest.php b/libraries/lithium/tests/cases/core/ObjectTest.php index bc80b7f..954dd4b 100644 --- a/libraries/lithium/tests/cases/core/ObjectTest.php +++ b/libraries/lithium/tests/cases/core/ObjectTest.php @@ -2,13 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\core; use lithium\core\Object; +use lithium\tests\mocks\core\MockRequest; use lithium\tests\mocks\core\MockMethodFiltering; use lithium\tests\mocks\core\MockExposed; use lithium\tests\mocks\core\MockCallable; @@ -193,7 +194,7 @@ class ObjectTest extends \lithium\test\Unit { public function testInstanceWithObject() { $object = new MockInstantiator(); - $request = new \lithium\tests\mocks\core\MockRequest(); + $request = new MockRequest(); $expected = 'lithium\tests\mocks\core\MockRequest'; $result = get_class($object->instance($request)); $this->assertEqual($expected, $result); @@ -201,8 +202,8 @@ class ObjectTest extends \lithium\test\Unit { public function testInstanceFalse() { $object = new MockInstantiator(); - $result = $object->instance(false); - $this->assertFalse($result); + $this->expectException('/^Invalid class lookup/'); + $object->instance(false); } } diff --git a/libraries/lithium/tests/cases/core/StaticObjectTest.php b/libraries/lithium/tests/cases/core/StaticObjectTest.php index a6c6fb9..18cca3e 100644 --- a/libraries/lithium/tests/cases/core/StaticObjectTest.php +++ b/libraries/lithium/tests/cases/core/StaticObjectTest.php @@ -2,13 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\core; use lithium\core\StaticObject; +use lithium\tests\mocks\core\MockRequest; use lithium\tests\mocks\core\MockStaticInstantiator; class StaticObjectTest extends \lithium\test\Unit { @@ -151,15 +152,15 @@ class StaticObjectTest extends \lithium\test\Unit { } public function testInstanceWithObject() { - $request = new \lithium\tests\mocks\core\MockRequest(); + $request = new MockRequest(); $expected = 'lithium\tests\mocks\core\MockRequest'; $result = get_class(MockStaticInstantiator::instance($request)); $this->assertEqual($expected, $result); } public function testInstanceFalse() { - $result = MockStaticInstantiator::instance(false); - $this->assertFalse($result); + $this->expectException('/^Invalid class lookup/'); + MockStaticInstantiator::instance(false); } } diff --git a/libraries/lithium/tests/cases/data/CollectionTest.php b/libraries/lithium/tests/cases/data/CollectionTest.php index 748f82d..d4ced38 100644 --- a/libraries/lithium/tests/cases/data/CollectionTest.php +++ b/libraries/lithium/tests/cases/data/CollectionTest.php @@ -2,9 +2,8 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License - * */ namespace lithium\tests\cases\data; @@ -34,6 +33,76 @@ class CollectionTest extends \lithium\test\Unit { $this->assertEqual($model, $collection->model()); $this->assertEqual(compact('model'), $collection->meta()); } + + public function testOffsetExists() { + $collection = new DocumentSet(); + $this->assertEqual($collection->offsetExists(0), false); + $collection->set(array('foo' => 'bar', 'bas' => 'baz')); + $this->assertEqual($collection->offsetExists(0), true); + $this->assertEqual($collection->offsetExists(1), true); + } + + public function testNextRewindCurrent() { + $collection = new DocumentSet(); + $collection->set(array( + 'title' => 'Lorem Ipsum', + 'value' => 42, + 'foo' => 'bar' + )); + $this->assertEqual('Lorem Ipsum', $collection->current()); + $this->assertEqual(42, $collection->next()); + $this->assertEqual('bar', $collection->next()); + $this->assertEqual('Lorem Ipsum', $collection->rewind()); + $this->assertEqual(42, $collection->next()); + } + + public function testEach() { + $collection = new DocumentSet(); + $collection->set(array( + 'title' => 'Lorem Ipsum', + 'key' => 'value', + 'foo' => 'bar' + )); + $collection->each(function($value) { + return $value . ' test'; + }); + $expected = array( + 'Lorem Ipsum test', + 'value test', + 'bar test' + ); + $this->assertEqual($collection->to('array'), $expected); + } + + public function testMap() { + $collection = new DocumentSet(); + $collection->set(array( + 'title' => 'Lorem Ipsum', + 'key' => 'value', + 'foo' => 'bar' + )); + $results = $collection->map(function($value) { + return $value . ' test'; + }); + $expected = array( + 'Lorem Ipsum test', + 'value test', + 'bar test' + ); + $this->assertEqual($results->to('array'), $expected); + $this->assertNotEqual($results->to('array'), $collection->to('array')); + } + + public function testData() { + $collection = new DocumentSet(); + $data = array( + 'Lorem Ipsum', + 'value', + 'bar' + ); + $collection->set($data); + $this->assertEqual($data, $collection->data()); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/ConnectionsTest.php b/libraries/lithium/tests/cases/data/ConnectionsTest.php index 43db8af..3830b27 100644 --- a/libraries/lithium/tests/cases/data/ConnectionsTest.php +++ b/libraries/lithium/tests/cases/data/ConnectionsTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -47,8 +47,8 @@ class ConnectionsTest extends \lithium\test\Unit { $expected = $this->config + array('type' => 'database'); $this->assertEqual($expected, $result); - $message = 'Your PHP was not compiled with the MySQL extension'; - $this->skipIf(!extension_loaded('mysql'), $message); + $this->skipIf(!MySql::enabled(), 'MySql is not enabled'); + $this->skipIf(!$this->_canConnect('localhost', 3306), 'Cannot connect to localhost:3306'); $this->expectException('/mysql_get_server_info/'); $this->expectException('/mysql_select_db/'); @@ -71,8 +71,8 @@ class ConnectionsTest extends \lithium\test\Unit { Connections::add('conn-test-2', $this->config); $this->assertEqual(array('conn-test', 'conn-test-2'), Connections::get()); - $message = 'Your PHP was not compiled with the MySQL extension'; - $this->skipIf(!extension_loaded('mysql'), $message); + $this->skipIf(!MySql::enabled(), 'MySql is not enabled'); + $this->skipIf(!$this->_canConnect('localhost', 3306), 'Cannot connect to localhost:3306'); $expected = $this->config + array('type' => 'database', 'filters' => array()); $this->assertEqual($expected, Connections::get('conn-test', array('config' => true))); @@ -87,8 +87,8 @@ class ConnectionsTest extends \lithium\test\Unit { Connections::add('conn-test', $this->config); Connections::add('conn-test-2', $this->config); - $message = 'Your PHP was not compiled with the MySQL extension'; - $this->skipIf(!extension_loaded('mysql'), $message); + $this->skipIf(!MySql::enabled(), 'MySql is not enabled'); + $this->skipIf(!$this->_canConnect('localhost', 3306), 'Cannot connect to localhost:3306'); $this->expectException('/mysql_get_server_info/'); $this->expectException('/mysql_select_db/'); @@ -142,6 +142,20 @@ class ConnectionsTest extends \lithium\test\Unit { Connections::reset(); $this->assertTrue(Connections::get(false) instanceof Mock); } + + protected function _canConnect($host, $port) { + $this->expectException(); + $this->expectException(); + + if ($conn = fsockopen($host, $port)) { + array_pop($this->_expected); + array_pop($this->_expected); + fclose($conn); + + return true; + } + return false; + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/EntityTest.php b/libraries/lithium/tests/cases/data/EntityTest.php new file mode 100644 index 0000000..35ec22a --- /dev/null +++ b/libraries/lithium/tests/cases/data/EntityTest.php @@ -0,0 +1,86 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\cases\data; + +use lithium\data\Entity; + +class EntityTest extends \lithium\test\Unit { + + protected $_model = 'lithium\tests\mocks\data\source\MockMongoPost'; + + public function testSchemaAccess() { + $schema = array('foo' => array('type' => 'string')); + $entity = new Entity(compact('schema')); + $this->assertEqual($schema, $entity->schema()); + } + + public function testPropertyAccess() { + $entity = new Entity(array('model' => 'Foo', 'exists' => false)); + $this->assertEqual('Foo', $entity->model()); + $this->assertFalse($entity->exists()); + + $entity = new Entity(array('exists' => true)); + $this->assertTrue($entity->exists()); + + $expected = array( + 'exists' => true, 'data' => array(), 'update' => array(), 'increment' => array() + ); + $this->assertEqual($expected, $entity->export()); + } + + public function testIncrement() { + $entity = new Entity(array('data' => array('counter' => 0))); + $this->assertEqual(0, $entity->counter); + + $entity->increment('counter'); + $this->assertEqual(1, $entity->counter); + + $entity->decrement('counter', 5); + $this->assertEqual(-4, $entity->counter); + + $this->assertNull($entity->increment); + $entity->increment('foo'); + $this->assertEqual(1, $entity->foo); + + $this->assertFalse(isset($entity->bar)); + $entity->bar = 'blah'; + $entity->update(); + + $this->expectException("/^Field 'bar' cannot be incremented.$/"); + $entity->increment('bar'); + } + + public function testMethodDispatch() { + $entity = new Entity(array('model' => $this->_model, 'data' => array('foo' => true))); + $this->assertTrue($entity->validates()); + $this->expectException("/^No model bound or unhandled method call `foo`.$/"); + $entity->foo(); + } + + public function testErrors() { + $entity = new Entity(); + $errors = array('foo' => 'Something bad happened.'); + $this->assertEqual(array(), $entity->errors()); + + $entity->errors($errors); + $this->assertEqual($errors, $entity->errors()); + $this->assertEqual('Something bad happened.', $entity->errors('foo')); + } + + public function testConversion() { + $data = array('foo' => '!!', 'bar' => '??', 'baz' => '--'); + $entity = new Entity(compact('data')); + + $this->assertEqual($data, $entity->to('array')); + $this->assertEqual($data, $entity->data()); + $this->assertEqual($entity, $entity->to('foo')); + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/ModelTest.php b/libraries/lithium/tests/cases/data/ModelTest.php index cb1feb8..636e2f1 100644 --- a/libraries/lithium/tests/cases/data/ModelTest.php +++ b/libraries/lithium/tests/cases/data/ModelTest.php @@ -2,27 +2,38 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\data; use lithium\data\Model; +use lithium\data\Entity; use lithium\data\model\Query; use lithium\data\Connections; use lithium\analysis\Inspector; +use lithium\data\entity\Record; use lithium\tests\mocks\data\MockTag; use lithium\tests\mocks\data\MockPost; +use lithium\tests\mocks\data\MockSource; use lithium\tests\mocks\data\MockComment; use lithium\tests\mocks\data\MockTagging; use lithium\tests\mocks\data\MockCreator; use lithium\tests\mocks\data\MockPostForValidates; +use lithium\tests\mocks\data\source\MockMongoConnection; class ModelTest extends \lithium\test\Unit { protected $_configs = array(); + protected $_altSchema = array( + 'id' => array('type' => 'integer'), + 'author_id' => array('type' => 'integer'), + 'title' => array('type' => 'string'), + 'body' => array('type' => 'text') + ); + public function setUp() { $this->_configs = Connections::config(); Connections::config(array('mock-source' => array( @@ -160,7 +171,7 @@ class ModelTest extends \lithium\test\Unit { 'link' => 'key', 'fields' => true, 'fieldName' => 'mockPost', - 'conditions' => null, + 'constraint' => array(), 'init' => true ); $this->assertEqual($expected, MockComment::relations('MockPost')->data()); @@ -174,7 +185,7 @@ class ModelTest extends \lithium\test\Unit { 'keys' => array('mock_post_id' => 'id'), 'link' => 'key', 'fieldName' => 'mockComment', - 'conditions' => null, + 'constraint' => array(), 'init' => true ); $this->assertEqual($expected, MockPost::relations('MockComment')->data()); @@ -218,7 +229,7 @@ class ModelTest extends \lithium\test\Unit { $this->assertEqual(array('foo' => 13), $result['query']->conditions()); $this->assertEqual(array('created_at' => 'desc'), $result['query']->order()); - $this->expectException('/Method findFoo not defined or handled in class/'); + $this->expectException('/Method `findFoo` not defined or handled in class/'); MockPost::findFoo(); } @@ -229,7 +240,7 @@ class ModelTest extends \lithium\test\Unit { */ public function testSimpleFindFirst() { $result = MockComment::first(); - $this->assertTrue($result instanceof \lithium\data\entity\Record); + $this->assertTrue($result instanceof Record); $expected = 'First comment'; $this->assertEqual($expected, $result->text); @@ -244,6 +255,7 @@ class ModelTest extends \lithium\test\Unit { public function testFilteredFind() { MockComment::applyFilter('find', function($self, $params, $chain) { $result = $chain->next($self, $params, $chain); + if ($result != null) { $result->filtered = true; } @@ -396,25 +408,23 @@ class ModelTest extends \lithium\test\Unit { $this->assertEqual('mock-source', MockPost::meta('connection')); MockPost::config(array('connection' => false)); $this->assertFalse(MockPost::meta('connection')); + $schema = MockPost::schema(); - $schema = array( - 'id' => array('type' => 'integer'), - 'author_id' => array('type' => 'integer'), - 'title' => array('type' => 'string'), - 'body' => array('type' => 'text') - ); - MockPost::overrideSchema($schema); - $this->assertEqual($schema, MockPost::schema()); + MockPost::overrideSchema($this->_altSchema); + $this->assertEqual($this->_altSchema, MockPost::schema()); $post = MockPost::create(array('title' => 'New post')); - $this->assertTrue($post instanceof \lithium\data\Entity); + $this->assertTrue($post instanceof Entity); $this->assertEqual('New post', $post->title); + MockPost::overrideSchema($schema); $this->expectException('/Connection name not defined/'); $post->save(); } public function testSave() { + $schema = MockPost::schema(); + MockPost::overrideSchema($this->_altSchema); $data = array('title' => 'New post', 'author_id' => 13); $record = MockPost::create($data); $result = $record->save(); @@ -422,6 +432,20 @@ class ModelTest extends \lithium\test\Unit { $this->assertEqual('create', $result['query']->type()); $this->assertEqual($data, $result['query']->data()); $this->assertEqual('lithium\tests\mocks\data\MockPost', $result['query']->model()); + MockPost::overrideSchema($schema); + } + + public function testSaveWithNoCallbacks() { + $schema = MockPost::schema(); + MockPost::overrideSchema($this->_altSchema); + $data = array('title' => 'New post', 'author_id' => 13); + $record = MockPost::create($data); + $result = $record->save(null, array('callbacks' => false)); + + $this->assertEqual('create', $result['query']->type()); + $this->assertEqual($data, $result['query']->data()); + $this->assertEqual('lithium\tests\mocks\data\MockPost', $result['query']->model()); + MockPost::overrideSchema($schema); } public function testSaveWithFailedValidation() { @@ -467,7 +491,7 @@ class ModelTest extends \lithium\test\Unit { $this->assertEqual(array('published' => false), $query->conditions()); $keys = array_keys(array_filter($query->export(Connections::get('mock-source')))); - $this->assertEqual(array('name', 'conditions', 'model', 'source', 'type'), $keys); + $this->assertEqual(array('type', 'name', 'conditions', 'model', 'source'), $keys); } public function testFindFirst() { @@ -501,6 +525,25 @@ class ModelTest extends \lithium\test\Unit { $result = MockPost::count(array('conditions' => array('email' => 'foo@example.com'))); $this->assertEqual($query, $result['query']); } + + public function testSettingNestedObjectDefaults() { + $this->skipIf(!MockMongoConnection::enabled(), 'MongoDb not enabled.'); + + MockPost::$connection = new MockMongoConnection(); + $schema = MockPost::schema(); + + MockPost::overrideSchema($schema + array('nested.value' => array( + 'type' => 'string', + 'default' => 'foo' + ))); + $this->assertEqual('foo', MockPost::create()->nested->value); + + $data = array('nested' => array('value' => 'bar')); + $this->assertEqual('bar', MockPost::create($data)->nested->value); + + MockPost::overrideSchema($schema); + MockPost::$connection = null; + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/SourceTest.php b/libraries/lithium/tests/cases/data/SourceTest.php index d5c1774..e8c6e3f 100644 --- a/libraries/lithium/tests/cases/data/SourceTest.php +++ b/libraries/lithium/tests/cases/data/SourceTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/data/collection/DocumentArrayTest.php b/libraries/lithium/tests/cases/data/collection/DocumentArrayTest.php new file mode 100644 index 0000000..0454b23 --- /dev/null +++ b/libraries/lithium/tests/cases/data/collection/DocumentArrayTest.php @@ -0,0 +1,39 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\cases\data\collection; + +use lithium\data\source\MongoDb; +use lithium\data\collection\DocumentArray; + +class DocumentArrayTest extends \lithium\test\Unit { + + protected $_model = 'lithium\tests\mocks\data\model\MockDocumentPost'; + + public function testInitialCasting() { + $array = new DocumentArray(array( + 'model' => $this->_model, + 'pathKey' => 'foo.bar', + 'data' => array('5', '6', '7') + )); + foreach ($array as $value) { + $this->assertTrue(is_int($value)); + } + } + + public function testExport() { + $array = new DocumentArray(array( + 'model' => $this->_model, + 'pathKey' => 'foo.bar', + 'data' => array('5', '6', '7') + )); + $array[] = 8; + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/collection/DocumentSetTest.php b/libraries/lithium/tests/cases/data/collection/DocumentSetTest.php index eb4a644..7fdf35a 100644 --- a/libraries/lithium/tests/cases/data/collection/DocumentSetTest.php +++ b/libraries/lithium/tests/cases/data/collection/DocumentSetTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,6 +10,8 @@ namespace lithium\tests\cases\data\collection; use stdClass; use lithium\data\Connections; +use lithium\data\source\MongoDb; +use lithium\data\source\http\adapter\CouchDb; use lithium\data\entity\Document; use lithium\data\collection\DocumentSet; use lithium\data\collection\DocumentArray; @@ -27,6 +29,11 @@ class DocumentSetTest extends \lithium\test\Unit { protected $_preserved = array(); + public function skip() { + $this->skipIf(!MongoDb::enabled(), 'MongoDb is not enabled'); + $this->skipIf(!CouchDb::enabled(), 'CouchDb is not enabled'); + } + public function setUp() { if (empty($this->_preserved)) { foreach (Connections::get() as $conn) { @@ -69,6 +76,17 @@ class DocumentSetTest extends \lithium\test\Unit { $this->assertNull($doc->next()); } + + public function testMappingToNewDocumentSet() { + $result = new MockResult(); + $model = $this->_model; + $doc = new DocumentSet(compact('model', 'result')); + + $mapped = $doc->map(function($data) { return $data; }); + $this->assertEqual($doc->data(), $mapped->data()); + $this->assertEqual($model, $doc->model()); + $this->assertEqual($model, $mapped->model()); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/collection/RecordSetTest.php b/libraries/lithium/tests/cases/data/collection/RecordSetTest.php index 11906df..83b2b4d 100644 --- a/libraries/lithium/tests/cases/data/collection/RecordSetTest.php +++ b/libraries/lithium/tests/cases/data/collection/RecordSetTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/data/entity/DocumentTest.php b/libraries/lithium/tests/cases/data/entity/DocumentTest.php index 86766a1..9e3e8f4 100644 --- a/libraries/lithium/tests/cases/data/entity/DocumentTest.php +++ b/libraries/lithium/tests/cases/data/entity/DocumentTest.php @@ -2,14 +2,17 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\data\entity; -use stdClass; +use MongoId; +use MongoDate; use lithium\data\Connections; +use lithium\data\source\MongoDb; +use lithium\data\source\http\adapter\CouchDb; use lithium\data\entity\Document; use lithium\data\collection\DocumentSet; use lithium\data\collection\DocumentArray; @@ -23,6 +26,11 @@ class DocumentTest extends \lithium\test\Unit { protected $_preserved = array(); + public function skip() { + $this->skipIf(!MongoDb::enabled(), 'MongoDb is not enabled'); + $this->skipIf(!CouchDb::enabled(), 'CouchDb is not enabled'); + } + public function setUp() { if (empty($this->_preserved)) { foreach (Connections::get() as $conn) { @@ -348,7 +356,7 @@ class DocumentTest extends \lithium\test\Unit { public function testInvalidCall() { $doc = new Document(); - $this->expectException("No model bound or unhandled method call 'medicin'."); + $this->expectException("No model bound or unhandled method call `medicin`."); $result = $doc->medicin(); $this->assertNull($result); } @@ -438,9 +446,8 @@ class DocumentTest extends \lithium\test\Unit { $result = $doc->data('title'); $this->assertEqual($expected, $result); - $expected = false; $result = $doc->data('permanent'); - $this->assertEqual($expected, $result); + $this->assertFalse($result); $doc = new Document(); $this->assertNull($doc->data('field')); @@ -467,8 +474,7 @@ class DocumentTest extends \lithium\test\Unit { unset($expected['title']); unset($doc->title); - $result = $doc->data(); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, $doc->data()); unset($expected['parsed']); unset($doc->parsed); @@ -532,6 +538,21 @@ class DocumentTest extends \lithium\test\Unit { } } + public function testExport() { + $data = array('foo' => 'bar', 'baz' => 'dib'); + $doc = new Document(compact('data') + array('exists' => false)); + + $expected = array( + 'data' => array('foo' => 'bar', 'baz' => 'dib'), + 'update' => array(), + 'remove' => array(), + 'increment' => array(), + 'key' => '', + 'exists' => false + ); + $this->assertEqual($expected, $doc->export()); + } + /** * Tests that a modified `Document` exports the proper fields in a newly-appended nested * `Document`. @@ -539,40 +560,106 @@ class DocumentTest extends \lithium\test\Unit { * @return void */ public function testModifiedExport() { - $database = new MockDocumentSource(); $model = $this->_model; $data = array('foo' => 'bar', 'baz' => 'dib'); - $doc = new Document(compact('model', 'data')); + $doc = new Document(compact('model', 'data') + array('exists' => false)); $doc->nested = array('more' => 'data'); - $newData = $doc->export($database); + $newData = $doc->export(); + + $expected = array('foo' => 'bar', 'baz' => 'dib', 'nested.more' => 'data'); + $this->assertFalse($newData['exists']); + $this->assertEqual(array('foo' => 'bar', 'baz' => 'dib'), $newData['data']); + $this->assertEqual(1, count($newData['update'])); + $this->assertTrue($newData['update']['nested'] instanceof Document); - $expected = array('foo' => 'bar', 'baz' => 'dib', 'nested' => array('more' => 'data')); - $this->assertEqual($expected, $newData); + $result = $newData['update']['nested']->export(); + $this->assertFalse($result['exists']); + $this->assertEqual(array('more' => 'data'), $result['data']); + $this->assertFalse($result['update']); + $this->assertEqual('nested', $result['key']); - $doc = new Document(array('model' => $model, 'exists' => true, 'data' => array( + $doc = new Document(compact('model') + array('exists' => true, 'data' => array( 'foo' => 'bar', 'baz' => 'dib' ))); - $this->assertFalse($doc->export($database)); + + $result = $doc->export(); + $this->assertFalse($result['update']); $doc->nested = array('more' => 'data'); $this->assertEqual('data', $doc->nested->more); - $modified = $doc->export($database); - $this->assertEqual(array('nested' => array('more' => 'data')), $modified); + $modified = $doc->export(); + $this->assertTrue($modified['exists']); + $this->assertEqual(array('foo' => 'bar', 'baz' => 'dib'), $modified['data']); + $this->assertEqual(array('nested'), array_keys($modified['update'])); + $this->assertNull($modified['key']); + + $nested = $modified['update']['nested']->export(); + $this->assertFalse($nested['exists']); + $this->assertEqual(array('more' => 'data'), $nested['data']); + $this->assertEqual('nested', $nested['key']); $doc->update(); - $this->assertFalse($doc->export($database)); + $result = $doc->export(); + $this->assertFalse($result['update']); $doc->more = 'cowbell'; $doc->nested->evenMore = 'cowbell'; - $modified = $doc->export($database); - $expected = array('nested' => array('evenMore' => 'cowbell'), 'more' => 'cowbell'); - $this->assertEqual($expected, $modified); + $modified = $doc->export(); + + $expected = array('more' => 'cowbell'); + $this->assertEqual($expected, $modified['update']); + $this->assertEqual(array('nested', 'foo', 'baz'), array_keys($modified['data'])); + $this->assertEqual('bar', $modified['data']['foo']); + $this->assertEqual('dib', $modified['data']['baz']); + + $nested = $modified['data']['nested']->export(); + $this->assertTrue($nested['exists']); + $this->assertEqual(array('more' => 'data'), $nested['data']); + $this->assertEqual(array('evenMore' => 'cowbell'), $nested['update']); + $this->assertEqual('nested', $nested['key']); $doc->update(); $doc->nested->evenMore = 'foo!'; - $this->assertEqual(array('nested' => array('evenMore' => 'foo!')), $doc->export($database)); + $modified = $doc->export(); + $this->assertFalse($modified['update']); + + $nested = $modified['data']['nested']->export(); + $this->assertEqual(array('evenMore' => 'foo!'), $nested['update']); + } + + public function testArrayConversion() { + $doc = new Document(array('data' => array( + 'id' => new MongoId(), + 'date' => new MongoDate() + ))); + $result = $doc->data(); + $this->assertPattern('/^[a-f0-9]{24}$/', $result['id']); + $this->assertEqual(time(), $result['date']); + } + + public function testInitializationWithNestedFields() { + $doc = new Document(array('model' => $this->_model, 'data' => array( + 'simple' => 'value', + 'nested.foo' => 'first', + 'nested.bar' => 'second', + 'really.nested.key' => 'value' + ))); + $this->assertEqual('value', $doc->simple); + $this->assertEqual('first', $doc->nested->foo); + $this->assertEqual('second', $doc->nested->bar); + $this->assertEqual('value', $doc->really->nested->key); + $this->assertEqual(array('simple', 'nested', 'really'), array_keys($doc->data())); + } + + public function testIdGetDoesNotSet() { + $document = MockDocumentPost::create(); + $message = 'The `_id` key should not be set.'; + $this->assertFalse(array_key_exists('_id', $document->data()), $message); + + $document->_id == ""; + $this->assertFalse(array_key_exists('_id', $document->data()), $message); } } diff --git a/libraries/lithium/tests/cases/data/entity/RecordTest.php b/libraries/lithium/tests/cases/data/entity/RecordTest.php index f29d450..a3fba17 100644 --- a/libraries/lithium/tests/cases/data/entity/RecordTest.php +++ b/libraries/lithium/tests/cases/data/entity/RecordTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -110,6 +110,7 @@ class RecordTest extends \lithium\test\Unit { $this->assertFalse($this->record->data()); $expected = array('id' => 1, 'name' => 'Joe Bloggs', 'address' => 'The Park'); $this->record->set($expected); + $this->assertEqual($expected, $this->record->data()); $this->assertEqual($expected, $this->record->to('array')); $this->assertEqual($expected['name'], $this->record->data('name')); } @@ -129,7 +130,7 @@ class RecordTest extends \lithium\test\Unit { $this->assertEqual('create', $result['query']->type()); $this->assertEqual(array('title' => 'foo'), $result['query']->data()); - $this->expectException("No model bound or unhandled method call 'invalid'."); + $this->expectException("No model bound or unhandled method call `invalid`."); $this->assertNull($this->record->invalid()); } } diff --git a/libraries/lithium/tests/cases/data/model/QueryTest.php b/libraries/lithium/tests/cases/data/model/QueryTest.php index 87d3f3e..d10cdb5 100644 --- a/libraries/lithium/tests/cases/data/model/QueryTest.php +++ b/libraries/lithium/tests/cases/data/model/QueryTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -244,6 +244,7 @@ class QueryTest extends \lithium\test\Unit { } public function testExport() { + MockQueryPost::meta('source', 'foo'); $query = new Query($this->_queryArr); $ds = new MockDatabase(); $export = $query->export($ds); @@ -281,9 +282,28 @@ class QueryTest extends \lithium\test\Unit { $result = $export['fields']; $this->assertEqual($expected, $result); - $expected = MockQueryPost::meta('source'); $result = $export['source']; - $this->assertEqual("{{$expected}}", $result); + $this->assertEqual("{foo}", $result); + } + + public function testRestrictedKeyExport() { + $options = array( + 'type' => 'update', + 'data' => array('title' => 'Bar'), + 'conditions' => array('title' => 'Foo'), + 'model' => $this->_model, + ); + $query = new Query($options); + + $result = $query->export(Connections::get('mock-database-connection'), array( + 'keys' => array('data', 'conditions') + )); + $expected = array( + 'type' => 'update', + 'data' => array('title' => 'Bar'), + 'conditions' => "WHERE {title} = 'Foo'", + ); + $this->assertEqual($expected, $result); } public function testPagination() { @@ -301,15 +321,24 @@ class QueryTest extends \lithium\test\Unit { $query = new Query(array('joins' => array(array('foo' => 'bar')))); $query->join(array('bar' => 'baz')); $expected = array(array('foo' => 'bar'), array('bar' => 'baz')); - $this->assertEqual($expected, $query->join()); + $joins = $query->join(); + + $this->assertEqual('bar', $joins[0]->foo()); + $this->assertNull($joins[0]->bar()); + + $this->assertEqual('baz', $joins[1]->bar()); + $this->assertNull($joins[1]->foo()); $query->join('zim', array('dib' => 'gir')); + $this->assertEqual(3, count($query->join())); + $expected = array( array('foo' => 'bar'), array('bar' => 'baz'), 'zim' => array('dib' => 'gir') ); - $this->assertEqual($expected, $query->join()); + $this->assertEqual(3, count($query->join())); + $this->assertEqual('gir', $query->join('zim')->dib()); } /** @@ -361,7 +390,7 @@ class QueryTest extends \lithium\test\Unit { $result = $query->export(Connections::get('mock-database-connection')); $this->assertEqual(array('title' => '..'), $result['data']); - $this->assertEqual("WHERE title = 'FML'", $result['conditions']); + $this->assertEqual("WHERE {title} = 'FML'", $result['conditions']); } public function testEntityConditions() { @@ -371,6 +400,14 @@ class QueryTest extends \lithium\test\Unit { $this->assertEqual(array('id' => 13), $query->conditions()); } + public function testInvalidEntityCondition() { + $entity = new Record(array('model' => $this->_model, 'exists' => true)); + $entity->_id = 13; + $query = new Query(compact('entity')); + $this->expectException('/No matching primary key found/'); + $query->conditions(); + } + public function testAutomaticAliasing() { $query = new Query(array('model' => $this->_model)); $this->assertEqual('MockQueryPost', $query->alias()); @@ -388,6 +425,28 @@ class QueryTest extends \lithium\test\Unit { $this->assertEqual($fields, $query->fields()); $this->assertEqual($order, $query->order()); } + + public function testRenderArrayJoin() { + $model = 'lithium\tests\mocks\data\model\MockQueryComment'; + + $query = new Query(compact('model') + array( + 'type' => 'read', + 'source' => 'comments', + 'alias' => 'Comment', + 'conditions' => array('Comment.id' => 1), + 'joins' => array(array( + 'type' => 'INNER', + 'source' => 'posts', + 'alias' => 'Post', + 'constraint' => array('Comment.post_id' => 'Post.id') + )) + )); + + $expected = "SELECT * FROM AS {Comment} INNER JOIN {posts} AS {Post} ON "; + $expected .= "{Comment}.{post_id} = {Post}.{id} WHERE Comment.id = 1;"; + $result = Connections::get('mock-database-connection')->renderCommand($query); + $this->assertEqual($expected, $result); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/source/DatabaseTest.php b/libraries/lithium/tests/cases/data/source/DatabaseTest.php index add1f65..e189f62 100644 --- a/libraries/lithium/tests/cases/data/source/DatabaseTest.php +++ b/libraries/lithium/tests/cases/data/source/DatabaseTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -387,7 +387,8 @@ class DatabaseTest extends \lithium\test\Unit { 'data' => array('published' => false), 'model' => $this->_model )); - $sql = "UPDATE {mock_database_posts} SET {published} = 0 WHERE {expires} >= '2010-05-13';"; + $sql = "UPDATE {mock_database_posts} SET {published} = 0 WHERE "; + $sql .= "({expires} >= '2010-05-13');"; $this->assertEqual($sql, $this->db->renderCommand($query)); } @@ -395,22 +396,29 @@ class DatabaseTest extends \lithium\test\Unit { $query = new Query(array('type' => 'read', 'model' => $this->_model, 'conditions' => array( 'score' => array('between' => array(90, 100)) ))); - $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} WHERE {score} "; - $sql .= "BETWEEN 90 AND 100;"; + $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} WHERE ({score} "; + $sql .= "BETWEEN 90 AND 100);"; $this->assertEqual($sql, $this->db->renderCommand($query)); $query = new Query(array('type' => 'read', 'model' => $this->_model, 'conditions' => array( 'score' => array('>' => 90, '<' => 100) ))); $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} WHERE "; - $sql .= "{score} > 90 AND {score} < 100;"; + $sql .= "({score} > 90 AND {score} < 100);"; $this->assertEqual($sql, $this->db->renderCommand($query)); $query = new Query(array('type' => 'read', 'model' => $this->_model, 'conditions' => array( 'score' => array('!=' => array(98, 99, 100)) ))); $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} "; - $sql .= "WHERE {score} NOT IN (98, 99, 100);"; + $sql .= "WHERE ({score} NOT IN (98, 99, 100));"; + $this->assertEqual($sql, $this->db->renderCommand($query)); + + $query = new Query(array('type' => 'read', 'model' => $this->_model, 'conditions' => array( + 'scorer' => array('like' => '%howard%') + ))); + $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} "; + $sql .= "WHERE ({scorer} like '%howard%');"; $this->assertEqual($sql, $this->db->renderCommand($query)); $conditions = "custom conditions string"; @@ -419,6 +427,32 @@ class DatabaseTest extends \lithium\test\Unit { )); $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} WHERE {$conditions};"; $this->assertEqual($sql, $this->db->renderCommand($query)); + + $query = new Query(array( + 'type' => 'read', 'model' => $this->_model, + 'conditions' => array( + 'field' => array('like' => '%value%') + ) + )); + $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} WHERE "; + $sql .= "({field} like '%value%');"; + $this->assertEqual($sql, $this->db->renderCommand($query)); + + $query = new Query(array( + 'type' => 'read', 'model' => $this->_model, + 'conditions' => array( + 'or' => array( + 'field1' => 'value1', + 'field2' => 'value2', + 'and' => array('sField' => '1', 'sField2' => '2') + ), + 'bField' => '3' + ) + )); + $sql = "SELECT * FROM {mock_database_posts} AS {MockDatabasePost} WHERE "; + $sql .= "({field1} = 'value1' OR {field2} = 'value2' OR ({sField} = 1 AND {sField2} = 2))"; + $sql .= " AND {bField} = 3;"; + $this->assertEqual($sql, $this->db->renderCommand($query)); } public function testRawConditions() { diff --git a/libraries/lithium/tests/cases/data/source/HttpTest.php b/libraries/lithium/tests/cases/data/source/HttpTest.php index 89b7ae3..72af2ea 100644 --- a/libraries/lithium/tests/cases/data/source/HttpTest.php +++ b/libraries/lithium/tests/cases/data/source/HttpTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/data/source/MongoDbTest.php b/libraries/lithium/tests/cases/data/source/MongoDbTest.php index 865129d..7d13921 100644 --- a/libraries/lithium/tests/cases/data/source/MongoDbTest.php +++ b/libraries/lithium/tests/cases/data/source/MongoDbTest.php @@ -2,9 +2,8 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License - * */ namespace lithium\tests\cases\data\source; @@ -21,7 +20,9 @@ use lithium\data\Connections; use lithium\data\model\Query; use lithium\data\entity\Document; use lithium\tests\mocks\data\MockPost; +use lithium\data\collection\DocumentSet; use lithium\data\collection\DocumentArray; +use lithium\tests\mocks\data\source\MockMongoSource; use lithium\tests\mocks\data\source\MockMongoConnection; class MongoDbTest extends \lithium\test\Unit { @@ -58,7 +59,7 @@ class MongoDbTest extends \lithium\test\Unit { protected $_configs = array(); public function skip() { - $this->skipIf(!MongoDb::enabled(), 'MongoDb Extension is not loaded'); + $this->skipIf(!MongoDb::enabled(), 'MongoDb is not enabled'); $db = new MongoDb($this->_testConfig); $message = "`{$this->_testConfig['database']}` database or connection unavailable"; @@ -93,6 +94,7 @@ class MongoDbTest extends \lithium\test\Unit { $this->db = Connections::get('lithium_mongo_test'); $model = $this->_model; $model::config(array('key' => '_id')); + $model::resetConnection(false); $this->query = new Query(compact('model') + array( 'entity' => new Document(compact('model')) @@ -239,37 +241,50 @@ class MongoDbTest extends \lithium\test\Unit { } public function testReadNoConditions() { + $this->db->connect(); + $connection = $this->db->connection; + $this->db->connection = new MockMongoSource(); + $this->db->connection->resultSets = array(array('ok' => true)); + $data = array('title' => 'Test Post'); + $options = array('safe' => false, 'fsync' => false); $this->query->data($data); - $this->db->create($this->query); + $this->assertIdentical(true, $this->db->create($this->query)); + $this->assertEqual(compact('data', 'options'), end($this->db->connection->queries)); + $this->db->connection->resultSets = array(array(array('_id' => new MongoId()) + $data)); $result = $this->db->read($this->query); - $this->assertTrue($result == true); - $this->assertEqual(1, $result->count()); - $expected = $data['title']; - $this->assertEqual($expected, $result->first()->title); + $this->assertTrue($result instanceof DocumentSet); + $this->assertEqual(1, $result->count()); + $this->assertEqual('Test Post', $result->first()->title); + $this->db->connection = $connection; } public function testReadWithConditions() { + $this->db->connect(); + $connection = $this->db->connection; + $this->db->connection = new MockMongoSource(); + $this->db->connection->resultSets = array(array('ok' => true)); + $data = array('title' => 'Test Post'); + $options = array('safe' => false, 'fsync' => false); $this->query->data($data); - $this->db->create($this->query); + $this->assertTrue($this->db->create($this->query)); $this->query->data(null); + $this->db->connection->resultSets = array(array()); $this->query->conditions(array('title' => 'Nonexistent Post')); $result = $this->db->read($this->query); $this->assertTrue($result == true); + $this->assertEqual(0, $result->count()); - $expected = 0; - $this->assertEqual($expected, $result->count()); - + $this->db->connection->resultSets = array(array($data)); $this->query->conditions($data); $result = $this->db->read($this->query); $this->assertTrue($result == true); - - $expected = 1; - $this->assertEqual($expected, $result->count()); + $this->assertEqual(1, $result->count()); + $this->db->connection = $connection; } public function testUpdate() { @@ -284,7 +299,7 @@ class MongoDbTest extends \lithium\test\Unit { $this->assertEqual(array('_id', 'title'), array_keys($original)); $this->assertEqual('Test Post', $original['title']); - $this->assertPattern('/[0-9a-f]{24}/', $original['_id']); + $this->assertPattern('/^[0-9a-f]{24}$/', $original['_id']); $this->query = new Query(compact('model') + array( 'data' => array('title' => 'New Post Title'), @@ -297,7 +312,8 @@ class MongoDbTest extends \lithium\test\Unit { ))); $this->assertEqual(1, $result->count()); - $updated = $result->first()->to('array'); + $updated = $result->first(); + $updated = $updated ? $updated->to('array') : array(); $this->assertEqual($original['_id'], $updated['_id']); $this->assertEqual('New Post Title', $updated['title']); } @@ -446,17 +462,19 @@ class MongoDbTest extends \lithium\test\Unit { $to = 'lithium\tests\mocks\data\MockPost'; $from::config(array('connection' => 'mock-source')); - $to::config(array('connection' => 'mock-source')); + $to::config(array('connection' => 'mock-source', 'key' => '_id')); $result = $this->db->relationship($from, 'belongsTo', 'MockPost'); - $expected = compact('from', 'to') + array( + $expected = array( 'name' => 'MockPost', 'type' => 'belongsTo', 'keys' => array('mockComment' => '_id'), + 'from' => $from, 'link' => 'contained', - 'conditions' => null, + 'to' => $to, 'fields' => true, 'fieldName' => 'mockPost', + 'constraint' => null, 'init' => true ); $this->assertEqual($expected, $result->data()); @@ -502,7 +520,7 @@ class MongoDbTest extends \lithium\test\Unit { $duplicate = $model::create(array('_id' => $document->_id), array('exists' => true)); $duplicate->values = 'new'; - $duplicate->save(); + $this->assertTrue($duplicate->save()); $document = $model::find((string) $duplicate->_id); $expected = array( @@ -531,57 +549,6 @@ class MongoDbTest extends \lithium\test\Unit { $this->assertEqual(array('_id' => 'custom'), $model::first('custom')->data()); } - /** - * Tests handling type values based on specified schema settings. - * - * @return void - */ - public function testTypeCasting() { - $data = array( - '_id' => '4c8f86167675abfabd970300', - 'title' => 'Foo', - 'tags' => 'test', - 'comments' => array( - "4c8f86167675abfabdbe0300", "4c8f86167675abfabdbf0300", "4c8f86167675abfabdc00300" - ), - 'authors' => '4c8f86167675abfabdb00300', - 'created' => time(), - 'modified' => date('Y-m-d H:i:s'), - 'rank_count' => '45', - 'rank' => '3.45688' - ); - $time = time(); - $result = $this->db->cast($this->_model, $data, array('schema' => $this->_schema)); - - $this->assertEqual(array_keys($data), array_keys($result)); - $this->assertTrue($result['_id'] instanceOf MongoId); - $this->assertEqual('4c8f86167675abfabd970300', (string) $result['_id']); - - $this->assertTrue($result['comments'] instanceOf DocumentArray); - $this->assertEqual(3, count($result['comments'])); - - $this->assertTrue($result['comments'][0] instanceOf MongoId); - $this->assertTrue($result['comments'][1] instanceOf MongoId); - $this->assertTrue($result['comments'][2] instanceOf MongoId); - $this->assertEqual('4c8f86167675abfabdbe0300', (string) $result['comments'][0]); - $this->assertEqual('4c8f86167675abfabdbf0300', (string) $result['comments'][1]); - $this->assertEqual('4c8f86167675abfabdc00300', (string) $result['comments'][2]); - - $this->assertEqual($data['comments'], $result['comments']->data()); - $this->assertEqual(array('test'), $result['tags']->data()); - $this->assertEqual(array('4c8f86167675abfabdb00300'), $result['authors']->data()); - $this->assertTrue($result['authors'][0] instanceOf MongoId); - - $this->assertTrue($result['modified'] instanceOf MongoDate); - $this->assertTrue($result['created'] instanceOf MongoDate); - - $this->assertEqual($time, $result['modified']->sec); - $this->assertEqual($time, $result['created']->sec); - - $this->assertIdentical(45, $result['rank_count']); - $this->assertIdentical(3.45688, $result['rank']); - } - public function testCastingConditionsValues() { $query = new Query(array('schema' => $this->_schema)); @@ -593,7 +560,7 @@ class MongoDbTest extends \lithium\test\Unit { $result = $this->db->conditions($conditions, $query); $this->assertEqual(array_keys($conditions), array_keys($result)); - $this->assertTrue($result['_id'] instanceOf MongoId); + $this->assertTrue($result['_id'] instanceof MongoId); $this->assertEqual($conditions['_id'], (string) $result['_id']); $conditions = array('_id' => array( @@ -601,9 +568,9 @@ class MongoDbTest extends \lithium\test\Unit { )); $result = $this->db->conditions($conditions, $query); $this->assertEqual(3, count($result['_id']['$in'])); - $this->assertTrue($result['_id']['$in'][0] instanceOf MongoId); - $this->assertTrue($result['_id']['$in'][1] instanceOf MongoId); - $this->assertTrue($result['_id']['$in'][2] instanceOf MongoId); + $this->assertTrue($result['_id']['$in'][0] instanceof MongoId); + $this->assertTrue($result['_id']['$in'][1] instanceof MongoId); + $this->assertTrue($result['_id']['$in'][2] instanceof MongoId); $conditions = array('voters' => array('$all' => array( "4c8f86167675abfabdbf0300", "4c8f86167675abfabdc00300" @@ -611,8 +578,8 @@ class MongoDbTest extends \lithium\test\Unit { $result = $this->db->conditions($conditions, $query); $this->assertEqual(2, count($result['voters']['$all'])); - $this->assertTrue($result['voters']['$all'][0] instanceOf MongoId); - $this->assertTrue($result['voters']['$all'][1] instanceOf MongoId); + $this->assertTrue($result['voters']['$all'][0] instanceof MongoId); + $this->assertTrue($result['voters']['$all'][1] instanceof MongoId); $conditions = array('$or' => array( array('_id' => "4c8f86167675abfabdbf0300"), @@ -621,25 +588,8 @@ class MongoDbTest extends \lithium\test\Unit { $result = $this->db->conditions($conditions, $query); $this->assertEqual(array('$or'), array_keys($result)); $this->assertEqual(2, count($result['$or'])); - $this->assertTrue($result['$or'][0]['_id'] instanceOf MongoId); - $this->assertTrue($result['$or'][1]['guid'] instanceOf MongoId); - } - - public function testNestedObjectCasting() { - $data = array('notifications' => array( - 'foo' => '', - 'bar' => '1', - 'baz' => 0 - )); - $model = $this->_model; - $schema = $model::schema(); - $model::schema($this->_schema); - $result = $this->db->cast($model, $data); - $model::schema($schema); - - $this->assertIdentical(false, $result['notifications']->foo); - $this->assertIdentical(true, $result['notifications']->bar); - $this->assertIdentical(false, $result['notifications']->baz); + $this->assertTrue($result['$or'][0]['_id'] instanceof MongoId); + $this->assertTrue($result['$or'][1]['guid'] instanceof MongoId); } public function testMultiOperationConditions() { @@ -647,6 +597,66 @@ class MongoDbTest extends \lithium\test\Unit { $result = $this->db->conditions($conditions, $this->query); $this->assertEqual($conditions, $result); } + + public function testCreateWithEmbeddedObjects() { + $data = array( + '_id' => new MongoId(), + 'created' => new MongoDate(strtotime('-1 hour')), + 'list' => array('foo', 'bar', 'baz') + ); + $entity = new Document(compact('data') + array('exists' => false)); + $query = new Query(array('type' => 'create') + compact('entity')); + $result = $query->export($this->db); + $this->assertIdentical($data, $result['data']['data']); + } + + public function testUpdateWithEmbeddedObjects() { + $data = array( + '_id' => new MongoId(), + 'created' => new MongoDate(strtotime('-1 hour')), + 'list' => array('foo', 'bar', 'baz') + ); + $model = $this->_model; + $schema = array('updated' => array('type' => 'MongoDate')); + $entity = new Document(compact('data', 'schema', 'model') + array('exists' => true)); + $entity->updated = time(); + $entity->list[] = 'dib'; + + $query = new Query(array('type' => 'update') + compact('entity')); + $result = $query->export($this->db); + $this->assertEqual(array('updated'), array_keys($result['data']['update'])); + $this->assertTrue($result['data']['update']['updated'] instanceof MongoDate); + } + + /** + * Assert that Mongo and the Mongo Exporter don't mangle manual geospatial queries. + * + * @return void + */ + public function testGeoQueries() { + $coords = array(84.13, 11.38); + $coords2 = array_map(function($point) { return $point + 5; }, $coords); + $conditions = array('location' => array('$near' => $coords)); + + $query = new Query(compact('conditions') + array('model' => $this->_model)); + $result = $query->export($this->db); + $this->assertEqual($result['conditions'], $conditions); + + $conditions = array('location' => array( + '$within' => array('$box' => array($coords2, $coords)) + )); + $query = new Query(compact('conditions') + array('model' => $this->_model)); + $result = $query->export($this->db); + $this->assertEqual($conditions, $result['conditions']); + } + + public function testSchemaCallback() { + $schema = array('_id' => array('type' => 'id'), 'created' => array('type' => 'date')); + $db = new MongoDb(array('autoConnect' => false, 'schema' => function() use ($schema) { + return $schema; + })); + $this->assertEqual($schema, $db->describe(null)); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/source/database/adapter/MySqlTest.php b/libraries/lithium/tests/cases/data/source/database/adapter/MySqlTest.php index 865b692..0b4ce2c 100644 --- a/libraries/lithium/tests/cases/data/source/database/adapter/MySqlTest.php +++ b/libraries/lithium/tests/cases/data/source/database/adapter/MySqlTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/data/source/database/adapter/Sqlite3Test.php b/libraries/lithium/tests/cases/data/source/database/adapter/Sqlite3Test.php index bbed7c1..743ff33 100644 --- a/libraries/lithium/tests/cases/data/source/database/adapter/Sqlite3Test.php +++ b/libraries/lithium/tests/cases/data/source/database/adapter/Sqlite3Test.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -25,10 +25,16 @@ class Sqlite3Test extends \lithium\test\Unit { * @todo Tie into the Environment class to ensure that the test database is being used. */ public function skip() { - $this->_dbConfig = Connections::get('sqlite3', array('config' => true)); + $this->skipIf(!Sqlite3::enabled(), 'Sqlite3 adapter is not enabled.'); + + $this->_dbConfig = Connections::get('test', array('config' => true)); $hasDb = (isset($this->_dbConfig['adapter']) && $this->_dbConfig['adapter'] == 'Sqlite3'); - $message = 'Test database is either unavailable, or not using a Sqlite adapter'; + $message = 'Test database is either unavailable, or not using a Sqlite3 adapter'; $this->skipIf(!$hasDb, $message); + + $isMemory = $this->_dbConfig['database'] == ':memory:'; + $message = "Test database is not an in-memory database."; + $this->skipIf(!$isMemory, $message); } public function setUp() { @@ -46,7 +52,7 @@ class Sqlite3Test extends \lithium\test\Unit { $expected = array( 'autoConnect' => false, 'database' => '', - 'flags' => NULL, + 'flags' => SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, 'key' => NULL, 'persistent' => true, 'host' => 'localhost', @@ -110,8 +116,28 @@ class Sqlite3Test extends \lithium\test\Unit { public function testExecuteException() { $this->expectException(); + $this->expectException(); $this->db->read('SELECT deliberate syntax error'); } + + public function testEnabledFeatures() { + $this->assertTrue(Sqlite3::enabled()); + $this->assertTrue(Sqlite3::enabled('relationships')); + $this->assertFalse(Sqlite3::enabled('arrays')); + } + + public function testValueQuoting() { + $result = $this->db->value('exciting news'); + $expected = "'exciting news'"; + $this->assertEqual($expected, $result); + } + + public function testNameQuoting() { + $db = new MockSqlite3(array('autoConnect' => false)); + $result = $db->name('title'); + $expected = '"title"'; + $this->assertEqual($expected, $result); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/source/http/adapter/CouchDbTest.php b/libraries/lithium/tests/cases/data/source/http/adapter/CouchDbTest.php index 9663598..11d3b5b 100644 --- a/libraries/lithium/tests/cases/data/source/http/adapter/CouchDbTest.php +++ b/libraries/lithium/tests/cases/data/source/http/adapter/CouchDbTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -108,9 +108,9 @@ class CouchDbTest extends \lithium\test\Unit { public function testCreateNoId() { $couchdb = new CouchDb($this->_testConfig); $this->query->data(array('name' => 'Acme Inc.')); - $expected = true; + $result = $couchdb->create($this->query); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = '/lithium-test'; $result = $couchdb->last->request->path; @@ -124,9 +124,9 @@ class CouchDbTest extends \lithium\test\Unit { public function testCreateWithId() { $couchdb = new CouchDb($this->_testConfig); $this->query->data(array('id' => 12345, 'name' => 'Acme Inc.')); - $expected = true; + $result = $couchdb->create($this->query); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = '/lithium-test/12345'; $result = $couchdb->last->request->path; @@ -139,9 +139,9 @@ class CouchDbTest extends \lithium\test\Unit { public function testReadNoConditions() { $couchdb = new CouchDb($this->_testConfig); - $expected = true; + $result = $couchdb->read($this->query); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $this->assertEqual(array('total_rows' => null, 'offset' => null), $result->stats()); $expected = '/lithium-test/_all_docs'; @@ -155,10 +155,10 @@ class CouchDbTest extends \lithium\test\Unit { public function testReadWithConditions() { $couchdb = new CouchDb($this->_testConfig); - $expected = true; + $this->query->conditions(array('id' => 12345)); $result = $couchdb->read($this->query); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $this->assertEqual(array('total_rows' => null, 'offset' => null), $result->stats()); $expected = '/lithium-test/12345'; @@ -168,17 +168,21 @@ class CouchDbTest extends \lithium\test\Unit { $expected = ''; $result = $couchdb->last->request->params; $this->assertEqual($expected, $result); + + $this->query->conditions(array('id' => 12345, 'path' => '/lithium-test/12345')); + $result = $couchdb->read($this->query); + $this->assertTrue($result); } public function testReadWithViewConditions() { $couchdb = new CouchDb($this->_testConfig); - $expected = true; + $this->query->conditions(array( 'design' => 'latest', 'view' => 'all', 'limit' => 10, 'descending' => 'true' )); $result = $couchdb->read($this->query); $this->assertEqual(array('total_rows' => null, 'offset' => null), $result->stats()); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = '/lithium-test/_design/latest/_view/all/'; $result = $couchdb->last->request->path; @@ -298,18 +302,16 @@ class CouchDbTest extends \lithium\test\Unit { public function testResultClose() { $couchdb = new CouchDb($this->_testConfig); - $expected = null; $result = $couchdb->result('close', (object) array(), $this->query); - $this->assertEqual($expected, $result); + $this->assertNull($result); } public function testUpdate() { $couchdb = new CouchDb($this->_testConfig); $this->query->data(array('id' => 12345, 'rev' => '1-1', 'title' => 'One')); - $expected = true; $result = $couchdb->update($this->query); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = '/lithium-test/12345'; $result = $couchdb->last->request->path; @@ -324,9 +326,8 @@ class CouchDbTest extends \lithium\test\Unit { $couchdb = new CouchDb($this->_testConfig); $this->query->data(array('id' => 12345, 'rev'=> '1-1', 'name' => 'Acme Inc')); - $expected = true; $result = $couchdb->delete($this->query); - $this->assertEqual($expected, $result); + $this->assertTrue($result); $expected = '/lithium-test/12345'; $result = $couchdb->last->request->path; @@ -336,6 +337,15 @@ class CouchDbTest extends \lithium\test\Unit { $result = $couchdb->last->request->params; $this->assertEqual($expected, $result); } + + public function testEnabled() { + $this->assertEqual(CouchDb::enabled(), true); + + $this->assertEqual(CouchDb::enabled('arrays'), true); + $this->assertEqual(CouchDb::enabled('transactions'), false); + $this->assertEqual(CouchDb::enabled('booleans'), true); + $this->assertEqual(CouchDb::enabled('relationships'), false); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/data/source/mongo_db/ExporterTest.php b/libraries/lithium/tests/cases/data/source/mongo_db/ExporterTest.php new file mode 100644 index 0000000..e327305 --- /dev/null +++ b/libraries/lithium/tests/cases/data/source/mongo_db/ExporterTest.php @@ -0,0 +1,321 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\cases\data\source\mongo_db; + +use MongoId; +use MongoDate; +use lithium\data\source\MongoDb; +use lithium\data\entity\Document; +use lithium\data\collection\DocumentArray; +use lithium\data\source\mongo_db\Exporter; + +class ExporterTest extends \lithium\test\Unit { + + protected $_model = 'lithium\tests\mocks\data\source\MockMongoPost'; + + protected $_schema = array( + '_id' => array('type' => 'id'), + 'guid' => array('type' => 'id'), + 'title' => array('type' => 'string'), + 'tags' => array('type' => 'string', 'array' => true), + 'comments' => array('type' => 'MongoId'), + 'authors' => array('type' => 'MongoId', 'array' => true), + 'created' => array('type' => 'MongoDate'), + 'modified' => array('type' => 'datetime'), + 'voters' => array('type' => 'id', 'array' => true), + 'rank_count' => array('type' => 'integer', 'default' => 0), + 'rank' => array('type' => 'float', 'default' => 0.0), + 'notifications.foo' => array('type' => 'boolean'), + 'notifications.bar' => array('type' => 'boolean'), + 'notifications.baz' => array('type' => 'boolean'), + ); + + protected $_handlers = array(); + + public function skip() { + $this->skipIf(!MongoDb::enabled(), 'MongoDb is not enabled'); + } + + public function setUp() { + $this->_handlers = array( + 'id' => function($v) { + return is_string($v) && preg_match('/^[0-9a-f]{24}$/', $v) ? new MongoId($v) : $v; + }, + 'date' => function($v) { + $v = is_numeric($v) ? intval($v) : strtotime($v); + return (time() == $v) ? new MongoDate() : new MongoDate($v); + }, + 'regex' => function($v) { return new MongoRegex($v); }, + 'integer' => function($v) { return (integer) $v; }, + 'float' => function($v) { return (float) $v; }, + 'boolean' => function($v) { return (boolean) $v; }, + 'code' => function($v) { return new MongoCode($v); }, + 'binary' => function($v) { return new MongoBinData($v); }, + ); + $model = $this->_model; + $model::resetConnection(true); + } + + public function testInvalid() { + $this->assertNull(Exporter::get(null, null)); + } + + public function testCreateWithFixedData() { + $doc = new Document(array('exists' => false, 'data' => array( + '_id' => new MongoId(), + 'created' => new MongoDate(), + 'numbers' => new DocumentArray(array('data' => array(7, 8, 9))), + 'objects' => new DocumentArray(array('data' => array( + new Document(array('data' => array('foo' => 'bar'))), + new Document(array('data' => array('baz' => 'dib'))) + ))), + 'deeply' => new Document(array('data' => array('nested' => 'object'))) + ))); + $this->assertEqual('object', $doc->deeply->nested); + $this->assertTrue($doc->_id instanceof MongoId); + + $result = Exporter::get('create', $doc->export()); + $this->assertTrue($result['create']['_id'] instanceof MongoId); + $this->assertTrue($result['create']['created'] instanceof MongoDate); + $this->assertIdentical(time(), $result['create']['created']->sec); + + $this->assertIdentical(array(7, 8, 9), $result['create']['numbers']); + $expected = array(array('foo' => 'bar'), array('baz' => 'dib')); + $this->assertIdentical($expected, $result['create']['objects']); + $this->assertIdentical(array('nested' => 'object'), $result['create']['deeply']); + } + + public function testCreateWithChangedData() { + $doc = new Document(array('exists' => false, 'data' => array( + 'numbers' => new DocumentArray(array('data' => array(7, 8, 9))), + 'objects' => new DocumentArray(array('data' => array( + new Document(array('data' => array('foo' => 'bar'))), + new Document(array('data' => array('baz' => 'dib'))) + ))), + 'deeply' => new Document(array('data' => array('nested' => 'object'))) + ))); + $doc->numbers[] = 10; + $doc->deeply->nested2 = 'object2'; + $doc->objects[1]->dib = 'gir'; + + $expected = array( + 'numbers' => array(7, 8, 9, 10), + 'objects' => array(array('foo' => 'bar'), array('baz' => 'dib', 'dib' => 'gir')), + 'deeply' => array('nested' => 'object', 'nested2' => 'object2') + ); + $result = Exporter::get('create', $doc->export()); + $this->assertEqual(array('create'), array_keys($result)); + $this->assertEqual($expected, $result['create']); + } + + public function testUpdateWithNoChanges() { + $doc = new Document(array('exists' => true, 'data' => array( + 'numbers' => new DocumentArray(array('exists' => true, 'data' => array(7, 8, 9))), + 'objects' => new DocumentArray(array('exists' => true, 'data' => array( + new Document(array('exists' => true, 'data' => array('foo' => 'bar'))), + new Document(array('exists' => true, 'data' => array('baz' => 'dib'))) + ))), + 'deeply' => new Document(array('exists' => true, 'data' => array('nested' => 'object'))) + ))); + $this->assertFalse(Exporter::get('update', $doc->export())); + } + + public function testUpdateWithSubObjects() { + $doc = new Document(array('exists' => true, 'data' => array( + 'numbers' => new DocumentArray(array('data' => array(7, 8, 9))), + 'deeply' => new Document(array( + 'pathKey' => 'deeply', 'exists' => true, 'data' => array('nested' => 'object') + )), + 'foo' => 'bar' + ))); + $doc->field = 'value'; + $doc->deeply->nested = 'foo'; + $doc->newObject = new Document(array( + 'exists' => false, 'data' => array('subField' => 'subValue') + )); + + $this->assertEqual('foo', $doc->deeply->nested); + $this->assertEqual('subValue', $doc->newObject->subField); + + $result = Exporter::get('update', $doc->export()); + $this->assertFalse(isset($result['update']['foo'])); + $this->assertEqual('value', $result['update']['field']); + $this->assertEqual(array('subField' => 'subValue'), $result['update']['newObject']); + $this->assertEqual('foo', $result['update']['deeply.nested']); + } + + public function testFieldRemoval() { + $doc = new Document(array('exists' => true, 'data' => array( + 'numbers' => new DocumentArray(array('data' => array(7, 8, 9))), + 'deeply' => new Document(array( + 'pathKey' => 'deeply', 'exists' => true, 'data' => array('nested' => 'object') + )), + 'foo' => 'bar' + ))); + $doc->set(array('flagged' => true, 'foo' => 'baz', 'bar' => 'dib')); + unset($doc->foo, $doc->flagged, $doc->numbers, $doc->deeply->nested); + + $result = Exporter::get('update', $doc->export()); + $expected = array( + 'foo' => true, 'flagged' => true, 'numbers' => true, 'deeply.nested' => true + ); + $this->assertEqual($expected, $result['remove']); + $this->assertEqual(array('bar' => 'dib'), $result['update']); + } + + /** + * Tests that when an existing object is attached as a value of another existing object, the + * whole sub-object is re-written to the new value. + * + * @return void + */ + public function testAppendExistingObjects() { + $doc = new Document(array('exists' => true, 'data' => array( + 'deeply' => new Document(array( + 'pathKey' => 'deeply', 'exists' => true, 'data' => array('nested' => 'object') + )), + 'foo' => 'bar' + ))); + $append = new Document(array('exists' => true, 'data' => array('foo' => 'bar'))); + + $doc->deeply = $append; + $result = Exporter::get('update', $doc->export()); + $expected = array('update' => array('deeply' => array('foo' => 'bar'))); + $this->assertEqual($expected, $result); + $doc->update(); + + $expected = array('$set' => array('deeply' => array('foo' => 'bar'))); + $this->assertEqual($expected, Exporter::toCommand($result)); + + $doc->append2 = new Document(array('exists' => false, 'data' => array('foo' => 'bar'))); + $expected = array('update' => array('append2' => array('foo' => 'bar'))); + $this->assertEqual($expected, Exporter::get('update', $doc->export())); + $doc->update(); + + $this->assertFalse(Exporter::get('update', $doc->export())); + $doc->append2->foo = 'baz'; + $doc->append2->bar = 'dib'; + $doc->deeply->nested = true; + + $expected = array('update' => array( + 'append2.foo' => 'baz', 'append2.bar' => 'dib', 'deeply.nested' => true + )); + $this->assertEqual($expected, Exporter::get('update', $doc->export())); + } + + public function testNestedObjectCasting() { + $model = $this->_model; + $data = array('notifications' => array('foo' => '', 'bar' => '1', 'baz' => 0, 'dib' => 42)); + + $model::schema($this->_schema); + $result = Exporter::cast($data, $this->_schema, $model::connection(), compact('model')); + + $this->assertIdentical(false, $result['notifications']->foo); + $this->assertIdentical(true, $result['notifications']->bar); + $this->assertIdentical(false, $result['notifications']->baz); + $this->assertIdentical(42, $result['notifications']->dib); + } + + /** + * Tests handling type values based on specified schema settings. + * + * @return void + */ + public function testTypeCasting() { + $data = array( + '_id' => '4c8f86167675abfabd970300', + 'title' => 'Foo', + 'tags' => 'test', + 'comments' => array( + "4c8f86167675abfabdbe0300", "4c8f86167675abfabdbf0300", "4c8f86167675abfabdc00300" + ), + 'authors' => '4c8f86167675abfabdb00300', + 'created' => time(), + 'modified' => date('Y-m-d H:i:s'), + 'rank_count' => '45', + 'rank' => '3.45688' + ); + $time = time(); + $model = $this->_model; + $handlers = $this->_handlers; + $options = compact('model', 'handlers'); + $result = Exporter::cast($data, $this->_schema, $model::connection(), $options); + + $this->assertEqual(array_keys($data), array_keys($result)); + $this->assertTrue($result['_id'] instanceof MongoId); + $this->assertEqual('4c8f86167675abfabd970300', (string) $result['_id']); + + $this->assertTrue($result['comments'] instanceof DocumentArray); + $this->assertEqual(3, count($result['comments'])); + + $this->assertTrue($result['comments'][0] instanceof MongoId); + $this->assertTrue($result['comments'][1] instanceof MongoId); + $this->assertTrue($result['comments'][2] instanceof MongoId); + $this->assertEqual('4c8f86167675abfabdbe0300', (string) $result['comments'][0]); + $this->assertEqual('4c8f86167675abfabdbf0300', (string) $result['comments'][1]); + $this->assertEqual('4c8f86167675abfabdc00300', (string) $result['comments'][2]); + + $this->assertEqual($data['comments'], $result['comments']->data()); + $this->assertEqual(array('test'), $result['tags']->data()); + $this->assertEqual(array('4c8f86167675abfabdb00300'), $result['authors']->data()); + $this->assertTrue($result['authors'][0] instanceof MongoId); + + $this->assertTrue($result['modified'] instanceof MongoDate); + $this->assertTrue($result['created'] instanceof MongoDate); + $this->assertTrue($result['created']->usec > 0); + + $this->assertEqual($time, $result['modified']->sec); + $this->assertEqual($time, $result['created']->sec); + + $this->assertIdentical(45, $result['rank_count']); + $this->assertIdentical(3.45688, $result['rank']); + } + + public function testWithArraySchema() { + $model = $this->_model; + $model::schema(array( + '_id' => array('type' => 'id'), + 'list' => array('array' => true), + 'list.foo' => array('type' => 'string'), + 'list.bar' => array('type' => 'string') + )); + $doc = new Document(compact('model')); + $doc->list[] = array('foo' => '!!', 'bar' => '??'); + + $data = array('list' => array(array('foo' => '!!', 'bar' => '??'))); + $this->assertEqual($data, $doc->data()); + + $result = Exporter::get('create', $doc->export()); + $this->assertEqual($data, $result['create']); + + $result = Exporter::get('update', $doc->export()); + $this->assertEqual($data, $result['update']); + + $doc = new Document(compact('model')); + $doc->list = array(); + $doc->list[] = array('foo' => '!!', 'bar' => '??'); + + $data = array('list' => array(array('foo' => '!!', 'bar' => '??'))); + $this->assertEqual($data, $doc->data()); + + $result = Exporter::get('create', $doc->export()); + $this->assertEqual($result['create'], $data); + + $result = Exporter::get('update', $doc->export()); + $this->assertEqual($result['update'], $data); + } + + /** + * @todo Implement me. + */ + public function testCreateWithWhitelist() { + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/g11n/CatalogTest.php b/libraries/lithium/tests/cases/g11n/CatalogTest.php index f761dc6..af27975 100644 --- a/libraries/lithium/tests/cases/g11n/CatalogTest.php +++ b/libraries/lithium/tests/cases/g11n/CatalogTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -306,78 +306,70 @@ class CatalogTest extends \lithium\test\Unit { $data = array('house' => 'Haus'); Catalog::write('runtime', 'message', 'de', $data); $result = Catalog::read('runtime', 'message', 'de', array('lossy' => false)); - $expected = array( - 'house' => array( - 'id' => 'house', - 'ids' => array(), - 'translated' => 'Haus', - 'flags' => array(), - 'comments' => array(), - 'occurrences' => array() + $expected = array('house' => array( + 'id' => 'house', + 'ids' => array(), + 'translated' => 'Haus', + 'flags' => array(), + 'comments' => array(), + 'occurrences' => array() )); $this->assertEqual($expected, $result); - $data = array( - 'house' => array( - 'id' => 'house', - 'ids' => array(), - 'translated' => 'Haus', - 'flags' => array(), - 'comments' => array(), - 'occurrences' => array() + $data = array('house' => array( + 'id' => 'house', + 'ids' => array(), + 'translated' => 'Haus', + 'flags' => array(), + 'comments' => array(), + 'occurrences' => array() )); Catalog::write('runtime', 'message', 'de', $data); $result = Catalog::read('runtime', 'message', 'de', array('lossy' => false)); - $expected = array( - 'house' => array( - 'id' => 'house', - 'ids' => array(), - 'translated' => 'Haus', - 'flags' => array(), - 'comments' => array(), - 'occurrences' => array() + $expected = array('house' => array( + 'id' => 'house', + 'ids' => array(), + 'translated' => 'Haus', + 'flags' => array(), + 'comments' => array(), + 'occurrences' => array() )); $this->assertEqual($expected, $result); } public function testOutputLossyFormat() { - $data = array( - 'house' => array( - 'id' => 'house', - 'ids' => array('singular' => 'house'), - 'translated' => 'Haus', - 'flags' => array(), - 'comments' => array(), - 'occurrences' => array() + $data = array('house' => array( + 'id' => 'house', + 'ids' => array('singular' => 'house'), + 'translated' => 'Haus', + 'flags' => array(), + 'comments' => array(), + 'occurrences' => array() )); Catalog::write('runtime', 'message', 'de', $data); $result = Catalog::read('runtime', 'message', 'de'); - $expected = array( - 'house' => 'Haus' - ); + $expected = array('house' => 'Haus'); $this->assertEqual($expected, $result); } public function testOutputLosslessFormat() { - $data = array( - 'house' => array( - 'id' => 'house', - 'ids' => array('singular' => 'house'), - 'translated' => 'Haus', - 'flags' => array(), - 'comments' => array(), - 'occurrences' => array() + $data = array('house' => array( + 'id' => 'house', + 'ids' => array('singular' => 'house'), + 'translated' => 'Haus', + 'flags' => array(), + 'comments' => array(), + 'occurrences' => array() )); Catalog::write('runtime', 'message', 'de', $data); $result = Catalog::read('runtime', 'message', 'de', array('lossy' => false)); - $expected = array( - 'house' => array( - 'id' => 'house', - 'ids' => array('singular' => 'house'), - 'translated' => 'Haus', - 'flags' => array(), - 'comments' => array(), - 'occurrences' => array() + $expected = array('house' => array( + 'id' => 'house', + 'ids' => array('singular' => 'house'), + 'translated' => 'Haus', + 'flags' => array(), + 'comments' => array(), + 'occurrences' => array() )); $this->assertEqual($expected, $result); } @@ -385,7 +377,7 @@ class CatalogTest extends \lithium\test\Unit { public function testInvalidWrite() { Catalog::reset(); $data = array('house' => array('id' => 'house')); - $this->expectException("Configuration 'runtime' has not been defined."); + $this->expectException("Configuration `runtime` has not been defined."); $this->assertFalse(Catalog::write('runtime', 'message', 'de', $data)); } } diff --git a/libraries/lithium/tests/cases/g11n/LocaleTest.php b/libraries/lithium/tests/cases/g11n/LocaleTest.php index e011c4e..f3d0343 100644 --- a/libraries/lithium/tests/cases/g11n/LocaleTest.php +++ b/libraries/lithium/tests/cases/g11n/LocaleTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/g11n/MessageTest.php b/libraries/lithium/tests/cases/g11n/MessageTest.php index f5dbbbf..aa6df4f 100644 --- a/libraries/lithium/tests/cases/g11n/MessageTest.php +++ b/libraries/lithium/tests/cases/g11n/MessageTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/g11n/catalog/AdapterTest.php b/libraries/lithium/tests/cases/g11n/catalog/AdapterTest.php index 1a144dc..8b8a7bd 100644 --- a/libraries/lithium/tests/cases/g11n/catalog/AdapterTest.php +++ b/libraries/lithium/tests/cases/g11n/catalog/AdapterTest.php @@ -2,13 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\g11n\catalog; -use \Exception; +use Exception; use lithium\tests\mocks\g11n\catalog\MockAdapter; class AdapterTest extends \lithium\test\Unit { diff --git a/libraries/lithium/tests/cases/g11n/catalog/adapter/CodeTest.php b/libraries/lithium/tests/cases/g11n/catalog/adapter/CodeTest.php index f414d48..85b719c 100644 --- a/libraries/lithium/tests/cases/g11n/catalog/adapter/CodeTest.php +++ b/libraries/lithium/tests/cases/g11n/catalog/adapter/CodeTest.php @@ -2,13 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\g11n\catalog\adapter; -use \Exception; +use Exception; +use lithium\core\Libraries; use lithium\g11n\catalog\adapter\Code; class CodeTest extends \lithium\test\Unit { @@ -18,7 +19,7 @@ class CodeTest extends \lithium\test\Unit { protected $_path; public function setUp() { - $this->_path = $path = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_path = $path = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($this->_path), "{$this->_path} is not writable."); $this->adapter = new Code(compact('path')); @@ -72,7 +73,7 @@ EOD; } public function testPathMustExist() { - $path = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $path = Libraries::get(true, 'resources') . '/tmp/tests'; try { new Code(array('path' => $this->_path)); diff --git a/libraries/lithium/tests/cases/g11n/catalog/adapter/GettextTest.php b/libraries/lithium/tests/cases/g11n/catalog/adapter/GettextTest.php index 0c8d70d..ca55470 100644 --- a/libraries/lithium/tests/cases/g11n/catalog/adapter/GettextTest.php +++ b/libraries/lithium/tests/cases/g11n/catalog/adapter/GettextTest.php @@ -2,13 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\g11n\catalog\adapter; -use \Exception; +use Exception; +use lithium\core\Libraries; use lithium\tests\mocks\g11n\catalog\adapter\MockGettext; class GettextTest extends \lithium\test\Unit { @@ -18,13 +19,13 @@ class GettextTest extends \lithium\test\Unit { protected $_path; public function skip() { - $path = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $path = Libraries::get(true, 'resources') . '/tmp/tests'; $message = "Path {$path} is not writable."; $this->skipIf(!is_writable($path), $message); } public function setUp() { - $this->_path = $path = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_path = $path = Libraries::get(true, 'resources') . '/tmp/tests'; mkdir("{$this->_path}/en/LC_MESSAGES", 0755, true); mkdir("{$this->_path}/de/LC_MESSAGES", 0755, true); $this->adapter = new MockGettext(compact('path')); diff --git a/libraries/lithium/tests/cases/g11n/catalog/adapter/PhpTest.php b/libraries/lithium/tests/cases/g11n/catalog/adapter/PhpTest.php index 37246c3..e58abc8 100644 --- a/libraries/lithium/tests/cases/g11n/catalog/adapter/PhpTest.php +++ b/libraries/lithium/tests/cases/g11n/catalog/adapter/PhpTest.php @@ -2,13 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\g11n\catalog\adapter; -use \Exception; +use Exception; +use lithium\core\Libraries; use lithium\g11n\catalog\adapter\Php; class PhpTest extends \lithium\test\Unit { @@ -18,7 +19,7 @@ class PhpTest extends \lithium\test\Unit { protected $_path; public function skip() { - $this->_path = $path = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $this->_path = $path = Libraries::get(true, 'resources') . '/tmp/tests'; $message = "{$path} is not writable."; $this->skipIf(!is_writable($path), $message); } diff --git a/libraries/lithium/tests/cases/net/MessageTest.php b/libraries/lithium/tests/cases/net/MessageTest.php index 3b31fe3..c1d703a 100644 --- a/libraries/lithium/tests/cases/net/MessageTest.php +++ b/libraries/lithium/tests/cases/net/MessageTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/net/http/MediaTest.php b/libraries/lithium/tests/cases/net/http/MediaTest.php index fbab783..27a1a71 100644 --- a/libraries/lithium/tests/cases/net/http/MediaTest.php +++ b/libraries/lithium/tests/cases/net/http/MediaTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -32,24 +32,25 @@ class MediaTest extends \lithium\test\Unit { * @return void */ public function testMediaTypes() { - $result = Media::types(); - - $this->assertTrue(is_array($result)); - $this->assertTrue(in_array('json', $result)); - $this->assertFalse(in_array('my', $result)); + // Get a list of all available media types: + $types = Media::types(); // returns array('html', 'json', 'rss', ...); - $this->assertEqual($result, Media::formats()); + $expected = array( + 'html', 'htm', 'form', 'json', 'rss', 'atom', 'css', 'js', 'text', 'txt', 'xml' + ); + $this->assertEqual($expected, $types); + $this->assertEqual($expected, Media::formats()); $result = Media::type('json'); $expected = 'application/json'; $this->assertEqual($expected, $result['content']); $expected = array( - 'view' => false, 'layout' => false, 'cast' => true, - 'encode' => 'json_encode', 'decode' => $result['options']['decode'] + 'cast' => true, 'encode' => 'json_encode', 'decode' => $result['options']['decode'] ); $this->assertEqual($expected, $result['options']); + // Add a custom media type with a custom view class: Media::type('my', 'text/x-my', array('view' => 'my\custom\View', 'layout' => false)); $result = Media::types(); @@ -61,10 +62,11 @@ class MediaTest extends \lithium\test\Unit { $expected = array( 'view' => 'my\custom\View', 'template' => null, 'layout' => null, - 'encode' => null, 'decode' => null, 'cast' => true + 'encode' => null, 'decode' => null, 'cast' => true, 'conditions' => array() ); $this->assertEqual($expected, $result['options']); + // Remove a custom media type: Media::type('my', false); $result = Media::types(); $this->assertFalse(in_array('my', $result)); @@ -77,23 +79,14 @@ class MediaTest extends \lithium\test\Unit { */ public function testContentTypeDetection() { $this->assertNull(Media::type('application/foo')); - - $result = Media::type('application/javascript'); - $this->assertEqual('js', $result['content']); - - $result = Media::type('*/*'); - $this->assertEqual('html', $result['content']); - - $result = Media::type('application/json'); - $this->assertEqual('json', $result['content']); - - $result = Media::type('application/json; charset=UTF-8'); - $this->assertEqual('json', $result['content']); + $this->assertEqual('js', Media::type('application/javascript')); + $this->assertEqual('html', Media::type('*/*')); + $this->assertEqual('json', Media::type('application/json')); + $this->assertEqual('json', Media::type('application/json; charset=UTF-8')); $result = Media::type('json'); $expected = array('content' => 'application/json', 'options' => array( - 'view' => false, 'layout' => false, 'cast' => true, - 'encode' => 'json_encode', 'decode' => $result['options']['decode'] + 'cast' => true, 'encode' => 'json_encode', 'decode' => $result['options']['decode'] )); $this->assertEqual($expected, $result); } @@ -245,13 +238,11 @@ class MediaTest extends \lithium\test\Unit { $data = array('something'); Media::render($response, $data); - $expected = array('Content-type: application/json'); $result = $response->headers(); - $this->assertEqual($expected, $result); + $this->assertEqual(array('Content-type: application/json; charset=UTF-8'), $result); - $expected = json_encode($data); $result = $response->body(); - $this->assertEqual($expected, $result); + $this->assertEqual(json_encode($data), $result); } /** @@ -297,8 +288,7 @@ class MediaTest extends \lithium\test\Unit { $this->assertEqual(array($expected), $result); $result = $response->headers['Content-type']; - $expected = 'application/csv'; - $this->assertEqual($expected, $result); + $this->assertEqual('application/csv; charset=UTF-8', $result); } /** @@ -311,9 +301,8 @@ class MediaTest extends \lithium\test\Unit { $response->type('text'); Media::render($response, "Hello, world!"); - $expected = array("Hello, world!"); $result = $response->body; - $this->assertEqual($expected, $result); + $this->assertEqual(array("Hello, world!"), $result); } /** @@ -326,7 +315,7 @@ class MediaTest extends \lithium\test\Unit { $response = new Response(); $response->type('bad'); - $this->expectException("Unhandled media type 'bad'"); + $this->expectException("Unhandled media type `bad`."); Media::render($response, array('foo' => 'bar')); $result = $response->body; @@ -343,7 +332,7 @@ class MediaTest extends \lithium\test\Unit { $response = new Response(); $response->type('xml'); - $this->expectException("Unhandled media type 'xml'"); + $this->expectException("Unhandled media type `xml`."); Media::render($response, array('foo' => 'bar')); $result = $response->body; @@ -415,7 +404,7 @@ class MediaTest extends \lithium\test\Unit { } public function testRenderWithOptionsMerging() { - $base = LITHIUM_APP_PATH . '/resources/tmp'; + $base = Libraries::get(true, 'resources') . '/tmp'; $this->skipIf(!is_writable($base), "{$base} is not writable."); $request = new Request(); @@ -534,6 +523,43 @@ class MediaTest extends \lithium\test\Unit { $result = Media::asset('foo/bar/image.jpg', 'image', array('base' => 'foo')); $this->assertEqual('foo/img/foo/bar/image.jpg', $result); + + $result = Media::asset('/foo/bar/image.jpg', 'image', array('base' => '')); + $this->assertEqual('/foo/bar/image.jpg', $result); + } + + public function testContentNegotiationByType() { + $this->assertEqual('html', Media::type('text/html')); + + Media::type('jsonp', 'text/html', array( + 'conditions' => array('type' => true) + )); + $this->assertEqual(array('jsonp', 'html'), Media::type('text/html')); + + $config = array('env' => array('HTTP_ACCEPT' => 'text/html,text/plain;q=0.5')); + $request = new Request($config); + $request->params = array('type' => 'jsonp'); + $this->assertEqual('jsonp', Media::negotiate($request)); + + $request = new Request($config); + $this->assertEqual('html', Media::negotiate($request)); + } + + public function testContentNegotiationByUserAgent() { + Media::type('iphone', 'application/xhtml+xml', array( + 'conditions' => array('mobile' => true) + )); + $request = new Request(array('env' => array( + 'HTTP_USER_AGENT' => 'Safari', + 'HTTP_ACCEPT' => 'application/xhtml+xml,text/html' + ))); + $this->assertEqual('html', Media::negotiate($request)); + + $request = new Request(array('env' => array( + 'HTTP_USER_AGENT' => 'iPhone', + 'HTTP_ACCEPT' => 'application/xhtml+xml,text/html' + ))); + $this->assertEqual('iphone', Media::negotiate($request)); } } diff --git a/libraries/lithium/tests/cases/net/http/MessageTest.php b/libraries/lithium/tests/cases/net/http/MessageTest.php index e44bf9e..f86969e 100644 --- a/libraries/lithium/tests/cases/net/http/MessageTest.php +++ b/libraries/lithium/tests/cases/net/http/MessageTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -29,9 +29,8 @@ class MessageTest extends \lithium\test\Unit { $result = $this->message->headers('Host'); $this->assertEqual($expected, $result); - $expected = null; $result = $this->message->headers('Host', false); - $this->assertEqual($expected, $result); + $this->assertFalse($result); } public function testHeaderKeyValue() { @@ -57,8 +56,7 @@ class MessageTest extends \lithium\test\Unit { } public function testType() { - $result = $this->message->type("json"); - $this->assertEqual('json', $result); + $this->assertEqual('json', $this->message->type("json")); $this->assertEqual('json', $this->message->type()); $expected = 'json'; diff --git a/libraries/lithium/tests/cases/net/http/RequestTest.php b/libraries/lithium/tests/cases/net/http/RequestTest.php index ccc3e42..d01846f 100644 --- a/libraries/lithium/tests/cases/net/http/RequestTest.php +++ b/libraries/lithium/tests/cases/net/http/RequestTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -165,8 +165,7 @@ class RequestTest extends \lithium\test\Unit { 'protocol_version' => '1.1', 'ignore_errors' => true )); - $result = $request->to('context'); - $this->assertEqual($expected, $result); + $this->assertEqual($expected, $request->to('context')); } public function testToStringWithBody() { diff --git a/libraries/lithium/tests/cases/net/http/ResponseTest.php b/libraries/lithium/tests/cases/net/http/ResponseTest.php index cacd9cf..3b70776 100644 --- a/libraries/lithium/tests/cases/net/http/ResponseTest.php +++ b/libraries/lithium/tests/cases/net/http/ResponseTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -43,9 +43,8 @@ class ResponseTest extends \lithium\test\Unit { $result = $response->status('See Other'); $this->assertEqual($expected, $result); - $expected = false; $result = $response->status('foobar'); - $this->assertEqual($expected, $result); + $this->assertFalse($result); } public function testParsingContentTypeWithEncoding() { @@ -90,6 +89,40 @@ class ResponseTest extends \lithium\test\Unit { $this->assertEqual($expected, (string) $response); } + public function testMessageContentTypeParsing() { + // Content type WITHOUT space between type and charset + $message = join("\r\n", array( + 'HTTP/1.1 200 OK', + 'Content-Type: application/json;charset=iso-8859-1', + '', + 'Test!' + )); + $response = new Response(array('message' => $message)); + $this->assertEqual('application/json', $response->type); + $this->assertEqual('ISO-8859-1', $response->encoding); + + // Content type WITH ONE space between type and charset + $message = join("\r\n", array( + 'HTTP/1.1 200 OK', + 'Content-Type: application/json; charset=iso-8859-1', + '', + 'Test!' + )); + $response = new Response(array('message' => $message)); + $this->assertEqual('application/json', $response->type); + $this->assertEqual('ISO-8859-1', $response->encoding); + + $message = join("\r\n", array( + 'HTTP/1.1 200 OK', + 'Content-Type: application/json; charset=iso-8859-1', + '', + 'Test!' + )); + $response = new Response(array('message' => $message)); + $this->assertEqual('application/json', $response->type); + $this->assertEqual('ISO-8859-1', $response->encoding); + } + public function testEmptyResponse() { $response = new Response(array('message' => "\n")); $result = trim((string) $response); @@ -149,8 +182,6 @@ class ResponseTest extends \lithium\test\Unit { '1', '', '', - '0', - '', '', )); $response = new Response(compact('message')); @@ -164,19 +195,12 @@ class ResponseTest extends \lithium\test\Unit { )); $this->assertEqual($expected, $response->body()); - $message = $headers . join("\r\n", array('body')); + $message = $headers . "\r\nbody"; $response = new Response(compact('message')); $result = $response->body(); $this->assertEqual('body', $result); - $message = $headers . join("\r\n", array('[part one];', '[part two]')); - $expected = '[part two]'; - $response = new Response(compact('message')); - - $result = $response->body(); - $this->assertEqual($expected, $result); - $message = join("\r\n", array( 'HTTP/1.1 200 OK', 'Header: Value', @@ -198,6 +222,72 @@ class ResponseTest extends \lithium\test\Unit { $this->assertPattern('/^HTTP\/1\.1 200 OK/', $result); $this->assertPattern('/Content-Type: application\/json\s+$/ms', $result); } + + /** + * Creates a chunked gzipped message to test response decoding. + * + * @param string $body Message body. + * @param array $headers Message headers. + * @return string Returns a raw HTTP message with headers and body. + */ + protected function _createMessage($body, array $headers = array()) { + $headers += array( + 'Connection: close', + 'Content-Encoding: gzip', + 'Content-Type: text/html; charset=ISO-8859-15', + 'Server: Apache/2.2.16 (Debian) mod_ssl/2.2.16 OpenSSL/0.9.8o', + 'Transfer-Encoding: chunked', + 'Vary: Accept-Encoding', + ); + return join("\r\n", $headers) . "\r\n\r\n" . $body; + } + + public function testWithoutChunksAndComment() { + $body = "\n<html>\n <head>\n <title>Simple site</title>\n </head>\n"; + $body .= "<body>\n <h1>Simple site</h1>\n <p>\n But awesome\n"; + $body .= " </p>\n </body>\n</html>\n"; + $message = $this->_createMessage($body); + $response = new Response(compact('message')); + $this->assertEqual(trim($body), $response->body()); + } + + public function testWithoutChunksAndCommentInBody() { + $body = "\n<html>\n <head>\n <title>Simple site</title>\n </head>"; + $body .= "\n <body>\n <!-- (c) 1998 - 2011 Tweakers.net B.V. --> "; + $body .= "\n <h1>Simple site</h1>\n <p>\n But awesome"; + $body .= "\n </p>\n </body>\n</html>\n"; + $message = $this->_createMessage($body); + $response = new Response(compact('message')); + $this->assertEqual(trim($body), $response->body()); + } + + public function testWithoutChunksAndRandomCommentInHtmlRoot() { + $body = "\n<html><!-- This is some random comment -->\n <head>"; + $body .= "\n <title>Simple site</title>\n </head>\n <body>"; + $body .= "\n <h1>Simple site</h1>\n <p>\n But awesome"; + $body .= "\n </p>\n </body>\n</html>\n"; + $message = $this->_createMessage($body); + $response = new Response(compact('message')); + $this->assertEqual(trim($body), $response->body()); + } + + public function testWithoutChunksAndCommentInHtmlRoot() { + $body = "\n<!doctype html><!-- (c) 1998 - 2011 Tweakers.net B.V. --> \n<html lang=\"nl\"> "; + $body .= "\n <head>\n <title>Simple site</title>\n </head>"; + $body .= "\n <body>\n <h1>Simple site</h1>\n <p>\n But awesome"; + $body .= "\n </p>\n </body>\n</html>\n"; + $message = $this->_createMessage($body); + $response = new Response(compact('message')); + $this->assertEqual(trim($body), $response->body()); + } + + public function testMessageWithNoHeaders() { + $body = "\n<html>...</html>\n"; + $message = "\r\n\r\n{$body}"; + $response = new Response(compact('message')); + $this->assertFalse($response->headers()); + $this->assertEqual(trim($body), $response->body()); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/net/http/RouteTest.php b/libraries/lithium/tests/cases/net/http/RouteTest.php index 215484e..a3a94da 100644 --- a/libraries/lithium/tests/cases/net/http/RouteTest.php +++ b/libraries/lithium/tests/cases/net/http/RouteTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/net/http/RouterTest.php b/libraries/lithium/tests/cases/net/http/RouterTest.php index 5cf5616..57c2759 100644 --- a/libraries/lithium/tests/cases/net/http/RouterTest.php +++ b/libraries/lithium/tests/cases/net/http/RouterTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -223,7 +223,9 @@ class RouterTest extends \lithium\test\Unit { $result = Router::match("Posts::index"); $this->assertEqual('/posts', $result); - $this->expectException('No parameter match found for routes.'); + $ex = "No parameter match found for URL "; + $ex .= "`('controller' => 'sessions', 'action' => 'create', 'id' => 'foo')`."; + $this->expectException($ex); $result = Router::match(array("Sessions::create", 'id' => 'foo')); } @@ -242,7 +244,10 @@ class RouterTest extends \lithium\test\Unit { $expected = '/posts/4bbf25bd8ead0e5180130000'; $this->assertEqual($expected, $result); - $this->expectException('No parameter match found for routes.'); + $ex = "No parameter match found for URL `("; + $ex .= "'controller' => 'posts', 'action' => 'view', 'id' => '4bbf25bd8ead0e5180130000')`."; + $this->expectException($ex); + $result = Router::match(array( 'controller' => 'posts', 'action' => 'view', 'id' => '4bbf25bd8ead0e5180130000' )); @@ -292,7 +297,10 @@ class RouterTest extends \lithium\test\Unit { Router::connect('/login', array('controller' => 'sessions', 'action' => 'add')); $result = Router::match(array('controller' => 'sessions', 'action' => 'add')); $this->assertEqual('/login', $result); - $this->expectException('No parameter match found for routes.'); + + $this->expectException( + "No parameter match found for URL `('controller' => 'sessions', 'action' => 'index')`." + ); Router::match(array('controller' => 'sessions', 'action' => 'index')); } @@ -305,7 +313,9 @@ class RouterTest extends \lithium\test\Unit { Router::connect('/{:controller}'); $this->assertEqual('/posts', Router::match(array('controller' => 'posts'))); - $this->expectException('No parameter match found for routes.'); + $this->expectException( + "No parameter match found for URL `('controller' => 'posts', 'action' => 'view')`." + ); Router::match(array('controller' => 'posts', 'action' => 'view')); } @@ -330,7 +340,9 @@ class RouterTest extends \lithium\test\Unit { $result = Router::match(array('controller' => 'posts', 'action' => 'view')); $this->assertEqual('/posts/view', $result); - $this->expectException('No parameter match found for routes.'); + $ex = "No parameter match found for URL "; + $ex .= "`('controller' => 'posts', 'action' => 'view', 'id' => '2')`."; + $this->expectException($ex); Router::match(array('controller' => 'posts', 'action' => 'view', 'id' => '2')); } @@ -406,6 +418,13 @@ class RouterTest extends \lithium\test\Unit { $expected = '/my/web/path/posts'; $this->assertEqual($expected, $result); + $request = new Request(array('base' => '/my/web/path')); + $result = Router::match('/some/where', $request, array('absolute' => true)); + $prefix = $this->request->env('HTTPS') ? 'https://' : 'http://'; + $prefix .= $this->request->env('HTTP_HOST'); + $this->assertEqual($prefix . '/my/web/path/some/where', $result); + + $result = Router::match('mailto:foo@localhost'); $expected = 'mailto:foo@localhost'; $this->assertEqual($expected, $result); diff --git a/libraries/lithium/tests/cases/net/http/ServiceTest.php b/libraries/lithium/tests/cases/net/http/ServiceTest.php index cdb7437..b25ae69 100644 --- a/libraries/lithium/tests/cases/net/http/ServiceTest.php +++ b/libraries/lithium/tests/cases/net/http/ServiceTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/net/socket/ContextTest.php b/libraries/lithium/tests/cases/net/socket/ContextTest.php index 4843dfd..2f917b6 100644 --- a/libraries/lithium/tests/cases/net/socket/ContextTest.php +++ b/libraries/lithium/tests/cases/net/socket/ContextTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -17,52 +17,55 @@ class ContextTest extends \lithium\test\Unit { protected $_testConfig = array( 'persistent' => false, 'scheme' => 'http', - 'host' => 'localhost', + 'host' => 'google.com', 'port' => 80, - 'timeout' => 30 + 'timeout' => 4, + 'classes' => array('request' => 'lithium\net\http\Request') ); - protected $_testUrl = 'http://localhost'; + protected $_testUrl = 'http://google.com'; - public function setUp() { - $this->socket = new Context($this->_testConfig); - $message = "Could not open {$this->_testUrl} - skipping " . __CLASS__; - $this->skipIf(!fopen($this->_testUrl, 'r'), $message); - } - - public function tearDown() { - unset($this->socket); + public function skip() { + $this->skipIf(dns_check_record("google.com") === false, "No internet connection."); } public function testConstruct() { - $subject = new Context(array('timeout' => 300)); + $subject = new Context(array('timeout' => 300) + $this->_testConfig); $this->assertTrue(300, $subject->timeout()); - $subject->close(); unset($subject); } public function testGetSetTimeout() { - $this->assertEqual(30, $this->socket->timeout()); - $this->assertEqual(25, $this->socket->timeout(25)); - $this->assertEqual(25, $this->socket->timeout()); + $subject = new Context($this->_testConfig); + $this->assertEqual(4, $subject->timeout()); + $this->assertEqual(25, $subject->timeout(25)); + $this->assertEqual(25, $subject->timeout()); - $this->socket->open(); - $this->assertEqual(25, $this->socket->timeout()); + $subject->open(); + $this->assertEqual(25, $subject->timeout()); - $result = stream_context_get_options($this->socket->resource()); + $result = stream_context_get_options($subject->resource()); $this->assertEqual(25, $result['http']['timeout']); } public function testOpen() { - $this->assertTrue(is_resource($this->socket->open())); + $stream = new Context($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); } public function testClose() { - $this->assertEqual(true, $this->socket->close()); + $stream = new Context($this->_testConfig); + $this->assertEqual(true, $stream->close()); } public function testEncoding() { - $this->assertEqual(false, $this->socket->encoding()); + $stream = new Context($this->_testConfig); + $this->assertEqual(false, $stream->encoding()); + } + + public function testEof() { + $stream = new Context($this->_testConfig); + $this->assertTrue(true, $stream->eof()); } public function testMessageInConfig() { @@ -74,11 +77,42 @@ class ContextTest extends \lithium\test\Unit { $stream = new Context($this->_testConfig); $this->assertTrue(is_resource($stream->open())); $this->assertTrue(is_resource($stream->resource())); + $this->assertEqual(1, $stream->write()); + $this->assertPattern("/^HTTP/", (string) $stream->read()); + } - $response = $stream->send(new Request(), array('response' => 'lithium\net\http\Response')); - $this->assertTrue($response instanceof Response); + public function testSendWithNull() { + $stream = new Context($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send( + new Request($this->_testConfig), + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof Response); + $this->assertPattern("/^HTTP/", (string) $result); + $this->assertTrue($stream->eof()); + } + + public function testSendWithArray() { + $stream = new Context($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send($this->_testConfig, + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof Response); + $this->assertPattern("/^HTTP/", (string) $result); + $this->assertTrue($stream->eof()); + } - $this->assertEqual(trim(file_get_contents($this->_testUrl)), trim($response->body())); + public function testSendWithObject() { + $stream = new Context($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send( + new Request($this->_testConfig), + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof Response); + $this->assertPattern("/^HTTP/", (string) $result); $this->assertTrue($stream->eof()); } } diff --git a/libraries/lithium/tests/cases/net/socket/CurlTest.php b/libraries/lithium/tests/cases/net/socket/CurlTest.php index b9edebc..1fba483 100644 --- a/libraries/lithium/tests/cases/net/socket/CurlTest.php +++ b/libraries/lithium/tests/cases/net/socket/CurlTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -16,12 +16,13 @@ class CurlTest extends \lithium\test\Unit { protected $_testConfig = array( 'persistent' => false, 'scheme' => 'http', - 'host' => 'localhost', + 'host' => 'google.com', 'port' => 80, - 'timeout' => 2 + 'timeout' => 2, + 'classes' => array('request' => 'lithium\net\http\Request') ); - protected $_testUrl = 'http://localhost'; + protected $_testUrl = 'http://google.com'; /** * Skip the test if curl is not available in your PHP installation. @@ -31,6 +32,13 @@ class CurlTest extends \lithium\test\Unit { public function skip() { $message = 'Your PHP installation was not compiled with curl support.'; $this->skipIf(!function_exists('curl_init'), $message); + + $config = $this->_testConfig; + $url = "{$config['scheme']}://{$config['host']}"; + $message = "Could not open {$url} - skipping " . __CLASS__; + $this->skipIf(!curl_init($url), $message); + + $this->skipIf(dns_check_record("google.com") === false, "No internet connection."); } public function testAllMethodsNoConnection() { @@ -89,14 +97,40 @@ class CurlTest extends \lithium\test\Unit { $stream = new Curl($this->_testConfig); $this->assertTrue(is_resource($stream->open())); $this->assertTrue(is_resource($stream->resource())); + $this->assertEqual(1, $stream->write()); + $this->assertPattern("/^HTTP/", (string) $stream->read()); + } + + public function testSendWithNull() { + $stream = new Curl($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send( + new Request($this->_testConfig), + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof \lithium\net\http\Response); + $this->assertPattern("/^HTTP/", (string) $result); + } - $stream->set(CURLOPT_URL, $this->_testUrl); - $this->assertTrue($stream->write(null)); - $this->assertTrue($stream->read()); + public function testSendWithArray() { + $stream = new Curl($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send($this->_testConfig, + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof \lithium\net\http\Response); + $this->assertPattern("/^HTTP/", (string) $result); + } - $response = $stream->send(new Request(), array('response' => 'lithium\net\http\Response')); - $this->assertEqual(trim(file_get_contents($this->_testUrl)), trim($response->body())); - $this->assertNull($stream->eof()); + public function testSendWithObject() { + $stream = new Curl($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send( + new Request($this->_testConfig), + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof \lithium\net\http\Response); + $this->assertPattern("/^HTTP/", (string) $result); } } diff --git a/libraries/lithium/tests/cases/net/socket/StreamTest.php b/libraries/lithium/tests/cases/net/socket/StreamTest.php index 83ab4d8..ea86700 100644 --- a/libraries/lithium/tests/cases/net/socket/StreamTest.php +++ b/libraries/lithium/tests/cases/net/socket/StreamTest.php @@ -2,24 +2,32 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\net\socket; +use lithium\net\http\Request; +use lithium\net\http\Response; use lithium\net\socket\Stream; class StreamTest extends \lithium\test\Unit { protected $_testConfig = array( 'persistent' => false, - 'scheme' => 'tcp', - 'host' => 'localhost', + 'scheme' => 'http', + 'host' => 'google.com', 'port' => 80, - 'timeout' => 2 + 'timeout' => 2, + 'classes' => array('request' => 'lithium\net\http\Request') ); + public function skip() { + $host = $this->_testConfig['host']; + $this->skipIf(dns_check_record($host) === false, "No internet connection."); + } + public function testAllMethodsNoConnection() { $stream = new Stream(array('scheme' => null)); $this->assertFalse($stream->open()); @@ -29,7 +37,7 @@ class StreamTest extends \lithium\test\Unit { $this->assertFalse($stream->write(null)); $this->assertFalse($stream->read()); $this->assertTrue($stream->eof()); - $this->assertNull($stream->send('')); + $this->assertNull($stream->send(new Request())); } public function testOpen() { @@ -76,30 +84,47 @@ class StreamTest extends \lithium\test\Unit { public function testWriteAndRead() { $stream = new Stream($this->_testConfig); - $result = $stream->open(); - $data = "GET / HTTP/1.1\r\n"; - $data .= "Host: localhost\r\n"; - $data .= "Connection: Close\r\n\r\n"; - $this->assertTrue($stream->write($data)); + $this->assertTrue(is_resource($stream->open())); + $this->assertTrue(is_resource($stream->resource())); - $result = $stream->eof(); - $this->assertFalse($result); + $result = $stream->write(); + $this->assertEqual(83, $result); + $this->assertPattern("/^HTTP/", (string) $stream->read()); + } + + public function testSendWithNull() { + $stream = new Stream($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send( + new Request($this->_testConfig), + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof Response); + $this->assertPattern("/^HTTP/", (string) $result); + $this->assertTrue($stream->eof()); + } - $result = $stream->read(); - $this->assertPattern("/^HTTP/", $result); + public function testSendWithArray() { + $stream = new Stream($this->_testConfig); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send($this->_testConfig, + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof Response); + $this->assertPattern("/^HTTP/", (string) $result); + $this->assertTrue($stream->eof()); } - public function testSend() { + public function testSendWithObject() { $stream = new Stream($this->_testConfig); - $result = $stream->open(); - $data = "GET / HTTP/1.1\r\n"; - $data .= "Host: localhost\r\n"; - $data .= "Connection: Close\r\n\r\n"; - - $result = $stream->send($data, array('classes' => array( - 'response' => '\lithium\net\http\Response' - ))); - $this->assertNotEqual(null, $result); + $this->assertTrue(is_resource($stream->open())); + $result = $stream->send( + new Request($this->_testConfig), + array('response' => 'lithium\net\http\Response') + ); + $this->assertTrue($result instanceof Response); + $this->assertPattern("/^HTTP/", (string) $result); + $this->assertTrue($stream->eof()); } } diff --git a/libraries/lithium/tests/cases/security/AuthTest.php b/libraries/lithium/tests/cases/security/AuthTest.php index bb4e4b2..32eac7b 100644 --- a/libraries/lithium/tests/cases/security/AuthTest.php +++ b/libraries/lithium/tests/cases/security/AuthTest.php @@ -20,7 +20,7 @@ class AuthTest extends \lithium\test\Unit { Auth::config(array( 'test' => array( - 'adapter' => '\lithium\tests\mocks\security\auth\adapter\MockAuthAdapter' + 'adapter' => 'lithium\tests\mocks\security\auth\adapter\MockAuthAdapter' ) )); } @@ -73,7 +73,7 @@ class AuthTest extends \lithium\test\Unit { public function testNoConfigurations() { Auth::reset(); $this->assertIdentical(array(), Auth::config()); - $this->expectException("Configuration 'user' has not been defined."); + $this->expectException("Configuration `user` has not been defined."); Auth::check('user'); } } diff --git a/libraries/lithium/tests/cases/security/auth/adapter/FormTest.php b/libraries/lithium/tests/cases/security/auth/adapter/FormTest.php index a81bb1d..eac9e72 100644 --- a/libraries/lithium/tests/cases/security/auth/adapter/FormTest.php +++ b/libraries/lithium/tests/cases/security/auth/adapter/FormTest.php @@ -24,20 +24,34 @@ class FormTest extends \lithium\test\Unit { $request->data = array('username' => 'Person', 'password' => 'password'); $result = $subject->check($request); - $expected = array('username' => 'Person', 'password' => sha1('password')); + $password = 'b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7'; + $password .= '785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86'; + $expected = array('username' => 'Person') + compact('password'); $this->assertEqual($expected, $result); } public function testLoginWithFilters() { - $subject = new Form(array('model' => __CLASS__, 'filters' => array( - 'username' => 'sha1' - ))); + $subject = new Form(array('model' => __CLASS__, 'filters' => array('username' => 'sha1'))); $request = new Request(); $request->data = array('username' => 'Person', 'password' => 'password'); + $password = 'b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7'; + $password .= '785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86'; + $expected = array('username' => sha1('Person')) + compact('password'); + $this->assertEqual($expected, $subject->check($request)); + } + + /** + * Tests that attempted exploitation via malformed credential submission. + * + * @return void + */ + public function testLoginWithArray() { + $subject = new Form(array('model' => __CLASS__)); + $request = new Request(); + $request->data = array('username' => array('!=' => ''), 'password' => ''); $result = $subject->check($request); - $expected = array('username' => sha1('Person'), 'password' => sha1('password')); - $this->assertEqual($expected, $result); + $this->assertEqual('Array', $result['username']); } /** diff --git a/libraries/lithium/tests/cases/security/auth/adapter/HttpTest.php b/libraries/lithium/tests/cases/security/auth/adapter/HttpTest.php index 4443491..4dc2c89 100644 --- a/libraries/lithium/tests/cases/security/auth/adapter/HttpTest.php +++ b/libraries/lithium/tests/cases/security/auth/adapter/HttpTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -48,7 +48,8 @@ class HttpTest extends \lithium\test\Unit { $this->assertFalse($result); $this->assertPattern('/Digest/', $http->headers[0]); - $this->assertPattern('/qop="auth"/', $http->headers[0]); + $this->assertPattern('/realm="app",/', $http->headers[0]); + $this->assertPattern('/qop="auth",/', $http->headers[0]); $this->assertPattern('/nonce=/', $http->headers[0]); } diff --git a/libraries/lithium/tests/cases/storage/CacheTest.php b/libraries/lithium/tests/cases/storage/CacheTest.php index e227d16..5744026 100644 --- a/libraries/lithium/tests/cases/storage/CacheTest.php +++ b/libraries/lithium/tests/cases/storage/CacheTest.php @@ -2,15 +2,16 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\storage; +use SplFileInfo; +use lithium\core\Libraries; use lithium\storage\Cache; use lithium\util\Collection; -use \SplFileInfo; class CacheTest extends \lithium\test\Unit { @@ -553,14 +554,14 @@ class CacheTest extends \lithium\test\Unit { } public function testIntegrationFileAdapterWrite() { - $directory = new SplFileInfo(LITHIUM_APP_PATH . "/resources/tmp/cache/"); + $directory = new SplFileInfo(Libraries::get(true, 'resources') . "/tmp/cache/"); $accessible = ($directory->isDir() && $directory->isReadable() && $directory->isWritable()); $message = "$directory does not have the proper permissions."; $this->skipIf(!$accessible, $message); $config = array('default' => array( 'adapter' => 'File', - 'path' => LITHIUM_APP_PATH . '/resources/tmp/cache', + 'path' => Libraries::get(true, 'resources') . '/tmp/cache', 'filters' => array() )); Cache::config($config); @@ -569,24 +570,24 @@ class CacheTest extends \lithium\test\Unit { $this->assertTrue($result); $time = time() + 60; - $result = file_get_contents(LITHIUM_APP_PATH . '/resources/tmp/cache/key'); + $result = file_get_contents(Libraries::get(true, 'resources') . '/tmp/cache/key'); $expected = "{:expiry:$time}\nvalue"; $this->assertEqual($result, $expected); - $result = unlink(LITHIUM_APP_PATH . '/resources/tmp/cache/key'); + $result = unlink(Libraries::get(true, 'resources') . '/tmp/cache/key'); $this->assertTrue($result); - $this->assertFalse(file_exists(LITHIUM_APP_PATH . '/resources/tmp/cache/key')); + $this->assertFalse(file_exists(Libraries::get(true, 'resources') . '/tmp/cache/key')); } public function testIntegrationFileAdapterWithStrategies() { - $directory = new SplFileInfo(LITHIUM_APP_PATH . "/resources/tmp/cache/"); + $directory = new SplFileInfo(Libraries::get(true, 'resources') . "/tmp/cache/"); $accessible = ($directory->isDir() && $directory->isReadable() && $directory->isWritable()); $message = "$directory does not have the proper permissions."; $this->skipIf(!$accessible, $message); $config = array('default' => array( 'adapter' => 'File', - 'path' => LITHIUM_APP_PATH . '/resources/tmp/cache', + 'path' => Libraries::get(true, 'resources') . '/tmp/cache', 'filters' => array(), 'strategies' => array('Serializer') )); @@ -597,7 +598,7 @@ class CacheTest extends \lithium\test\Unit { $this->assertTrue($result); $time = time() + 60; - $result = file_get_contents(LITHIUM_APP_PATH . '/resources/tmp/cache/key'); + $result = file_get_contents(Libraries::get(true, 'resources') . '/tmp/cache/key'); $expected = "{:expiry:$time}\na:1:{s:4:\"some\";s:4:\"data\";}"; $this->assertEqual($result, $expected); @@ -605,20 +606,20 @@ class CacheTest extends \lithium\test\Unit { $result = Cache::read('default', 'key'); $this->assertEqual($data, $result); - $result = unlink(LITHIUM_APP_PATH . '/resources/tmp/cache/key'); + $result = unlink(Libraries::get(true, 'resources') . '/tmp/cache/key'); $this->assertTrue($result); - $this->assertFalse(file_exists(LITHIUM_APP_PATH . '/resources/tmp/cache/key')); + $this->assertFalse(file_exists(Libraries::get(true, 'resources') . '/tmp/cache/key')); } public function testIntegrationFileAdapterMultipleStrategies() { - $directory = new SplFileInfo(LITHIUM_APP_PATH . "/resources/tmp/cache/"); + $directory = new SplFileInfo(Libraries::get(true, 'resources') . "/tmp/cache/"); $accessible = ($directory->isDir() && $directory->isReadable() && $directory->isWritable()); $message = "$directory does not have the proper permissions."; $this->skipIf(!$accessible, $message); $config = array('default' => array( 'adapter' => 'File', - 'path' => LITHIUM_APP_PATH . '/resources/tmp/cache', + 'path' => Libraries::get(true, 'resources') . '/tmp/cache', 'filters' => array(), 'strategies' => array('Serializer', 'Base64') )); @@ -629,7 +630,7 @@ class CacheTest extends \lithium\test\Unit { $this->assertTrue($result); $time = time() + 60; - $result = file_get_contents(LITHIUM_APP_PATH . '/resources/tmp/cache/key'); + $result = file_get_contents(Libraries::get(true, 'resources') . '/tmp/cache/key'); $expected = "{:expiry:$time}\nYToxOntzOjQ6InNvbWUiO3M6NDoiZGF0YSI7fQ=="; $this->assertEqual($result, $expected); @@ -637,9 +638,9 @@ class CacheTest extends \lithium\test\Unit { $result = Cache::read('default', 'key'); $this->assertEqual($data, $result); - $result = unlink(LITHIUM_APP_PATH . '/resources/tmp/cache/key'); + $result = unlink(Libraries::get(true, 'resources') . '/tmp/cache/key'); $this->assertTrue($result); - $this->assertFalse(file_exists(LITHIUM_APP_PATH . '/resources/tmp/cache/key')); + $this->assertFalse(file_exists(Libraries::get(true, 'resources') . '/tmp/cache/key')); } } diff --git a/libraries/lithium/tests/cases/storage/SessionTest.php b/libraries/lithium/tests/cases/storage/SessionTest.php index a0cb348..0fc5542 100644 --- a/libraries/lithium/tests/cases/storage/SessionTest.php +++ b/libraries/lithium/tests/cases/storage/SessionTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -166,7 +166,7 @@ class SessionTest extends \lithium\test\Unit { /** * Tests querying session keys from the primary adapter. - * The memory adapter returns a UUID based on a server variable for portability. + * The memory adapter returns a UUID. * * @return void */ @@ -189,7 +189,7 @@ class SessionTest extends \lithium\test\Unit { public function testSessionState() { $this->assertTrue(Session::isStarted()); $this->assertTrue(Session::isStarted('default')); - $this->expectException("Configuration 'invalid' has not been defined."); + $this->expectException("Configuration `invalid` has not been defined."); $this->assertFalse(Session::isStarted('invalid')); } @@ -200,7 +200,7 @@ class SessionTest extends \lithium\test\Unit { public function testSessionStateResetNamed() { Session::reset(); - $this->expectException("Configuration 'default' has not been defined."); + $this->expectException("Configuration `default` has not been defined."); $this->assertFalse(Session::isStarted('default')); } diff --git a/libraries/lithium/tests/cases/storage/cache/adapter/ApcTest.php b/libraries/lithium/tests/cases/storage/cache/adapter/ApcTest.php index 1295cbb..5879178 100644 --- a/libraries/lithium/tests/cases/storage/cache/adapter/ApcTest.php +++ b/libraries/lithium/tests/cases/storage/cache/adapter/ApcTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/storage/cache/adapter/FileTest.php b/libraries/lithium/tests/cases/storage/cache/adapter/FileTest.php index eef9aa2..01cc90f 100644 --- a/libraries/lithium/tests/cases/storage/cache/adapter/FileTest.php +++ b/libraries/lithium/tests/cases/storage/cache/adapter/FileTest.php @@ -2,14 +2,15 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\storage\cache\adapter; +use SplFileInfo; +use lithium\core\Libraries; use lithium\storage\cache\adapter\File; -use \SplFileInfo; class FileTest extends \lithium\test\Unit { @@ -28,21 +29,21 @@ class FileTest extends \lithium\test\Unit { * @return void */ public function skip() { - $directory = new SplFileInfo(LITHIUM_APP_PATH . "/resources/tmp/cache/"); + $directory = new SplFileInfo(Libraries::get(true, 'resources') . "/tmp/cache/"); $accessible = ($directory->isDir() && $directory->isReadable() && $directory->isWritable()); $message = 'The File cache adapter path does not have the proper permissions.'; $this->skipIf(!$accessible, $message); } public function setUp() { - $this->_hasEmpty = file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/empty"); + $this->_hasEmpty = file_exists(Libraries::get(true, 'resources') . "/tmp/cache/empty"); $this->File = new File(); } public function tearDown() { if ($this->_hasEmpty) { - touch(LITHIUM_APP_PATH . "/resources/tmp/cache/empty"); - touch(LITHIUM_APP_PATH . "/resources/tmp/cache/templates/empty"); + touch(Libraries::get(true, 'resources') . "/tmp/cache/empty"); + touch(Libraries::get(true, 'resources') . "/tmp/cache/templates/empty"); } unset($this->File); } @@ -66,14 +67,14 @@ class FileTest extends \lithium\test\Unit { $expected = 25; $this->assertEqual($expected, $result); - $this->assertTrue(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + $this->assertTrue(file_exists(Libraries::get(true, 'resources') . "/tmp/cache/{$key}")); $this->assertEqual( - file_get_contents(LITHIUM_APP_PATH . "/resources/tmp/cache/$key"), + file_get_contents(Libraries::get(true, 'resources') . "/tmp/cache/{$key}"), "{:expiry:$time}\ndata" ); - $this->assertTrue(unlink(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); - $this->assertFalse(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + $this->assertTrue(unlink(Libraries::get(true, 'resources') . "/tmp/cache/{$key}")); + $this->assertFalse(file_exists(Libraries::get(true, 'resources') . "/tmp/cache/{$key}")); } public function testWriteDefaultCacheExpiry() { @@ -90,14 +91,14 @@ class FileTest extends \lithium\test\Unit { $expected = 25; $this->assertEqual($expected, $result); - $this->assertTrue(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + $this->assertTrue(file_exists(Libraries::get(true, 'resources') . "/tmp/cache/{$key}")); $this->assertEqual( - file_get_contents(LITHIUM_APP_PATH . "/resources/tmp/cache/$key"), - "{:expiry:$time}\ndata" + file_get_contents(Libraries::get(true, 'resources') . "/tmp/cache/{$key}"), + "{:expiry:{$time}}\ndata" ); - $this->assertTrue(unlink(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); - $this->assertFalse(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + $this->assertTrue(unlink(Libraries::get(true, 'resources') . "/tmp/cache/{$key}")); + $this->assertFalse(file_exists(Libraries::get(true, 'resources') . "/tmp/cache/{$key}")); } public function testRead() { @@ -107,16 +108,15 @@ class FileTest extends \lithium\test\Unit { $closure = $this->File->read($key); $this->assertTrue(is_callable($closure)); - file_put_contents(LITHIUM_APP_PATH . "/resources/tmp/cache/$key", "{:expiry:$time}\ndata"); - $this->assertTrue(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + $path = Libraries::get(true, 'resources') . "/tmp/cache/{$key}"; + file_put_contents($path, "{:expiry:$time}\ndata"); + $this->assertTrue(file_exists($path)); $params = compact('key'); $result = $closure($this->File, $params, null); - $expected = 'data'; - $this->assertEqual($expected, $result); + $this->assertEqual('data', $result); - $this->assertTrue(unlink(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); - $this->assertFalse(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + unlink($path); $key = 'non_existent'; $params = compact('key'); @@ -133,47 +133,47 @@ class FileTest extends \lithium\test\Unit { $closure = $this->File->read($key); $this->assertTrue(is_callable($closure)); + $path = Libraries::get(true, 'resources') . "/tmp/cache/{$key}"; - file_put_contents(LITHIUM_APP_PATH . "/resources/tmp/cache/$key", "{:expiry:$time}\ndata"); - $this->assertTrue(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + file_put_contents($path, "{:expiry:$time}\ndata"); + $this->assertTrue(file_exists($path)); sleep(2); $params = compact('key'); - $result = $closure($this->File, $params, null); - $this->assertFalse($result); + $this->assertFalse($closure($this->File, $params, null)); } public function testDelete() { $key = 'key_to_delete'; $time = time() + 1; + $path = Libraries::get(true, 'resources') . "/tmp/cache/{$key}"; - file_put_contents(LITHIUM_APP_PATH . "/resources/tmp/cache/$key", "{:expiry:$time}\ndata"); - $this->assertTrue(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + file_put_contents($path, "{:expiry:$time}\ndata"); + $this->assertTrue(file_exists($path)); $closure = $this->File->delete($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->File, $params, null); - $this->assertTrue($result); + $this->assertTrue($closure($this->File, $params, null)); $key = 'non_existent'; $params = compact('key'); - $result = $closure($this->File, $params, null); - $this->assertFalse($result); + $this->assertFalse($closure($this->File, $params, null)); } public function testClear() { $key = 'key_to_clear'; $time = time() + 1; - file_put_contents(LITHIUM_APP_PATH . "/resources/tmp/cache/$key", "{:expiry:$time}\ndata"); + $path = Libraries::get(true, 'resources') . "/tmp/cache/{$key}"; + file_put_contents($path, "{:expiry:$time}\ndata"); $result = $this->File->clear(); $this->assertTrue($result); - $this->assertFalse(file_exists(LITHIUM_APP_PATH . "/resources/tmp/cache/$key")); + $this->assertFalse(file_exists($path)); - $result = touch(LITHIUM_APP_PATH . "/resources/tmp/cache/empty"); + $result = touch(Libraries::get(true, 'resources') . "/tmp/cache/empty"); $this->assertTrue($result); } @@ -188,8 +188,6 @@ class FileTest extends \lithium\test\Unit { $result = $this->File->decrement($key); $this->assertEqual(false, $result); } - - } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/storage/cache/adapter/MemcacheTest.php b/libraries/lithium/tests/cases/storage/cache/adapter/MemcacheTest.php index a266133..7930a7a 100644 --- a/libraries/lithium/tests/cases/storage/cache/adapter/MemcacheTest.php +++ b/libraries/lithium/tests/cases/storage/cache/adapter/MemcacheTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/storage/cache/adapter/MemoryTest.php b/libraries/lithium/tests/cases/storage/cache/adapter/MemoryTest.php index 01dc020..b42c33d 100644 --- a/libraries/lithium/tests/cases/storage/cache/adapter/MemoryTest.php +++ b/libraries/lithium/tests/cases/storage/cache/adapter/MemoryTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/storage/cache/adapter/RedisTest.php b/libraries/lithium/tests/cases/storage/cache/adapter/RedisTest.php index 4ac9413..40170e9 100644 --- a/libraries/lithium/tests/cases/storage/cache/adapter/RedisTest.php +++ b/libraries/lithium/tests/cases/storage/cache/adapter/RedisTest.php @@ -2,57 +2,65 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\storage\cache\adapter; +use Exception; +use Redis as RedisCore; use lithium\storage\cache\adapter\Redis; class RedisTest extends \lithium\test\Unit { + public function __construct(array $config = array()) { + $defaults = array( + 'host' => '127.0.0.1', + 'port' => 6379, + ); + parent::__construct($config + $defaults); + } + /** * Skip the test if the Redis extension is unavailable. * * @return void */ public function skip() { - $extensionExists = extension_loaded('redis'); - $message = 'The redis extension is not installed.'; - $this->skipIf(!$extensionExists, $message); + $this->skipIf(!Redis::enabled(), 'The redis extension is not installed.'); + + $redis = new RedisCore(); + $cfg = $this->_config; - $R = new \Redis(); - $result = null; try { - $R->connect('127.0.0.1', 6379); - } catch (\Exception $e) { - $message = 'redis-server does not appear to be running on 127.0.0.1:6379'; - $result = $R->info(); - $this->skipIf(empty($result), $message); + $redis->connect($cfg['host'], $cfg['port']); + } catch (Exception $e) { + $info = $redis->info(); + $msg = "redis-server does not appear to be running on {$cfg['host']}:{$cfg['port']}"; + $this->skipIf(!$info, $msg); } - unset($R); + unset($redis); } public function setUp() { - $this->server = array('host' => '127.0.0.1', 'port' => 6379); - $this->_Redis = new \Redis(); - $this->_Redis->connect($this->server['host'], $this->server['port']); - $this->Redis = new Redis(); + $this->_redis = new RedisCore(); + $this->_redis->connect($this->_config['host'], $this->_config['port']); + $this->redis = new Redis(); } public function tearDown() { - $this->_Redis->flushdb(); + $this->_redis->flushdb(); } public function testEnabled() { - $redis = $this->Redis; + $redis = $this->redis; $this->assertTrue($redis::enabled()); } public function testInit() { - $Redis = new Redis(); - $this->assertTrue($Redis::$connection instanceof \Redis); + $redis = new Redis(); + $this->assertTrue($redis->connection instanceof RedisCore); } public function testSimpleWrite() { @@ -61,21 +69,21 @@ class RedisTest extends \lithium\test\Unit { $expiry = '+5 seconds'; $time = strtotime($expiry); - $closure = $this->Redis->write($key, $data, $expiry); + $closure = $this->redis->write($key, $data, $expiry); $this->assertTrue(is_callable($closure)); $params = compact('key', 'data', 'expiry'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = $data; $this->assertEqual($expected, $result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($expected, $result); - $result = $this->_Redis->ttl($key); + $result = $this->_redis->ttl($key); $this->assertEqual($time - time(), $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); $key = 'another_key'; @@ -83,103 +91,112 @@ class RedisTest extends \lithium\test\Unit { $expiry = '+1 minute'; $time = strtotime($expiry); - $closure = $this->Redis->write($key, $data, $expiry); + $closure = $this->redis->write($key, $data, $expiry); $this->assertTrue(is_callable($closure)); $params = compact('key', 'data', 'expiry'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = $data; $this->assertEqual($expected, $result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($expected, $result); - $result = $this->_Redis->ttl($key); + $result = $this->_redis->ttl($key); $this->assertEqual($time - time(), $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); } public function testWriteDefaultCacheExpiry() { - $Redis = new Redis(array('expiry' => '+5 seconds')); + $redis = new Redis(array('expiry' => '+5 seconds')); $key = 'default_key'; $data = 'value'; $time = strtotime('+5 seconds'); - $closure = $Redis->write($key, $data); + $closure = $redis->write($key, $data); $this->assertTrue(is_callable($closure)); $params = compact('key', 'data'); - $result = $closure($Redis, $params, null); + $result = $closure($redis, $params, null); $expected = $data; $this->assertEqual($expected, $result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($expected, $result); - $result = $this->_Redis->ttl($key); + $result = $this->_redis->ttl($key); $this->assertEqual($time - time(), $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); } + public function testWriteNoCacheExpiry() { + $redis = new Redis(array('expiry' => null)); + $key = 'default_key'; + $data = 'value'; + $redis->write($key, $data)->__invoke(null, compact('key', 'data'), null); + $this->assertEqual($data, $this->_redis->get($key)); + $this->assertTrue($this->_redis->delete($key)); + } + public function testSimpleRead() { $key = 'read_key'; $data = 'read data'; - $result = $this->_Redis->set($key, $data); + $result = $this->_redis->set($key, $data); $this->assertTrue($result); - $closure = $this->Redis->read($key); + $closure = $this->redis->read($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = $data; $this->assertEqual($expected, $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); $key = 'another_read_key'; $data = 'read data'; $time = strtotime('+1 minute'); - $result = $this->_Redis->set($key, $data); + $result = $this->_redis->set($key, $data); $this->assertTrue($result); - $result = $this->_Redis->ttl($key); + $result = $this->_redis->ttl($key); $this->assertTrue($result); - $closure = $this->Redis->read($key); + $closure = $this->redis->read($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = $data; $this->assertEqual($expected, $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); } public function testMultiRead() { $data = array('key1' => 'value1', 'key2' => 'value2'); - $result = $this->_Redis->mset($data); + $result = $this->_redis->mset($data); $this->assertTrue($result); - $closure = $this->Redis->read(array_keys($data)); + $closure = $this->redis->read(array_keys($data)); $this->assertTrue(is_callable($closure)); $params = array('key' => array_keys($data)); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = array_values($data); $this->assertEqual($expected, $result); foreach ($data as $k => $v) { - $result = $this->_Redis->delete($k); + $result = $this->_redis->delete($k); $this->assertTrue($result); } } @@ -189,26 +206,26 @@ class RedisTest extends \lithium\test\Unit { $expiry = '+5 seconds'; $time = strtotime($expiry); - $closure = $this->Redis->write($key, $expiry); + $closure = $this->redis->write($key, $expiry); $this->assertTrue(is_callable($closure)); $params = array('key' => $key, 'data' => $expiry, 'expiry' => null); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = array('key1' => true, 'key2' => true); $this->assertEqual($expected, $result); - $result = $this->_Redis->getMultiple(array_keys($key)); + $result = $this->_redis->getMultiple(array_keys($key)); $expected = array_values($key); $this->assertEqual($expected, $result); } public function testReadKeyThatDoesNotExist() { $key = 'does_not_exist'; - $closure = $this->Redis->read($key); + $closure = $this->redis->read($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertFalse($result); } @@ -218,26 +235,26 @@ class RedisTest extends \lithium\test\Unit { $data = 'data to delete'; $time = strtotime('+1 minute'); - $result = $this->_Redis->set($key, $data); + $result = $this->_redis->set($key, $data); $this->assertTrue($result); - $closure = $this->Redis->delete($key); + $closure = $this->redis->delete($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertTrue($result); - $this->assertFalse($this->_Redis->delete($key)); + $this->assertFalse($this->_redis->delete($key)); } public function testDeleteNonExistentKey() { $key = 'delete_key'; - $closure = $this->Redis->delete($key); + $closure = $this->redis->delete($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertFalse($result); } @@ -247,67 +264,67 @@ class RedisTest extends \lithium\test\Unit { $expiry = '+5 seconds'; $time = strtotime($expiry); - $closure = $this->Redis->write($key, $data, $expiry); + $closure = $this->redis->write($key, $data, $expiry); $this->assertTrue(is_callable($closure)); $params = compact('key', 'data', 'expiry'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = $data; $this->assertEqual($expected, $result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($expected, $result); - $closure = $this->Redis->read($key); + $closure = $this->redis->read($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $expected = $data; $this->assertEqual($expected, $result); - $closure = $this->Redis->delete($key); + $closure = $this->redis->delete($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertTrue($result); - $this->assertFalse($this->_Redis->get($key)); + $this->assertFalse($this->_redis->get($key)); } public function testClear() { - $result = $this->_Redis->set('key', 'value'); + $result = $this->_redis->set('key', 'value'); $this->assertTrue($result); - $result = $this->_Redis->set('another_key', 'value'); + $result = $this->_redis->set('another_key', 'value'); $this->assertTrue($result); - $result = $this->Redis->clear(); + $result = $this->redis->clear(); $this->assertTrue($result); - $this->assertFalse($this->_Redis->get('key')); - $this->assertFalse($this->_Redis->get('another_key')); + $this->assertFalse($this->_redis->get('key')); + $this->assertFalse($this->_redis->get('another_key')); } public function testDecrement() { $key = 'decrement'; $value = 10; - $result = $this->_Redis->set($key, $value); + $result = $this->_redis->set($key, $value); $this->assertTrue($result); - $closure = $this->Redis->decrement($key); + $closure = $this->redis->decrement($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertEqual($value - 1, $result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($value - 1, $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); } @@ -315,20 +332,20 @@ class RedisTest extends \lithium\test\Unit { $key = 'non_integer'; $value = 'no'; - $result = $this->_Redis->set($key, $value); + $result = $this->_redis->set($key, $value); $this->assertTrue($result); - $closure = $this->Redis->decrement($key); + $closure = $this->redis->decrement($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertFalse($result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($value, $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); } @@ -336,20 +353,20 @@ class RedisTest extends \lithium\test\Unit { $key = 'increment'; $value = 10; - $result = $this->_Redis->set($key, $value); + $result = $this->_redis->set($key, $value); $this->assertTrue($result); - $closure = $this->Redis->increment($key); + $closure = $this->redis->increment($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertEqual($value + 1, $result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($value + 1, $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); } @@ -357,20 +374,20 @@ class RedisTest extends \lithium\test\Unit { $key = 'non_integer_increment'; $value = 'yes'; - $result = $this->_Redis->set($key, $value); + $result = $this->_redis->set($key, $value); $this->assertTrue($result); - $closure = $this->Redis->increment($key); + $closure = $this->redis->increment($key); $this->assertTrue(is_callable($closure)); $params = compact('key'); - $result = $closure($this->Redis, $params, null); + $result = $closure($this->redis, $params, null); $this->assertFalse($result); - $result = $this->_Redis->get($key); + $result = $this->_redis->get($key); $this->assertEqual($value, $result); - $result = $this->_Redis->delete($key); + $result = $this->_redis->delete($key); $this->assertTrue($result); } } diff --git a/libraries/lithium/tests/cases/storage/cache/adapter/XCacheTest.php b/libraries/lithium/tests/cases/storage/cache/adapter/XCacheTest.php index ce95da3..61bc4bf 100644 --- a/libraries/lithium/tests/cases/storage/cache/adapter/XCacheTest.php +++ b/libraries/lithium/tests/cases/storage/cache/adapter/XCacheTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/storage/cache/strategy/Base64Test.php b/libraries/lithium/tests/cases/storage/cache/strategy/Base64Test.php index 7905787..7d96d32 100644 --- a/libraries/lithium/tests/cases/storage/cache/strategy/Base64Test.php +++ b/libraries/lithium/tests/cases/storage/cache/strategy/Base64Test.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/storage/cache/strategy/JsonTest.php b/libraries/lithium/tests/cases/storage/cache/strategy/JsonTest.php index fb506e1..b3c1791 100644 --- a/libraries/lithium/tests/cases/storage/cache/strategy/JsonTest.php +++ b/libraries/lithium/tests/cases/storage/cache/strategy/JsonTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/storage/session/adapter/CookieTest.php b/libraries/lithium/tests/cases/storage/session/adapter/CookieTest.php index e6174f7..98cee75 100644 --- a/libraries/lithium/tests/cases/storage/session/adapter/CookieTest.php +++ b/libraries/lithium/tests/cases/storage/session/adapter/CookieTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of Rad, Inc. (http://union-of-rad.org) + * @copyright Copyright 2011, Union of Rad, Inc. (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -31,7 +31,12 @@ class CookieTest extends \lithium\test\Unit { } public function tearDown() { - $this->_destroyCookie(); + $this->_destroyCookie($this->name); + $cookies = array_keys($_COOKIE); + + foreach ($cookies as $cookie) { + setcookie($cookie, "", time()-1); + } } protected function _destroyCookie($name = null) { @@ -84,7 +89,11 @@ class CookieTest extends \lithium\test\Unit { public function testWriteArrayData() { $key = 'user'; - $value = array('email' => 'test@localhost', 'name' => 'Testy McTesterson'); + $value = array( + 'email' => 'test@localhost', + 'name' => 'Testy McTesterson', + 'address' => array('country' => 'Iran', 'city' => 'Mashhad') + ); $expires = "+2 days"; $path = '/'; @@ -95,7 +104,11 @@ class CookieTest extends \lithium\test\Unit { $expected = compact('expires'); $expected += array('key' => 'user.email', 'value' => 'test@localhost'); - $this->assertCookie($expected, headers_list()); + $this->assertCookie($expected); + + $expected = compact('expires'); + $expected += array('key' => 'user.address.country', 'value' => 'Iran'); + $this->assertCookie($expected); } public function testReadDotSyntax() { @@ -111,7 +124,7 @@ class CookieTest extends \lithium\test\Unit { $this->assertEqual($value, $result); $result = $closure($this->cookie, array('key' => null), null); - $this->assertEqual($_COOKIE, $result); + $this->assertEqual($_COOKIE[$this->name], $result); $key = 'does_not_exist'; $closure = $this->cookie->read($key); @@ -152,7 +165,7 @@ class CookieTest extends \lithium\test\Unit { $this->assertEqual($value, $result); $result = $closure($this->cookie, array('key' => null), null); - $this->assertEqual($_COOKIE, $result); + $this->assertEqual($_COOKIE[$this->name], $result); $key = 'does_not_exist'; $closure = $this->cookie->read($key); @@ -163,6 +176,16 @@ class CookieTest extends \lithium\test\Unit { $this->assertNull($result); } + public function testReadNestedKey() { + $key = 'User.id'; + $closure = $this->cookie->read($key); + $this->assertTrue(is_callable($closure)); + + $params = compact('key'); + $result = $closure($this->cookie, $params, null); + $this->assertNull($result); + } + public function testCheck() { $key = 'read'; $value = 'value to be read'; @@ -184,6 +207,47 @@ class CookieTest extends \lithium\test\Unit { $this->assertFalse($result); } + public function testClearCookie() { + $key = 'clear_key'; + $value = 'clear_value'; + $_COOKIE[$this->name][$key] = $value; + + $closure = $this->cookie->check($key); + $this->assertTrue(is_callable($closure)); + + $params = compact('key'); + $result = $closure($this->cookie, $params, null); + $this->assertTrue($result); + + $closure = $this->cookie->clear(); + $this->assertTrue(is_callable($closure)); + + $params = array(); + $result = $closure($this->cookie, $params, null); + $this->assertTrue($result); + $this->assertNoCookie(compact('key', 'value')); + + } + + public function testDeleteArrayData() { + $key = 'user'; + $value = array('email' => 'user@localhost', 'name' => 'Ali'); + $_COOKIE[$this->name][$key] = $value; + + $closure = $this->cookie->delete($key); + $this->assertTrue(is_callable($closure)); + + $params = compact('key'); + $result = $closure($this->cookie, $params, null); + $this->assertTrue($result); + + $expected = array('key' => 'user.name', 'value' => 'deleted'); + $this->assertCookie($expected); + + $expected = array('key' => 'user.email', 'value' => 'deleted'); + $this->assertCookie($expected); + } + public function testDeleteNonExistentValue() { $key = 'delete'; $value = 'deleted'; @@ -194,7 +258,7 @@ class CookieTest extends \lithium\test\Unit { $params = compact('key'); $result = $closure($this->cookie, $params, null); - $this->assertNull($result); + $this->assertTrue($result); $this->assertCookie(compact('key', 'value', 'path')); } @@ -216,6 +280,7 @@ class CookieTest extends \lithium\test\Unit { $result = $cookie->write($key, $value)->__invoke($cookie, compact('key', 'value'), null); $this->assertCookie(compact('key', 'value')); } + } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/storage/session/adapter/PhpTest.php b/libraries/lithium/tests/cases/storage/session/adapter/PhpTest.php index 709b984..12c2ffd 100644 --- a/libraries/lithium/tests/cases/storage/session/adapter/PhpTest.php +++ b/libraries/lithium/tests/cases/storage/session/adapter/PhpTest.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of Rad, Inc. (http://union-of-rad.org) + * @copyright Copyright 2011, Union of Rad, Inc. (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\storage\session\adapter; +use lithium\core\Libraries; use lithium\storage\session\adapter\Php; use lithium\tests\mocks\storage\session\adapter\MockPhp; @@ -70,7 +71,7 @@ class PhpTest extends \lithium\test\Unit { $config = array( 'session.name' => 'awesome_name', 'session.cookie_lifetime' => 1200, 'session.cookie_domain' => 'awesome.domain', - 'session.save_path' => LITHIUM_APP_PATH . '/resources/tmp/', + 'session.save_path' => Libraries::get(true, 'resources') . '/tmp/', 'somebad.configuration' => 'whoops' ); diff --git a/libraries/lithium/tests/cases/storage/session/strategy/HmacTest.php b/libraries/lithium/tests/cases/storage/session/strategy/HmacTest.php new file mode 100644 index 0000000..0fd4f47 --- /dev/null +++ b/libraries/lithium/tests/cases/storage/session/strategy/HmacTest.php @@ -0,0 +1,99 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\cases\storage\session\strategy; + +use lithium\storage\session\strategy\Hmac; +use lithium\tests\mocks\storage\session\strategy\MockCookieSession; + + +class HmacTest extends \lithium\test\Unit { + + public function setUp() { + $this->secret = 'foobar'; + $this->Hmac = new Hmac(array('secret' => $this->secret)); + $this->mock = 'lithium\tests\mocks\storage\session\strategy\MockCookieSession'; + MockCookieSession::reset(); + } + + public function testConstructException() { + $this->expectException('/HMAC strategy requires a secret key./'); + $hmac = new Hmac(); + } + + public function testConstruct() { + $secret = 'foo'; + $hmac = new Hmac(compact('secret')); + $this->assertTrue($hmac instanceof Hmac); + } + + public function testWrite() { + $value = 'value'; + $key = 'new_key'; + $oldData = MockCookieSession::data(); + $class = $this->mock; + + $result = $this->Hmac->write($value, compact('key', 'class')); + $this->assertEqual($value, $result); + + $signature = hash_hmac('sha1', serialize($oldData + array($key => $value)), $this->secret); + $signedData = MockCookieSession::data(); + $this->assertEqual($signedData, $oldData + array('__signature' => $signature)); + } + + public function testReadWithValidSignature() { + $class = $this->mock; + $currentData = MockCookieSession::data(); + $signature = hash_hmac('sha1', serialize($currentData), $this->secret); + $result = MockCookieSession::write('__signature', $signature); + $this->assertEqual($signature, $result); + + $value = 'data_read'; + $result = $this->Hmac->read($value, compact('class')); + $this->assertEqual($value, $result); + } + + public function testReadWithNoSignature() { + $class = $this->mock; + $value = 'data_read'; + $this->expectException('/HMAC signature not found./'); + $result = $this->Hmac->read($value, compact('class')); + } + + public function testReadWithInvalidSignature() { + $class = $this->mock; + $currentData = MockCookieSession::data(); + $signature = 'some_invalid_signature'; + $result = MockCookieSession::write('__signature', $signature); + $this->assertEqual($signature, $result); + + $value = 'data_read_that_wont_match_signature'; + $this->expectException('/Possible data tampering: HMAC signature does not match data./'); + $result = $this->Hmac->read($value, compact('class')); + } + + public function testDelete() { + $key = 'one'; + $class = $this->mock; + $oldData = MockCookieSession::data(); + $currentSignature = hash_hmac('sha1', serialize($oldData), $this->secret); + $result = MockCookieSession::write('__signature', $currentSignature); + + $newData = $oldData; + unset($newData[$key]); + + $expectedSignature = hash_hmac('sha1', serialize($newData), $this->secret); + $result = $this->Hmac->delete('foo', compact('class', 'key')); + + $this->assertEqual('foo', $result); + $signature = MockCookieSession::read('__signature'); + $this->assertEqual($expectedSignature, $signature); + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/template/HelperTest.php b/libraries/lithium/tests/cases/template/HelperTest.php index a102ea9..1ca58e7 100644 --- a/libraries/lithium/tests/cases/template/HelperTest.php +++ b/libraries/lithium/tests/cases/template/HelperTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/template/ViewTest.php b/libraries/lithium/tests/cases/template/ViewTest.php index 5bbde6f..c59aef9 100644 --- a/libraries/lithium/tests/cases/template/ViewTest.php +++ b/libraries/lithium/tests/cases/template/ViewTest.php @@ -2,22 +2,17 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\template; use lithium\template\View; +use lithium\action\Response; use lithium\g11n\catalog\adapter\Memory; use lithium\template\view\adapter\Simple; - -class TestViewClass extends \lithium\template\View { - - public function renderer() { - return $this->_config['renderer']; - } -} +use lithium\tests\mocks\template\MockView; class ViewTest extends \lithium\test\Unit { @@ -29,15 +24,18 @@ class ViewTest extends \lithium\test\Unit { public function testInitialization() { $expected = new Simple(); - $this->_view = new TestViewClass(array('renderer' => $expected)); + $this->_view = new MockView(array('renderer' => $expected)); $result = $this->_view->renderer(); $this->assertEqual($expected, $result); } - public function testInitializationWithBadClasses() { - $this->expectException("Class 'Badness' of type 'adapter.template.view' not found."); + public function testInitializationWithBadLoader() { + $this->expectException("Class `Badness` of type `adapter.template.view` not found."); new View(array('loader' => 'Badness')); - $this->expectException("Class 'Badness' of type 'adapter.template.view' not found."); + } + + public function testInitializationWithBadRenderer() { + $this->expectException("Class `Badness` of type `adapter.template.view` not found."); new View(array('renderer' => 'Badness')); } @@ -48,6 +46,31 @@ class ViewTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); } + /** + * Tests that the output-escaping handler correctly inherits its encoding from the `Response` + * object, if provided. + * + * @return void + */ + public function testEscapeOutputFilterWithInjectedEncoding() { + $message = "Multibyte string support must be enabled to test character encodings."; + $this->skipIf(!function_exists('mb_convert_encoding'), $message); + + $string = "Joël"; + + $response = new Response(); + $response->encoding = 'UTF-8'; + $view = new View(compact('response')); + $handler = $view->outputFilters['h']; + $this->assertTrue(mb_check_encoding($handler($string), "UTF-8")); + + $response = new Response(); + $response->encoding = 'ISO-8859-1'; + $view = new View(compact('response')); + $handler = $view->outputFilters['h']; + $this->assertTrue(mb_check_encoding($handler($string), "ISO-8859-1")); + } + public function testBasicRenderModes() { $view = new View(array('loader' => 'Simple', 'renderer' => 'Simple')); @@ -63,6 +86,12 @@ class ViewTest extends \lithium\test\Unit { $expected = "Logged in as: Cap'n Crunch."; $this->assertEqual($expected, $result); + $result = $view->render('element', array('name' => "Cap'n Crunch"), array( + 'element' => 'Logged in as: {:name}.' + )); + $expected = "Logged in as: Cap'n Crunch."; + $this->assertEqual($expected, $result); + $xmlHeader = '<' . '?xml version="1.0" ?' . '>' . "\n"; $result = $view->render('all', array('type' => 'auth', 'success' => 'true'), array( 'layout' => $xmlHeader . "\n{:content}\n", @@ -72,6 +101,20 @@ class ViewTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); } + public function testTwoStepRenderWithVariableCapture() { + $view = new View(array('loader' => 'Simple', 'renderer' => 'Simple')); + + $result = $view->render( + array( + array('path' => 'element', 'capture' => array('data' => 'foo')), + array('path' => 'template') + ), + array('name' => "Cap'n Crunch"), + array('element' => 'Logged in as: {:name}.', 'template' => '--{:foo}--') + ); + $this->assertEqual('--Logged in as: Cap\'n Crunch.--', $result); + } + public function testFullRenderNoLayout() { $view = new View(array('loader' => 'Simple', 'renderer' => 'Simple')); $result = $view->render('all', array('type' => 'auth', 'success' => 'true'), array( diff --git a/libraries/lithium/tests/cases/template/helper/FormTest.php b/libraries/lithium/tests/cases/template/helper/FormTest.php old mode 100755 new mode 100644 index 573a92b..96649de --- a/libraries/lithium/tests/cases/template/helper/FormTest.php +++ b/libraries/lithium/tests/cases/template/helper/FormTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -96,7 +96,11 @@ class FormTest extends \lithium\test\Unit { $result = $this->form->create(null, array('id' => 'Registration')); $this->assertTags($result, array( - 'form' => array('action' => "{$this->base}posts", 'method' => 'post', 'id' => 'Registration') + 'form' => array( + 'action' => "{$this->base}posts", + 'method' => 'post', + 'id' => 'Registration' + ) )); } @@ -267,6 +271,13 @@ class FormTest extends \lithium\test\Unit { ))); } + public function testHiddenFieldWithId() { + $result = $this->form->hidden('my_field'); + $this->assertTags($result, array('input' => array( + 'type' => 'hidden', 'name' => 'my_field', 'id' => 'MyField' + ))); + } + public function testLabelGeneration() { $result = $this->form->label('next', 'Enter the next value >>'); $this->assertTags($result, array( @@ -648,6 +659,20 @@ class FormTest extends \lithium\test\Unit { $this->assertEqual(join('', $expected), $result); } + /** + * Verifies that calls to `field()` with `'type' => 'hidden'` do not produce `<label />`s. + * + * @return void + */ + public function testHiddenFieldWithNoLabel() { + $result = $this->form->field('foo', array('type' => 'hidden')); + $this->assertTags($result, array( + 'div' => array(), + 'input' => array('type' => 'hidden', 'name' => 'foo', 'id' => 'Foo'), + '/div' + )); + } + public function testFormFieldWithCustomTemplate() { $result = $this->form->field('name', array( 'template' => '<div{:wrap}>{:label}: {:input}{:error}</div>' @@ -668,6 +693,35 @@ class FormTest extends \lithium\test\Unit { )); } + /** + * Demonstrates that the options for a `<label />` element can be passed through the `field()` + * method, using the label text as a key. + * + * @return void + */ + public function testFieldLabelWithOptions() { + $result = $this->form->field('name', array( + 'label' => array('Item Name' => array('class' => 'required')) + )); + $this->assertTags($result, array( + 'div' => array(), + 'label' => array('for' => 'Name', 'class' => 'required'), 'Item Name', '/label', + 'input' => array('type' => 'text', 'name' => 'name', 'id' => 'Name'), + )); + + $result = $this->form->field('video_preview', array( + 'label' => array('<a href="http://www.youtube.com/">Youtube</a>' => array( + 'escape' => false + )) + )); + $this->assertTags($result, array( + 'div' => array(), + 'label' => array('for' => 'VideoPreview'), + 'a' => array('href' => 'http://www.youtube.com/'), 'Youtube', '/a', '/label', + 'input' => array('type' => 'text', 'name' => 'video_preview', 'id' => 'VideoPreview'), + )); + } + public function testMultipleFields() { $result = $this->form->field(array( 'name' => 'Enter a name', @@ -895,6 +949,47 @@ class FormTest extends \lithium\test\Unit { $this->assertTags($result, array('input' => array( 'type' => 'text', 'name' => 'foo[bar]', 'id' => 'FooBar', 'value' => 'value' ))); + + $result = $this->form->field('foo.bar'); + $this->assertTags($result, array( + 'div' => array(), + 'label' => array('for' => 'FooBar'), 'Foo Bar', '/label', + 'input' => array( + 'type' => 'text', 'name' => 'foo[bar]', 'id' => 'FooBar', 'value' => 'value' + ), + )); + } + + /** + * Tests rendering errors for nested fields. + */ + public function testNestedFieldError() { + $doc = new Document(array('data' => array('foo' => array('bar' => 'value')))); + $doc->errors(array('foo.bar' => 'Something bad happened.')); + + $this->form->create($doc); + $result = $this->form->field('foo.bar'); + + $this->assertTags($result, array( + array('div' => array()), + 'label' => array('for' => 'FooBar'), 'Foo Bar', '/label', + 'input' => array( + 'type' => 'text', 'name' => 'foo[bar]', 'id' => 'FooBar', 'value' => 'value' + ), + 'div' => array('class' => 'error'), 'Something bad happened.', '/div', + array('/div' => array()), + )); + } + + public function testFormCreationWithNoContext() { + $this->form = new Form(array('context' => new MockFormRenderer(array( + 'request' => new Request(array('base' => '/bbq')) + )))); + $result = $this->form->create(null, array('url' => '/foo')); + + $this->assertTags($result, array('form' => array( + 'action' => "/bbq/foo", 'method'=> "post" + ))); } } diff --git a/libraries/lithium/tests/cases/template/helper/HtmlTest.php b/libraries/lithium/tests/cases/template/helper/HtmlTest.php index 3d293a7..9f20dad 100644 --- a/libraries/lithium/tests/cases/template/helper/HtmlTest.php +++ b/libraries/lithium/tests/cases/template/helper/HtmlTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,6 +10,8 @@ namespace lithium\tests\cases\template\helper; use lithium\net\http\Router; use lithium\template\helper\Html; +use lithium\action\Request; +use lithium\action\Response; use lithium\tests\mocks\template\helper\MockHtmlRenderer; class HtmlTest extends \lithium\test\Unit { @@ -34,7 +36,12 @@ class HtmlTest extends \lithium\test\Unit { Router::connect('/{:controller}/{:action}/{:id}.{:type}'); Router::connect('/{:controller}/{:action}.{:type}'); - $this->context = new MockHtmlRenderer(); + $this->context = new MockHtmlRenderer(array( + 'request' => new Request(array( + 'base' => '', 'env' => array('HTTP_HOST' => 'foo.local') + )), + 'response' => new Response() + )); $this->html = new Html(array('context' => &$this->context)); } @@ -53,21 +60,25 @@ class HtmlTest extends \lithium\test\Unit { } /** - * Tests that character set declarations render the correct character set and meta tag. + * Tests that character set declarations render the + * correct character set and short meta tag. * * @return void */ public function testCharset() { $result = $this->html->charset(); + $this->assertTags($result, array('meta' => array( + 'charset' => 'UTF-8' + ))); + $result = $this->html->charset('utf-8'); $this->assertTags($result, array('meta' => array( - 'http-equiv' => 'Content-Type', 'content' => 'text/html; charset=utf-8' + 'charset' => 'utf-8' ))); $result = $this->html->charset('UTF-7'); - $this->assertTags($result, array('meta' => array( - 'http-equiv' => 'Content-Type', 'content' => 'text/html; charset=UTF-7' + 'charset' => 'UTF-7' ))); } @@ -280,6 +291,18 @@ class HtmlTest extends \lithium\test\Unit { '<\/script>\s*$/', $result ); + + $result = $this->html->script("foo", array( + 'async' => true, 'defer' => true, 'onload' => 'init()' + )); + + $this->assertTags($result, array('script' => array( + 'type' => 'text/javascript', + 'src' => '/js/foo.js', + 'async' => 'async', + 'defer' => 'defer', + 'onload' => 'init()' + ))); } /** @@ -338,7 +361,9 @@ class HtmlTest extends \lithium\test\Unit { $expected = array('meta' => array('author' => 'foo')); $this->assertTags($result, $expected); - $result = $this->html->head('unexisting-name', array('options' => array('author' => 'foo'))); + $result = $this->html->head('unexisting-name', array( + 'options' => array('author' => 'foo') + )); $this->assertNull($result); } diff --git a/libraries/lithium/tests/cases/template/view/CompilerTest.php b/libraries/lithium/tests/cases/template/view/CompilerTest.php index c7b032c..cd37047 100644 --- a/libraries/lithium/tests/cases/template/view/CompilerTest.php +++ b/libraries/lithium/tests/cases/template/view/CompilerTest.php @@ -2,27 +2,34 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\template\view; +use lithium\core\Libraries; use lithium\template\view\Compiler; class CompilerTest extends \lithium\test\Unit { protected $_path; - protected $_file = 'resources/tmp/tests/template.html.php'; + protected $_file = 'template.html.php'; public function skip() { - $path = LITHIUM_APP_PATH . '/resources/tmp/tests'; - $this->skipIf(!is_writable($path), "{$path} is not writable."); + $path = realpath(Libraries::get(true, 'resources') . '/tmp/tests'); + $this->skipIf(!is_writable($path), "Path `{$path}` is not writable."); + + $path = realpath(Libraries::get(true, 'resources') . '/tmp/cache/templates'); + $this->skipIf(!is_writable($path), "Path `{$path}` is not writable."); } public function setUp() { - $this->_path = str_replace('\\', '/', LITHIUM_APP_PATH); + $this->_path = realpath( + str_replace('\\', '/', Libraries::get(true, 'resources')) . '/tmp/tests' + ); + file_put_contents("{$this->_path}/{$this->_file}", " <?php echo 'this is unescaped content'; ?" . "> <?='this is escaped content'; ?" . "> @@ -36,11 +43,14 @@ class CompilerTest extends \lithium\test\Unit { that breaks over several lines '; ?" . "> + <?=\$h('This is pre-escaped content'); ?> "); } public function tearDown() { - foreach (glob("{$this->_path}/resources/tmp/cache/templates/*.php") as $file) { + $path = realpath(Libraries::get(true, 'resources') . '/tmp/cache/templates'); + + foreach (glob("{$path}/*.php") as $file) { unlink($file); } unlink("{$this->_path}/{$this->_file}"); @@ -48,46 +58,25 @@ class CompilerTest extends \lithium\test\Unit { public function testTemplateContentRewriting() { $template = Compiler::template("{$this->_path}/{$this->_file}"); - $this->assertTrue(file_exists($template)); + $expected = array( + "<?php echo 'this is unescaped content'; ?" . ">", + "<?php echo \$h('this is escaped content'); ?" . ">", + "<?php echo \$h(\$alsoEscaped); ?" . ">", + "<?php echo \$this->escape('this is also escaped content'); ?" . ">", + '<?php echo $this->escape(', + "'this, too, is escaped content'", + '); ?>', + "<?php echo \$h('This is", + 'escaped content', + 'that breaks over', + 'several lines', + "'); ?>", + "<?php echo \$h('This is pre-escaped content'); ?>" + ); $result = array_map('trim', explode("\n", trim(file_get_contents($template)))); - - $expected = "<?php echo 'this is unescaped content'; ?" . ">"; - $this->assertEqual($expected, $result[0]); - - $expected = "<?php echo \$h('this is escaped content'); ?" . ">"; - $this->assertEqual($expected, $result[1]); - - $expected = "<?php echo \$h(\$alsoEscaped); ?" . ">"; - $this->assertEqual($expected, $result[2]); - - $expected = "<?php echo \$this->escape('this is also escaped content'); ?" . ">"; - $this->assertEqual($expected, $result[3]); - - $expected = '<?php echo $this->escape('; - $this->assertEqual($expected, $result[4]); - - $expected = "'this, too, is escaped content'"; - $this->assertEqual($expected, $result[5]); - - $expected = '); ?>'; - $this->assertEqual($expected, $result[6]); - - $expected = "<?php echo \$h('This is"; - $this->assertEqual($expected, $result[7]); - - $expected = 'escaped content'; - $this->assertEqual($expected, $result[8]); - - $expected = 'that breaks over'; - $this->assertEqual($expected, $result[9]); - - $expected = 'several lines'; - $this->assertEqual($expected, $result[10]); - - $expected = "'); ?>"; - $this->assertEqual($expected, $result[11]); + $this->assertEqual($expected, $result); } public function testFallbackWithNonWritableDirectory() { @@ -107,7 +96,7 @@ class CompilerTest extends \lithium\test\Unit { } public function testTemplateCacheHit() { - $path = LITHIUM_APP_PATH . '/resources/tmp/cache/templates'; + $path = Libraries::get(true, 'resources') . '/tmp/cache/templates'; $original = Compiler::template("{$this->_path}/{$this->_file}", compact('path')); $cache = glob("{$path}/*"); clearstatcache(); diff --git a/libraries/lithium/tests/cases/template/view/RendererTest.php b/libraries/lithium/tests/cases/template/view/RendererTest.php index a005033..044d2de 100644 --- a/libraries/lithium/tests/cases/template/view/RendererTest.php +++ b/libraries/lithium/tests/cases/template/view/RendererTest.php @@ -2,19 +2,20 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\template\view; +use stdClass; use lithium\template\View; use lithium\action\Request; +use lithium\action\Response; use lithium\template\Helper; use lithium\template\helper\Html; use lithium\template\view\adapter\Simple; use lithium\net\http\Router; -use stdClass; class RendererTest extends \lithium\test\Unit { @@ -22,9 +23,12 @@ class RendererTest extends \lithium\test\Unit { $this->_routes = Router::get(); Router::reset(); Router::connect('/{:controller}/{:action}'); - $this->subject = new Simple(array('request' => new Request(array( - 'base' => '', 'env' => array('HTTP_HOST' => 'foo.local') - )))); + $this->subject = new Simple(array( + 'request' => new Request(array( + 'base' => '', 'env' => array('HTTP_HOST' => 'foo.local') + )), + 'response' => new Response() + )); } public function tearDown() { @@ -35,9 +39,7 @@ class RendererTest extends \lithium\test\Unit { } public function testInitialization() { - $expected = array( - 'url', 'path', 'options', 'content', 'title', 'scripts', 'styles', 'head' - ); + $expected = array('url', 'path', 'options', 'title', 'scripts', 'styles', 'head'); $result = array_keys($this->subject->handlers()); $this->assertEqual($expected, $result); @@ -58,6 +60,12 @@ class RendererTest extends \lithium\test\Unit { $this->assertNull($this->subject->foo()); $this->assertFalse(isset($this->subject->foo)); + $result = $this->subject->title("<script>alert('XSS');</script>"); + $this->assertEqual('&lt;script&gt;alert(&#039;XSS&#039;);&lt;/script&gt;', $result); + + $result = $this->subject->title(); + $this->assertEqual('&lt;script&gt;alert(&#039;XSS&#039;);&lt;/script&gt;', $result); + $this->subject = new Simple(array('context' => array( 'content' => '', 'title' => '', 'scripts' => array(), 'styles' => array(), 'foo' => '!' ))); @@ -82,6 +90,7 @@ class RendererTest extends \lithium\test\Unit { $class = get_class($helper); $path = $this->subject->applyHandler($helper, "{$class}::script", 'path', 'foo/file'); $this->assertEqual('/js/foo/file.js', $path); + $this->assertEqual('/some/generic/path', $this->subject->path('some/generic/path')); } public function testHandlerInsertion() { @@ -91,9 +100,7 @@ class RendererTest extends \lithium\test\Unit { $foo = function($value) { return "Foo: {$value}"; }; - $expected = array( - 'url', 'path', 'options', 'content', 'title', 'scripts', 'styles', 'head', 'foo' - ); + $expected = array('url', 'path', 'options', 'title', 'scripts', 'styles', 'head', 'foo'); $result = array_keys($this->subject->handlers(compact('foo'))); $this->assertEqual($expected, $result); @@ -180,8 +187,10 @@ class RendererTest extends \lithium\test\Unit { public function testGetters() { $this->assertTrue($this->subject->request() instanceof Request); + $this->assertTrue($this->subject->response() instanceof Response); $this->subject = new Simple(); $this->assertNull($this->subject->request()); + $this->assertNull($this->subject->response()); } public function testSetAndData() { @@ -192,11 +201,11 @@ class RendererTest extends \lithium\test\Unit { $result = $this->subject->data(); $this->assertEqual($data, $result); - $result = $this->subject->set(array('more' => new StdClass())); + $result = $this->subject->set(array('more' => new stdClass())); $this->assertNull($result); $result = $this->subject->data(); - $this->assertEqual($data + array('more' => new StdClass()), $result); + $this->assertEqual($data + array('more' => new stdClass()), $result); } /** diff --git a/libraries/lithium/tests/cases/template/view/adapter/FileTest.php b/libraries/lithium/tests/cases/template/view/adapter/FileTest.php index b70296b..5ecaa3b 100644 --- a/libraries/lithium/tests/cases/template/view/adapter/FileTest.php +++ b/libraries/lithium/tests/cases/template/view/adapter/FileTest.php @@ -2,54 +2,50 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\template\view\adapter; +use lithium\core\Libraries; use lithium\template\view\adapter\File; class FileTest extends \lithium\test\Unit { - protected $_path = '/resources/tmp/tests'; + protected $_path; public function setUp() { + $this->_path = Libraries::get(true, 'resources') . '/tmp/tests'; + $template1 = '<' . '?php echo $foo; ?' . '>'; $template2 = '<' . '?php echo $this["foo"]; ?' . '>'; - file_put_contents(LITHIUM_APP_PATH . "{$this->_path}/template1.html.php", $template1); - file_put_contents(LITHIUM_APP_PATH . "{$this->_path}/template2.html.php", $template2); + file_put_contents("{$this->_path}/template1.html.php", $template1); + file_put_contents("{$this->_path}/template2.html.php", $template2); } public function tearDown() { - unlink(LITHIUM_APP_PATH . "{$this->_path}/template1.html.php"); - unlink(LITHIUM_APP_PATH . "{$this->_path}/template2.html.php"); + unlink("{$this->_path}/template1.html.php"); + unlink("{$this->_path}/template2.html.php"); } public function testRenderingWithExtraction() { $file = new File(); - $content = $file->render(LITHIUM_APP_PATH . "{$this->_path}/template1.html.php", array( - 'foo' => 'bar' - )); + + $content = $file->render("{$this->_path}/template1.html.php", array('foo' => 'bar')); $this->assertEqual('bar', $content); - $content = $file->render(LITHIUM_APP_PATH . "{$this->_path}/template2.html.php", array( - 'foo' => 'bar' - )); + $content = $file->render("{$this->_path}/template2.html.php", array('foo' => 'bar')); $this->assertEqual('bar', $content); } public function testRenderingWithNoExtraction() { $file = new File(array('extract' => false)); $this->expectException('Undefined variable: foo'); - $content = $file->render(LITHIUM_APP_PATH . "{$this->_path}/template1.html.php", array( - 'foo' => 'bar' - )); + $content = $file->render("{$this->_path}/template1.html.php", array('foo' => 'bar')); $this->assertFalse($content); - $content = $file->render(LITHIUM_APP_PATH . "{$this->_path}/template2.html.php", array( - 'foo' => 'bar' - )); + $content = $file->render("{$this->_path}/template2.html.php", array('foo' => 'bar')); $this->assertEqual('bar', $content); } @@ -83,14 +79,20 @@ class FileTest extends \lithium\test\Unit { )); $this->assertPattern('/\/views\/pages\/home\.html\.php$/', $template); - $template = $file->template('invalid', array('template' => 'foo')); - $this->assertNull($template); - $this->expectException('/Template not found/'); $file->template('template', array( 'controller' => 'pages', 'template' => 'foo', 'type' => 'html' )); } + + public function testInvalidTemplateType() { + $file = new File(array('compile' => false, 'paths' => array( + 'template' => '{:library}/views/{:controller}/{:template}.{:type}.php' + ))); + + $this->expectException("Invalid template type 'invalid'."); + $template = $file->template('invalid', array('template' => 'foo')); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/template/view/adapter/SimpleTest.php b/libraries/lithium/tests/cases/template/view/adapter/SimpleTest.php index 934925f..9158ddd 100644 --- a/libraries/lithium/tests/cases/template/view/adapter/SimpleTest.php +++ b/libraries/lithium/tests/cases/template/view/adapter/SimpleTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/test/ControllerTest.php b/libraries/lithium/tests/cases/test/ControllerTest.php index 9151d47..325059f 100644 --- a/libraries/lithium/tests/cases/test/ControllerTest.php +++ b/libraries/lithium/tests/cases/test/ControllerTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/test/DispatcherTest.php b/libraries/lithium/tests/cases/test/DispatcherTest.php index 942b923..58ad03f 100644 --- a/libraries/lithium/tests/cases/test/DispatcherTest.php +++ b/libraries/lithium/tests/cases/test/DispatcherTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/test/GroupTest.php b/libraries/lithium/tests/cases/test/GroupTest.php index a6db4a9..58017bb 100644 --- a/libraries/lithium/tests/cases/test/GroupTest.php +++ b/libraries/lithium/tests/cases/test/GroupTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,6 +10,7 @@ namespace lithium\tests\cases\test; use lithium\test\Group; use lithium\util\Collection; +use lithium\core\Libraries; class GroupTest extends \lithium\test\Unit { @@ -46,7 +47,7 @@ class GroupTest extends \lithium\test\Unit { public function testAddByString() { $group = new Group(); - $result = $group->add('g11n'); + $result = $group->add('lithium\tests\cases\g11n'); $expected = array( 'lithium\tests\cases\g11n\CatalogTest', 'lithium\tests\cases\g11n\LocaleTest', @@ -58,7 +59,7 @@ class GroupTest extends \lithium\test\Unit { ); $this->assertEqual($expected, $result); - $result = $group->add('data\ModelTest'); + $result = $group->add('lithium\tests\cases\data\ModelTest'); $expected = array( 'lithium\tests\cases\g11n\CatalogTest', 'lithium\tests\cases\g11n\LocaleTest', @@ -73,30 +74,24 @@ class GroupTest extends \lithium\test\Unit { } public function testAddByMixedThroughConstructor() { - $expected = new Collection(array('data' => array( - new \lithium\tests\cases\data\ModelTest(), - new \lithium\tests\cases\core\ObjectTest() - ))); - $group = new Group(array('data' => array( - 'data\ModelTest', + 'lithium\tests\cases\data\ModelTest', new \lithium\tests\cases\core\ObjectTest() ))); - $this->assertEqual($expected, $group->tests()); - - $group = new Group(array('data' => array(array( - 'Data\ModelTest', + $expected = new Collection(array('data' => array( + new \lithium\tests\cases\data\ModelTest(), new \lithium\tests\cases\core\ObjectTest() - )))); - $this->assertEqual($group->tests(), $expected); + ))); + $result = $group->tests(); + $this->assertEqual($expected, $result); } public function testTests() { $group = new Group(); - $result = $group->add('g11n\CatalogTest'); $expected = array( 'lithium\tests\cases\g11n\CatalogTest', ); + $result = $group->add('lithium\tests\cases\g11n\CatalogTest'); $this->assertEqual($expected, $result); $results = $group->tests(); @@ -106,7 +101,7 @@ class GroupTest extends \lithium\test\Unit { $this->assertTrue(is_a($results->current(), 'lithium\tests\cases\g11n\CatalogTest')); } - public function testTestsRun() { + public function testAddEmptyTestsRun() { $group = new Group(); $result = $group->add('lithium\tests\mocks\test\MockUnitTest'); $expected = array('lithium\tests\mocks\test\MockUnitTest'); @@ -138,10 +133,91 @@ class GroupTest extends \lithium\test\Unit { $this->assertEqual($expected, str_replace('\\', '/', $result)); } - public function testQueryAllTests() { + public function testGroupAllForLithium() { + Libraries::cache(false); $result = Group::all(array('library' => 'lithium')); $this->assertTrue(count($result) >= 60); } + + public function testAddTestAppGroup() { + $test_app = Libraries::get(true, 'resources') . '/tmp/tests/test_app'; + mkdir($test_app); + Libraries::add('test_app', array('path' => $test_app)); + + mkdir($test_app . '/tests/cases/models', 0777, true); + file_put_contents($test_app . '/tests/cases/models/UserTest.php', + "<?php namespace test_app\\tests\\cases\\models;\n + class UserTest extends \\lithium\\test\\Unit { public function testMe() { + \$this->assertTrue(true); + }}" + ); + Libraries::cache(false); + + $expected = (array) Libraries::find('test_app', array( + 'recursive' => true, + 'path' => '/tests', + 'filter' => '/cases|integration|functional/', + )); + + Libraries::cache(false); + + $group = new Group(); + $result = $group->add('test_app'); + $this->assertEqual($expected, $result); + + Libraries::cache(false); + $this->_cleanUp(); + } + + public function testRunGroupAllForTestApp() { + $test_app = Libraries::get(true, 'resources') . '/tmp/tests/test_app'; + mkdir($test_app); + Libraries::add('test_app', array('path' => $test_app)); + + mkdir($test_app . '/tests/cases/models', 0777, true); + file_put_contents($test_app . '/tests/cases/models/UserTest.php', + "<?php namespace test_app\\tests\\cases\\models;\n + class UserTest extends \\lithium\\test\\Unit { public function testMe() { + \$this->assertTrue(true); + }}" + ); + Libraries::cache(false); + + $expected = array('test_app\\tests\\cases\\models\\UserTest'); + $result = Group::all(array('library' => 'test_app')); + $this->assertEqual($expected, $result); + + Libraries::cache(false); + $this->_cleanUp(); + } + + public function testRunGroupForTestAppModel() { + $test_app = Libraries::get(true, 'resources') . '/tmp/tests/test_app'; + mkdir($test_app); + Libraries::add('test_app', array('path' => $test_app)); + + mkdir($test_app . '/tests/cases/models', 0777, true); + file_put_contents($test_app . '/tests/cases/models/UserTest.php', + "<?php namespace test_app\\tests\\cases\\models;\n + class UserTest extends \\lithium\\test\\Unit { public function testMe() { + \$this->assertTrue(true); + }}" + ); + Libraries::cache(false); + + $group = new Group(array('data' => array('\\test_app\\tests\\cases'))); + + $expected = array('test_app\\tests\\cases\\models\\UserTest'); + $result = $group->to('array'); + $this->assertEqual($expected, $result); + + $expected = 'pass'; + $result = $group->tests()->run(); + $this->assertEqual($expected, $result[0][0]['result']); + + Libraries::cache(false); + $this->_cleanUp(); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/test/IntegrationTest.php b/libraries/lithium/tests/cases/test/IntegrationTest.php index f62154b..bcbb03f 100644 --- a/libraries/lithium/tests/cases/test/IntegrationTest.php +++ b/libraries/lithium/tests/cases/test/IntegrationTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/test/ReportTest.php b/libraries/lithium/tests/cases/test/ReportTest.php index 7df1317..2f501e1 100644 --- a/libraries/lithium/tests/cases/test/ReportTest.php +++ b/libraries/lithium/tests/cases/test/ReportTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/cases/test/UnitTest.php b/libraries/lithium/tests/cases/test/UnitTest.php index b6b92c0..8c76dd6 100644 --- a/libraries/lithium/tests/cases/test/UnitTest.php +++ b/libraries/lithium/tests/cases/test/UnitTest.php @@ -2,16 +2,17 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\test; +use Exception; +use lithium\core\Libraries; use lithium\tests\mocks\test\MockUnitTest; use lithium\tests\mocks\test\cases\MockSkipThrowsException; use lithium\tests\mocks\test\cases\MockTestErrorHandling; -use \Exception; class UnitTest extends \lithium\test\Unit { @@ -29,15 +30,13 @@ class UnitTest extends \lithium\test\Unit { } public function testCompareIsEqual() { - $expected = true; $result = $this->compare('equal', 'string', 'string'); - $this->assertEqual($expected, $result); + $this->assertTrue($result); } public function testCompareIsIdentical() { - $expected = true; $result = $this->compare('identical', 'string', 'string'); - $this->assertEqual($expected, $result); + $this->assertTrue($result); } public function testCompareTypes() { @@ -65,19 +64,12 @@ class UnitTest extends \lithium\test\Unit { 'result' => 'fail', 'file' => __FILE__, 'line' => __LINE__ - 3, 'method' => 'testAssertEqualNumericFail', 'assertion' => 'assertEqual', 'class' => __CLASS__, 'message' => - "trace: [2]\nexpected: array (\n 0 => 1,\n 1 => 2,\n 2 => 3,\n)\n" - . "result: array (\n 0 => 1,\n 1 => 2,\n)\n", + "trace: [2]\nexpected: 3\n" + . "result: NULL\n", 'data' => array( 'trace' => '[2]', - 'expected' => array( - 0 => 1, - 1 => 2, - 2 => 3, - ), - 'result' => array( - 0 => 1, - 1 => 2, - ) + 'expected' => 3, + 'result' => null ) ); $result = array_pop($this->_results); @@ -111,32 +103,22 @@ class UnitTest extends \lithium\test\Unit { 'result' => 'fail', 'file' => __FILE__, 'line' => __LINE__ - 3, 'method' => 'testAssertEqualThreeDFail', 'assertion' => 'assertEqual', 'class' => __CLASS__, 'message' => - "trace: [0][1][1]\nexpected: array (\n 0 => 1,\n 1 => 2,\n)\n" - . "result: array (\n 0 => 1,\n)\n" - . "trace: [1][1][1]\nexpected: array (\n 0 => 1,\n 1 => 2,\n)\n" - . "result: array (\n 0 => 1,\n)\n", + "trace: [0][1][1]\nexpected: 2\n" + . "result: NULL\n" + . "trace: [1][1][1]\nexpected: 2\n" + . "result: NULL\n", 'data' => array( array( array( 'trace' => '[0][1][1]', - 'expected' => array( - 0 => 1, - 1 => 2, - ), - 'result' => array( - 0 => 1, - ) + 'expected' => 2, + 'result' => null ), ), array( array('trace' => '[1][1][1]', - 'expected' => array( - 0 => 1, - 1 => 2, - ), - 'result' => array( - 0 => 1, - ) + 'expected' => 2, + 'result' => null ) ) ) @@ -381,7 +363,7 @@ class UnitTest extends \lithium\test\Unit { } public function testCleanUp() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); $this->assertTrue(mkdir("{$base}/cleanup_test")); @@ -393,7 +375,7 @@ class UnitTest extends \lithium\test\Unit { } public function testCleanUpWithFullPath() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); $this->assertTrue(mkdir("{$base}/cleanup_test")); @@ -409,7 +391,7 @@ class UnitTest extends \lithium\test\Unit { } public function testCleanUpWithRelativePath() { - $base = LITHIUM_APP_PATH . '/resources/tmp/tests'; + $base = Libraries::get(true, 'resources') . '/tmp/tests'; $this->skipIf(!is_writable($base), "{$base} is not writable."); $this->assertTrue(mkdir("{$base}/cleanup_test")); @@ -427,7 +409,7 @@ class UnitTest extends \lithium\test\Unit { public function testSkipIf() { try { $this->skipIf(true, 'skip me'); - } catch (\Exception $e) { + } catch (Exception $e) { $result = $e->getMessage(); } $expected = 'skip me'; @@ -493,11 +475,11 @@ class UnitTest extends \lithium\test\Unit { public function testCompareWithEmptyResult() { $result = $this->compare('equal', array('key' => array('val1', 'val2')), array()); - $expected = array(array( - 'trace' => '[key][0]', + $expected = array( + 'trace' => '[key]', 'expected' => array('val1', 'val2'), 'result' => array() - )); + ); $this->assertEqual($expected, $result); } @@ -540,12 +522,38 @@ class UnitTest extends \lithium\test\Unit { $this->assertEqual($expected, $result['data']['trace']); } + public function testCompareIdenticalArray() { + $expected = array( + 'trace' => null, + 'expected' => array(), + 'result' => array('two', 'values') + ); + $result = $this->compare('identical', array(), array('two', 'values')); + $this->assertEqual($expected, $result); + } + + public function imethods() { + return array('testCompareIdenticalArray'); + } + + public function testCompareEqualNullArray() { + $expected = array('trace' => null, 'expected' => array(), 'result' => array(null)); + $result = $this->compare('equal', array(), array(null)); + $this->assertEqual($expected, $result); + } + + public function testCompareIdenticalNullArray() { + $expected = array('trace' => null, 'expected' => array(), 'result' => array(null)); + $result = $this->compare('identical', array(), array(null)); + $this->assertEqual($expected, $result); + } + /** * Always keep second to last. * */ public function testResults() { - $expected = 86; + $expected = 89; $result = count($this->results()); $this->assertEqual($expected, $result); } @@ -570,7 +578,8 @@ class UnitTest extends \lithium\test\Unit { 'testGetTest', 'testAssertCookie', 'testAssertCookieWithHeaders', 'testCompareWithEmptyResult', 'testExceptionCatching', 'testErrorHandling', 'testAssertObjects', - 'testAssertArrayIdentical', + 'testAssertArrayIdentical', 'testCompareIdenticalArray', + 'testCompareEqualNullArray', 'testCompareIdenticalNullArray', 'testResults', 'testTestMethods' ); $this->assertIdentical($expected, $this->methods()); diff --git a/libraries/lithium/tests/cases/test/filter/AffectedTest.php b/libraries/lithium/tests/cases/test/filter/AffectedTest.php index 9b2ae50..4c53dc5 100644 --- a/libraries/lithium/tests/cases/test/filter/AffectedTest.php +++ b/libraries/lithium/tests/cases/test/filter/AffectedTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -77,6 +77,23 @@ class AffectedTest extends \lithium\test\Unit { $result = $tests->map('get_class', array('collect' => false)); $this->assertEqual($expected, $result); } + + public function testAnalyze() { + $ns = 'lithium\tests\cases'; + + $expected = array( + 'lithium\g11n\Message' => "{$ns}\g11n\MessageTest", + 'lithium\console\command\g11n\Extract' => "{$ns}\console\command\g11n\ExtractTest" + ); + + $group = new Group(); + $group->add('lithium\tests\cases\g11n\CatalogTest'); + $this->report->group = $group; + $tests = Affected::apply($this->report, $group->tests()); + $results = Affected::analyze($this->report); + + $this->assertEqual($results, $expected); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/util/CollectionTest.php b/libraries/lithium/tests/cases/util/CollectionTest.php index b5e4922..5c47ae6 100644 --- a/libraries/lithium/tests/cases/util/CollectionTest.php +++ b/libraries/lithium/tests/cases/util/CollectionTest.php @@ -2,13 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\cases\util; -use \stdClass; +use stdClass; use lithium\util\Collection; use lithium\tests\mocks\util\MockCollectionMarker; use lithium\tests\mocks\util\MockCollectionObject; diff --git a/libraries/lithium/tests/cases/util/InflectorTest.php b/libraries/lithium/tests/cases/util/InflectorTest.php index 9c6a0b3..a5d4c84 100644 --- a/libraries/lithium/tests/cases/util/InflectorTest.php +++ b/libraries/lithium/tests/cases/util/InflectorTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -356,9 +356,12 @@ class InflectorTest extends \lithium\test\Unit { /** * This is a helper method for testStorageMechanism to fetch a private * property of the Inflector class. + * + * @param string $property + * @return string The value of the property. */ private function getProtectedValue($property) { - $info = Inspector::info("lithium\util\Inflector::$property"); + $info = Inspector::info("lithium\util\Inflector::{$property}"); return $info['value']; } } diff --git a/libraries/lithium/tests/cases/util/SetTest.php b/libraries/lithium/tests/cases/util/SetTest.php index 41c1cd8..3e5a792 100644 --- a/libraries/lithium/tests/cases/util/SetTest.php +++ b/libraries/lithium/tests/cases/util/SetTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -16,31 +16,31 @@ class SetTest extends \lithium\test\Unit { public function testDepthWithEmptyData() { $data = array(); $result = Set::depth($data); - $this->assertEqual($result, 0); + $this->assertEqual(0, $result); } public function testDepthOneLevelWithDefaults() { $data = array(); $result = Set::depth($data); - $this->assertEqual($result, 0); + $this->assertEqual(0, $result); $data = array('one', '2', 'three'); $result = Set::depth($data); - $this->assertEqual($result, 1); + $this->assertEqual(1, $result); $data = array('1' => '1.1', '2', '3'); $result = Set::depth($data); - $this->assertEqual($result, 1); + $this->assertEqual(1, $result); $data = array('1' => '1.1', '2', '3' => array('3.1' => '3.1.1')); - $result = Set::depth($data, false, 0); - $this->assertEqual($result, 1); + $result = Set::depth($data, array('all' => false)); + $this->assertEqual(1, $result); } public function testDepthTwoLevelsWithDefaults() { $data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => '3.1.1')); $result = Set::depth($data); - $this->assertEqual($result, 2); + $this->assertEqual(2, $result); $data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => array( '3.1.1' => '3.1.1.1' @@ -55,14 +55,14 @@ class SetTest extends \lithium\test\Unit { )), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, false, 0); + $result = Set::depth($data, array('all' => false)); $this->assertEqual($result, 2); } public function testDepthTwoLevelsWithAll() { $data = array('1' => '1.1', '2', '3' => array('3.1' => '3.1.1')); - $result = Set::depth($data, true); - $this->assertEqual($result, 2); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(2, $result); } @@ -70,57 +70,59 @@ class SetTest extends \lithium\test\Unit { $data = array( '1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, true); - $this->assertEqual($result, 3); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(3, $result); $data = array( '1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => '2.1.1.1'))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, true); - $this->assertEqual($result, 4); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(4, $result); $data = array( '1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => array('2.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, true); - $this->assertEqual($result, 5); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(5, $result); - $data = array('1' => array('1.1' => '1.1.1'), array( + $data = array( + '1' => array('1.1' => '1.1.1'), array( '2' => array('2.1' => array('2.1.1' => array('2.1.1.1' => '2.1.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, true); - $this->assertEqual($result, 5); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(5, $result); } public function testDepthFourLevelsWithAll() { - $data = array('1' => array('1.1' => '1.1.1'), array( + $data = array( + '1' => array('1.1' => '1.1.1'), array( '2' => array('2.1' => array('2.1.1' => '2.1.1.1'))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, true); - $this->assertEqual($result, 4); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(4, $result); } public function testDepthFiveLevelsWithAll() { - - $data = array('1' => array('1.1' => '1.1.1'), array( + $data = array( + '1' => array('1.1' => '1.1.1'), array( '2' => array('2.1' => array('2.1.1' => array('2.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, true); - $this->assertEqual($result, 5); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(5, $result); $data = array('1' => array('1.1' => '1.1.1'), array( '2' => array('2.1' => array('2.1.1' => array('2.1.1.1' => '2.1.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')) ); - $result = Set::depth($data, true); - $this->assertEqual($result, 5); + $result = Set::depth($data, array('all' => true)); + $this->assertEqual(5, $result); } public function testFlattenOneLevel() { @@ -160,9 +162,18 @@ class SetTest extends \lithium\test\Unit { $result = Set::flatten($data); $this->assertEqual($expected, $result); - $result = Set::flatten(array('Post' => $data[0]['Post']), '/'); - $expected = array('Post/id' => '1', 'Post/author_id' => '1', 'Post/title' => 'First Post'); + $result = Set::expand($result); + $this->assertEqual($data, $result); + + $result = Set::flatten($data[0], array('separator' => '/')); + $expected = array( + 'Post/id' => '1', 'Post/author_id' => '1', 'Post/title' => 'First Post', + 'Author/id' => '1', 'Author/user' => 'nate', 'Author/password' => 'foo' + ); $this->assertEqual($expected, $result); + + $result = Set::expand($expected, array('separator' => '/')); + $this->assertEqual($data[0], $result); } public function testFormat() { @@ -234,25 +245,25 @@ class SetTest extends \lithium\test\Unit { array('Article' => array('id' => 3, 'title' => 'Article 3')) ); - $this->assertTrue(Set::matches(array('id=2'), $a[1]['Article'])); - $this->assertFalse(Set::matches(array('id>2'), $a[1]['Article'])); - $this->assertTrue(Set::matches(array('id>=2'), $a[1]['Article'])); - $this->assertFalse(Set::matches(array('id>=3'), $a[1]['Article'])); - $this->assertTrue(Set::matches(array('id<=2'), $a[1]['Article'])); - $this->assertFalse(Set::matches(array('id<2'), $a[1]['Article'])); - $this->assertTrue(Set::matches(array('id>1'), $a[1]['Article'])); - $this->assertTrue(Set::matches(array('id>1', 'id<3', 'id!=0'), $a[1]['Article'])); - - $this->assertTrue(Set::matches(array('3'), null, 3)); - $this->assertTrue(Set::matches(array('5'), null, 5)); - - $this->assertTrue(Set::matches(array('id'), $a[1]['Article'])); - $this->assertTrue(Set::matches(array('id', 'title'), $a[1]['Article'])); - $this->assertFalse(Set::matches(array('non-existant'), $a[1]['Article'])); - - $this->assertTrue(Set::matches('/Article[id=2]', $a)); - $this->assertFalse(Set::matches('/Article[id=4]', $a)); - $this->assertTrue(Set::matches(array(), $a)); + $this->assertTrue(Set::matches($a[1]['Article'], array('id=2'))); + $this->assertFalse(Set::matches($a[1]['Article'], array('id>2'))); + $this->assertTrue(Set::matches($a[1]['Article'], array('id>=2'))); + $this->assertFalse(Set::matches($a[1]['Article'], array('id>=3'))); + $this->assertTrue(Set::matches($a[1]['Article'], array('id<=2'))); + $this->assertFalse(Set::matches($a[1]['Article'], array('id<2'))); + $this->assertTrue(Set::matches($a[1]['Article'], array('id>1'))); + $this->assertTrue(Set::matches($a[1]['Article'], array('id>1', 'id<3', 'id!=0'))); + + $this->assertTrue(Set::matches(array(), array('3'), 3)); + $this->assertTrue(Set::matches(array(), array('5'), 5)); + + $this->assertTrue(Set::matches($a[1]['Article'], array('id'))); + $this->assertTrue(Set::matches($a[1]['Article'], array('id', 'title'))); + $this->assertFalse(Set::matches($a[1]['Article'], array('non-existant'))); + + $this->assertTrue(Set::matches($a, '/Article[id=2]')); + $this->assertFalse(Set::matches($a, '/Article[id=4]')); + $this->assertTrue(Set::matches($a, array())); } public function testMatchesMultipleLevels() { @@ -271,8 +282,7 @@ class SetTest extends \lithium\test\Unit { ) ) ); - $result = Set::matches($result, '/Article/keep/Comment'); - $this->assertTrue($result); + $this->assertTrue(Set::matches($result, '/Article/keep/Comment')); $result = Set::matches($result, '/Article/keep/Comment/fields/user'); $this->assertFalse($result); @@ -283,9 +293,6 @@ class SetTest extends \lithium\test\Unit { $result = Set::extract(array(), '/Post/id'); $this->assertIdentical($expected, $result); - $result = Set::extract('/Post/id', array()); - $this->assertIdentical($expected, $result); - $result = Set::extract(array( array('Post' => array('name' => 'bob')), array('Post' => array('name' => 'jim')) @@ -426,7 +433,7 @@ class SetTest extends \lithium\test\Unit { ); $expected = array(array('a' => $c[2]['a'])); - $result = Set::extract('/a/II[a=3]/..', $c); + $result = Set::extract($c, '/a/II[a=3]/..'); $this->assertEqual($expected, $result); $expected = array(1, 2, 3, 4, 5); @@ -582,15 +589,15 @@ class SetTest extends \lithium\test\Unit { 'Comment' => array() ) ); - $result = Set::extract('/', $common); + $result = Set::extract($common, '/'); $this->assertEqual($result, $common); $expected = array(1); - $result = Set::extract('/Comment/id[:first]', $common); + $result = Set::extract($common, '/Comment/id[:first]'); $this->assertEqual($expected, $result); $expected = array(5); - $result = Set::extract('/Comment/id[:last]', $common); + $result = Set::extract($common, '/Comment/id[:last]'); $this->assertEqual($expected, $result); $result = Set::extract($common, '/Comment/id'); @@ -614,15 +621,15 @@ class SetTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); $expected = array(array('Comment' => $common[1]['Comment'][0])); - $result = Set::extract('/Comment[addition=]', $common); + $result = Set::extract($common, '/Comment[addition=]'); $this->assertEqual($expected, $result); $expected = array(3); - $result = Set::extract('/Article[:last]/id', $common); + $result = Set::extract($common, '/Article[:last]/id'); $this->assertEqual($expected, $result); $expected = array(); - $result = Set::extract('/User/id', array()); + $result = Set::extract(array(), '/User/id'); $this->assertEqual($expected, $result); } @@ -887,16 +894,13 @@ class SetTest extends \lithium\test\Unit { } public function testMerge() { - $result = Set::merge(array('foo')); - $this->assertIdentical($result, array('foo')); - - $result = Set::merge('foo'); + $result = Set::merge(array('foo'), array()); $this->assertIdentical($result, array('foo')); - $result = Set::merge('foo', 'bar'); + $result = Set::merge((array) 'foo', (array) 'bar'); $this->assertIdentical($result, array('foo', 'bar')); - $result = Set::merge('foo', array('user' => 'bob', 'no-bar')); + $result = Set::merge((array) 'foo', array('user' => 'bob', 'no-bar')); $this->assertIdentical($result, array('foo', 'user' => 'bob', 'no-bar')); $a = array('foo', 'foo2'); @@ -1303,7 +1307,7 @@ class SetTest extends \lithium\test\Unit { $this->assertIdentical($expected, $result); } - public function testBlend() { + public function testAppend() { $array1 = array('ModelOne' => array( 'id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' => 'a1.m1.f2' )); @@ -1311,7 +1315,7 @@ class SetTest extends \lithium\test\Unit { 'id' => 1002, 'field_one' => 'a2.m2.f1', 'field_two' => 'a2.m2.f2' )); - $result = Set::blend($array1, $array2); + $result = Set::append($array1, $array2); $this->assertIdentical($result, $array1 + $array2); @@ -1319,7 +1323,7 @@ class SetTest extends \lithium\test\Unit { 'id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' => 'a3.m1.f2', 'field_three' => 'a3.m1.f3' )); - $result = Set::blend($array1, $array3); + $result = Set::append($array1, $array3); $expected = array('ModelOne' => array( 'id' => 1001, 'field_one' => 'a1.m1.f1', @@ -1344,14 +1348,14 @@ class SetTest extends \lithium\test\Unit { )) ); - $result = Set::blend($array1, $array2); + $result = Set::append($array1, $array2); $this->assertIdentical($result, $array1); $array3 = array(array('ModelThree' => array( 'id' => 1003, 'field_one' => 's3.0.m3.f1', 'field_two' => 's3.0.m3.f2' ))); - $result = Set::blend($array1, $array3); + $result = Set::append($array1, $array3); $expected = array( array( 'ModelOne' => array( @@ -1367,13 +1371,13 @@ class SetTest extends \lithium\test\Unit { ); $this->assertIdentical($expected, $result); - $result = Set::blend($array1, null); + $result = Set::append($array1, array()); $this->assertIdentical($result, $array1); - $result = Set::blend($array1, $array2); + $result = Set::append($array1, $array2); $this->assertIdentical($result, $array1 + $array2); - $result = Set::blend(array(), array('2')); + $result = Set::append(array(), array('2')); $this->assertIdentical(array('2'), $result); } diff --git a/libraries/lithium/tests/cases/util/StringTest.php b/libraries/lithium/tests/cases/util/StringTest.php index 2dceac9..faa6e2a 100644 --- a/libraries/lithium/tests/cases/util/StringTest.php +++ b/libraries/lithium/tests/cases/util/StringTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -13,6 +13,25 @@ use lithium\net\http\Request; use lithium\tests\mocks\util\MockStringObject; class StringTest extends \lithium\test\Unit { + /** + * testRandomGenerator method + * + * @return void + */ + public function testRandomGenerator() { + // Disallow allow seeding twice + $this->assertFalse(String::seed() && String::seed()); + + $check = array(); + $count = 50; + $pattern = "/^[0-9A-Za-z\.\/]+$/"; + for ($i = 0; $i < $count; $i++) { + $result = String::random(8); + $this->assertPattern($pattern, String::encode64($result)); + $this->assertFalse(in_array($result, $check)); + $check[] = $result; + } + } /** * testUuidGeneration method @@ -20,11 +39,11 @@ class StringTest extends \lithium\test\Unit { * @return void */ public function testUuidGeneration() { - $result = String::uuid(new Request()); - $pattern = "/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/"; + $result = String::uuid(); + $pattern = "/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[8-9a-b][a-f0-9]{3}-[a-f0-9]{12}$/"; $this->assertPattern($pattern, $result); - $result = String::uuid($_SERVER); + $result = String::uuid(); $this->assertPattern($pattern, $result); } @@ -35,11 +54,11 @@ class StringTest extends \lithium\test\Unit { */ public function testMultipleUuidGeneration() { $check = array(); - $count = 500; - $pattern = "/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/"; + $count = 50; + $pattern = "/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[8-9a-b][a-f0-9]{3}-[a-f0-9]{12}$/"; for ($i = 0; $i < $count; $i++) { - $result = String::uuid($_SERVER); + $result = String::uuid(); $match = preg_match($pattern, $result); $this->assertTrue($match); $this->assertFalse(in_array($result, $check)); @@ -48,36 +67,6 @@ class StringTest extends \lithium\test\Unit { } /** - * Tests generating a UUID with seed data provided by an anonymous function. - * - * @return void - */ - public function testGeneratingUuidWithCallback() { - $pattern = "/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/"; - - $result = String::uuid(function($value) { - if ($value == 'SERVER_ADDR') { - return '::1'; - } - }); - $this->assertPattern($pattern, $result); - - $result = String::uuid(function($value) { - if ($value == 'HOST') { - return '127.0.0.1'; - } - }); - $this->assertPattern($pattern, $result); - - $result = String::uuid(function($value) { - if ($value == 'SERVER_ADDR') { - return '127.0.0.2'; - } - }); - $this->assertPattern($pattern, $result); - } - - /** * testHash method - Tests hash generation using `util\String::hash()` * * @return void @@ -110,6 +99,48 @@ class StringTest extends \lithium\test\Unit { } /** + * testPassword method + * + * @return void + **/ + public function testPassword() { + $pass = 'Lithium rocks!'; + + $bfSalt = "{^\\$2a\\$06\\$[0-9A-Za-z./]{22}$}"; + $bfHash = "{^\\$2a\\$06\\$[0-9A-Za-z./]{53}$}"; + + $xdesSalt = "{^_zD..[0-9A-Za-z./]{4}$}"; + $xdesHash = "{^_zD..[0-9A-Za-z./]{15}$}"; + + $md5Salt = "{^\\$1\\$[0-9A-Za-z./]{8}$}"; + $md5Hash = "{^\\$1\\$[0-9A-Za-z./]{8}\\$[0-9A-Za-z./]{22}$}"; + + // Make it a bit slow, else we'll be there tomorrow + foreach (array('bf' => 6, 'xdes' => 10, 'md5' => false) as $method => $log2) { + $salts = array(); + $hashes = array(); + $count = 50; + $saltPattern = ${$method . 'Salt'}; + $hashPattern = ${$method . 'Hash'}; + + for ($i = 0; $i < $count; $i++) { + $salt = String::genSalt($method, $log2); + $this->assertPattern($saltPattern, $salt); + $this->assertFalse(in_array($salt, $salts)); + $salts[] = $salt; + + $hash = String::hashPassword($pass, $salt); + $this->assertPattern($hashPattern, $hash); + $this->assertEqual(substr($hash, 0, strlen($salt)), $salt); + $this->assertFalse(in_array($hash, $hashes)); + $hashes[] = $hash; + + $this->assertTrue(String::checkPassword($pass, $hash)); + } + } + } + + /** * testInsert method * * @return void @@ -400,8 +431,7 @@ class StringTest extends \lithium\test\Unit { $this->assertEqual($expected, $result); $result = String::tokenize(null); - $expected = null; - $this->assertEqual($expected, $result); + $this->assertNull($result); } /** diff --git a/libraries/lithium/tests/cases/util/ValidatorTest.php b/libraries/lithium/tests/cases/util/ValidatorTest.php index 76c8449..3deab22 100644 --- a/libraries/lithium/tests/cases/util/ValidatorTest.php +++ b/libraries/lithium/tests/cases/util/ValidatorTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -78,7 +78,7 @@ class ValidatorTest extends \lithium\test\Unit { $this->assertTrue(in_array('foo', Validator::rules())); $this->assertEqual('/^foo$/', Validator::rules('foo')); - $this->expectException("Rule 'bar' is not a validation rule"); + $this->expectException("Rule `bar` is not a validation rule."); $this->assertNull(Validator::isBar('foo')); } @@ -107,6 +107,7 @@ class ValidatorTest extends \lithium\test\Unit { $this->assertTrue(Validator::isUuid('1c0a5831-6025-11de-8a39-0800200c9a66')); $this->assertTrue(Validator::isUuid('1c0a5832-6025-11de-8a39-0800200c9a66')); $this->assertFalse(Validator::isUuid('zc0a5832-6025-11de-8a39-0800200c9a66')); + $this->assertFalse(Validator::isUuid('1-1c0a5832-6025-11de-8a39-0800200c9a66')); } /** @@ -143,6 +144,35 @@ class ValidatorTest extends \lithium\test\Unit { $this->assertTrue(Validator::isFoo('foo')); } + /** + * Tests the regular expression validation for various regex delimiters + * + * @link http://www.php.net/manual/en/regexp.reference.delimiters.php Regex Delimiters + */ + public function testIsRegex() { + $this->assertTrue(Validator::isRegex('/^123$/')); + $this->assertTrue(Validator::isRegex('/^abc$/')); + $this->assertTrue(Validator::isRegex('/^abc123$/')); + $this->assertTrue(Validator::isRegex('@^abc$@')); + $this->assertTrue(Validator::isRegex('#^abc$#')); + $this->assertFalse(Validator::isRegex('d^abc$d')); + + $this->assertTrue(Validator::isRegex('(^abc$)')); + $this->assertTrue(Validator::isRegex('{^abc$}')); + $this->assertTrue(Validator::isRegex('[^abc$]')); + $this->assertTrue(Validator::isRegex('<^abc$>')); + $this->assertTrue(Validator::isRegex(')^abc$)')); + $this->assertTrue(Validator::isRegex('}^abc$}')); + $this->assertTrue(Validator::isRegex(']^abc$]')); + $this->assertTrue(Validator::isRegex('>^abc$>')); + + $this->assertFalse(Validator::isRegex('\\^abc$\\')); + $this->assertFalse(Validator::isRegex('(^abc$(')); + $this->assertFalse(Validator::isRegex('{^abc${')); + $this->assertFalse(Validator::isRegex('[^abc$[')); + $this->assertFalse(Validator::isRegex('<^abc$<')); + } + public function testPrefilterMethodAccess() { $this->assertTrue(Validator::isNotEmpty('0')); $this->assertFalse(Validator::isNotEmpty('')); @@ -1037,8 +1067,6 @@ class ValidatorTest extends \lithium\test\Unit { /** * Tests that event flags applied to rules only trigger when the corresponding event is passed * in the `$options` parameter of `check()`. - * - * @return void */ public function testEvents() { $rules = array('number' => array('numeric', 'message' => 'Badness!')); @@ -1068,6 +1096,28 @@ class ValidatorTest extends \lithium\test\Unit { $result = Validator::check(array('number' => 'o'), $rules, array('events' => 'foo')); $this->assertEqual($expected, $result); } + + /** + * Tests validating nested fields using dot-separated paths. + */ + public function testNestedFields() { + $rules = array( + 'id' => array('numeric', 'message' => 'Bad ID'), + 'profile.name' => "Can't be empty", + 'profile.email' => array('email', 'message' => 'Must be a valid email') + ); + $data = array('id' => 1, 'profile' => array('email' => 'foo')); + $result = Validator::check($data, $rules); + $expected = array( + 'profile.name' => array("Can't be empty"), + 'profile.email' => array('Must be a valid email') + ); + $this->assertEqual($expected, $result); + + $data = array('id' => '.', 'profile' => array('email' => 'foo@bar.com', 'name' => 'Bob')); + $result = Validator::check($data, $rules); + $this->assertEqual(array('id' => array('Bad ID')), $result); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/cases/util/collection/FiltersTest.php b/libraries/lithium/tests/cases/util/collection/FiltersTest.php index 9f1c381..1dfa6f7 100644 --- a/libraries/lithium/tests/cases/util/collection/FiltersTest.php +++ b/libraries/lithium/tests/cases/util/collection/FiltersTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/integration/analysis/LoggerTest.php b/libraries/lithium/tests/integration/analysis/LoggerTest.php new file mode 100644 index 0000000..2394d3e --- /dev/null +++ b/libraries/lithium/tests/integration/analysis/LoggerTest.php @@ -0,0 +1,45 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\integration\analysis; + +use lithium\core\Libraries; +use lithium\analysis\Logger; +use lithium\util\Collection; +use lithium\util\collection\Filters; + +/** + * Logger adapter integration test cases + */ +class LoggerTest extends \lithium\test\Unit { + + public function testWriteFilter() { + + $base = Libraries::get(true, 'resources') . '/tmp/logs'; + $this->skipIf(!is_writable($base), "{$base} is not writable."); + + Filters::apply('lithium\analysis\Logger', 'write', function($self, $params, $chain) { + $params['message'] = 'Filtered Message'; + return $chain->next($self, $params, $chain); + }); + + $config = array('default' => array('adapter' => 'File', 'timestamp' => false)); + Logger::config($config); + + $result = Logger::write('info', 'Original Message'); + $this->assertTrue(file_exists($base . '/info.log')); + + $expected = "Filtered Message\n"; + $result = file_get_contents($base . '/info.log'); + $this->assertEqual($expected, $result); + + unlink($base . '/info.log'); + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/integration/data/CrudTest.php b/libraries/lithium/tests/integration/data/CrudTest.php index eca74cb..87eb2cd 100644 --- a/libraries/lithium/tests/integration/data/CrudTest.php +++ b/libraries/lithium/tests/integration/data/CrudTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,7 +10,7 @@ namespace lithium\tests\integration\data; use Exception; use lithium\data\Connections; -use lithium\tests\mocks\data\Company; +use lithium\tests\mocks\data\Companies; class CrudTest extends \lithium\test\Integration { @@ -24,9 +24,9 @@ class CrudTest extends \lithium\test\Integration { ); public function setUp() { + Companies::config(); + $this->_key = Companies::key(); $this->_connection = Connections::get('test'); - Company::config(); - $this->_key = Company::meta('key'); } /** @@ -49,42 +49,77 @@ class CrudTest extends \lithium\test\Integration { * @return void */ public function testCreate() { - $new = Company::create(array($this->_key => 12345, 'name' => 'Acme, Inc.')); + Companies::all()->delete(); + $this->assertIdentical(0, Companies::count()); - $result = $new->data(); - $expected = array($this->_key => 12345, 'name' => 'Acme, Inc.'); - $this->assertEqual($expected[$this->_key], $result[$this->_key]); - $this->assertEqual($expected['name'], $result['name']); + $new = Companies::create(array('name' => 'Acme, Inc.', 'active' => true)); + $this->assertEqual($new->data(), array('name' => 'Acme, Inc.', 'active' => true)); - $this->assertFalse($new->exists()); - $this->assertTrue($new->save()); - $this->assertTrue($new->exists()); + $this->assertEqual( + array(false, true, true), + array($new->exists(), $new->save(), $new->exists()) + ); + $this->assertIdentical(1, Companies::count()); } public function testRead() { - $existing = Company::find(12345); - $expected = array($this->_key => 12345, 'name' => 'Acme, Inc.'); - $result = $existing->data(); - $this->assertEqual($expected[$this->_key], $result[$this->_key]); - $this->assertEqual($expected['name'], $result['name']); + $existing = Companies::first(); + + foreach (Companies::key($existing) as $val) { + $this->assertTrue($val); + } + $this->assertEqual('Acme, Inc.', $existing->name); + $this->assertTrue($existing->active); $this->assertTrue($existing->exists()); } public function testUpdate() { - $existing = Company::find(12345); + $existing = Companies::first(); + $this->assertEqual($existing->name, 'Acme, Inc.'); $existing->name = 'Big Brother and the Holding Company'; $result = $existing->save(); $this->assertTrue($result); - $existing = Company::find(12345); - $result = $existing->data(); - $expected = array($this->_key => 12345, 'name' => 'Big Brother and the Holding Company'); - $this->assertEqual($expected[$this->_key], $result[$this->_key]); - $this->assertEqual($expected['name'], $result['name']); + $existing = Companies::first(); + foreach (Companies::key($existing) as $val) { + $this->assertTrue($val); + } + $this->assertTrue($existing->active); + $this->assertEqual('Big Brother and the Holding Company', $existing->name); } public function testDelete() { - $existing = Company::find(12345); + $existing = Companies::first(); + $this->assertTrue($existing->exists()); $this->assertTrue($existing->delete()); + $this->assertNull(Companies::first(array('conditions' => Companies::key($existing)))); + $this->assertIdentical(0, Companies::count()); + } + + public function testCrudMulti() { + $large = Companies::create(array('name' => 'BigBoxMart', 'active' => true)); + $medium = Companies::create(array('name' => 'Acme, Inc.', 'active' => true)); + $small = Companies::create(array('name' => 'Ma & Pa\'s', 'active' => true)); + + foreach (array('large', 'medium', 'small') as $key) { + $this->assertFalse(${$key}->exists()); + $this->assertTrue(${$key}->save()); + $this->assertTrue(${$key}->exists()); + } + $this->assertEqual(3, Companies::count()); + + $all = Companies::all(); + $this->assertEqual(3, $all->count()); + + $match = 'BigBoxMart'; + $filter = function($entity) use (&$match) { return $entity->name == $match; }; + + foreach (array('BigBoxMart', 'Acme, Inc.', 'Ma & Pa\'s') as $match) { + $this->assertTrue($all->first($filter)->exists()); + } + $this->assertEqual(array(true, true, true), array_values($all->delete())); + $this->assertEqual(0, Companies::count()); } -} \ No newline at end of file +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/integration/data/DatabaseTest.php b/libraries/lithium/tests/integration/data/DatabaseTest.php index a75be5e..8cfe62d 100644 --- a/libraries/lithium/tests/integration/data/DatabaseTest.php +++ b/libraries/lithium/tests/integration/data/DatabaseTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/integration/data/FieldsTest.php b/libraries/lithium/tests/integration/data/FieldsTest.php index 548959d..fb0072d 100644 --- a/libraries/lithium/tests/integration/data/FieldsTest.php +++ b/libraries/lithium/tests/integration/data/FieldsTest.php @@ -4,24 +4,16 @@ namespace lithium\tests\integration\data; use lithium\data\Connections; use lithium\data\Entity; - -class MockCompany extends \lithium\data\Model { - - protected $_meta = array( - 'source' => 'companies', - 'connection' => 'test' - ); -} - +use lithium\tests\mocks\data\Company; class FieldsTest extends \lithium\test\Unit { public function setUp() { - MockCompany::config(); + Company::config(); } public function tearDown() { - MockCompany::remove(); + Company::remove(); } public function skip() { @@ -33,12 +25,12 @@ class FieldsTest extends \lithium\test\Unit { } public function testSingleField() { - $new = MockCompany::create(array('name' => 'Acme, Inc.')); - $key = MockCompany::meta('key'); + $new = Company::create(array('name' => 'Acme, Inc.')); + $key = Company::meta('key'); $new->save(); $id = is_object($new->{$key}) ? (string) $new->{$key} : $new->{$key}; - $entity = MockCompany::first($id); + $entity = Company::first($id); $this->assertTrue($entity instanceof Entity); $this->skipIf(!$entity instanceof Entity, 'Queried object is not an entity.'); @@ -47,7 +39,7 @@ class FieldsTest extends \lithium\test\Unit { $result = $entity->data(); $this->assertEqual($expected, $result); - $entity = MockCompany::first(array('fields' => array($key))); + $entity = Company::first(array('fields' => array($key))); $this->assertTrue($entity instanceof Entity); $this->skipIf(!$entity instanceof Entity, 'Queried object is not an entity.'); @@ -56,7 +48,7 @@ class FieldsTest extends \lithium\test\Unit { $result = $entity->data(); $this->assertEqual($expected, $result); - $entity = MockCompany::find('first',array( + $entity = Company::find('first',array( 'conditions' => array($key => $id), 'fields' => array($key) )); diff --git a/libraries/lithium/tests/integration/data/SourceTest.php b/libraries/lithium/tests/integration/data/SourceTest.php index 86396c8..c356918 100644 --- a/libraries/lithium/tests/integration/data/SourceTest.php +++ b/libraries/lithium/tests/integration/data/SourceTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/integration/data/source/CouchDbTest.php b/libraries/lithium/tests/integration/data/source/CouchDbTest.php index eba1614..e7ed518 100644 --- a/libraries/lithium/tests/integration/data/source/CouchDbTest.php +++ b/libraries/lithium/tests/integration/data/source/CouchDbTest.php @@ -2,19 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\integration\data\source; use lithium\data\Connections; - -class MockCouchModel extends \lithium\data\Model { - protected $_schema = array( - 'someKey' => array() - ); -} +use lithium\tests\mocks\MockCouchModel; class CouchDbTest extends \lithium\test\Integration { @@ -98,7 +93,6 @@ class CouchDbTest extends \lithium\test\Integration { $this->assertEqual($data['id'], $updated['id']); $this->assertNotEqual($data['rev'], $updated['rev']); } - } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/integration/g11n/CatalogInflectorTest.php b/libraries/lithium/tests/integration/g11n/CatalogInflectorTest.php index 952c848..6c1226d 100644 --- a/libraries/lithium/tests/integration/g11n/CatalogInflectorTest.php +++ b/libraries/lithium/tests/integration/g11n/CatalogInflectorTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/integration/g11n/CatalogValidatorTest.php b/libraries/lithium/tests/integration/g11n/CatalogValidatorTest.php index 69549e3..b871bf5 100644 --- a/libraries/lithium/tests/integration/g11n/CatalogValidatorTest.php +++ b/libraries/lithium/tests/integration/g11n/CatalogValidatorTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/integration/g11n/ResourcesMessageTest.php b/libraries/lithium/tests/integration/g11n/ResourcesMessageTest.php index 84118a1..526dd0a 100644 --- a/libraries/lithium/tests/integration/g11n/ResourcesMessageTest.php +++ b/libraries/lithium/tests/integration/g11n/ResourcesMessageTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -28,7 +28,8 @@ class ResourcesMessageTest extends \lithium\test\Unit { 'lithium' => array( 'adapter' => 'Php', 'path' => LITHIUM_LIBRARY_PATH . '/lithium/g11n/resources/php' - ))); + ) + )); } public function tearDown() { @@ -46,13 +47,11 @@ class ResourcesMessageTest extends \lithium\test\Unit { * @return void */ public function testPlurals1() { - $locales = array( - 'en' - ); + $locales = array('en'); + foreach ($locales as $locale) { - $expected = 2; $result = Catalog::read('lithium', 'message.pluralForms', $locale); - $this->assertEqual($expected, $result, "Locale: `{$locale}`\n{:message}"); + $this->assertEqual(2, $result, "Locale: `{$locale}`\n{:message}"); $rule = Catalog::read('lithium', 'message.pluralRule', $locale); diff --git a/libraries/lithium/tests/integration/g11n/ResourcesValidatorTest.php b/libraries/lithium/tests/integration/g11n/ResourcesValidatorTest.php index c925555..abd8dbc 100644 --- a/libraries/lithium/tests/integration/g11n/ResourcesValidatorTest.php +++ b/libraries/lithium/tests/integration/g11n/ResourcesValidatorTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/integration/net/SocketTest.php b/libraries/lithium/tests/integration/net/SocketTest.php new file mode 100644 index 0000000..6f865c7 --- /dev/null +++ b/libraries/lithium/tests/integration/net/SocketTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\integration\net; + +use lithium\net\socket\Context; +use lithium\net\socket\Curl; +use lithium\net\socket\Stream; + +class SocketTest extends \lithium\test\Integration { + + protected $_testConfig = array( + 'persistent' => false, + 'scheme' => 'http', + 'host' => 'www.google.com', + 'port' => 80, + 'timeout' => 1, + 'classes' => array( + 'request' => 'lithium\net\http\Request', + 'response' => 'lithium\net\http\Response' + ) + ); + + public function skip() { + $config = $this->_testConfig; + $message = "Could not open {$config['host']} - skipping " . __CLASS__; + $this->skipIf($config['host'] == gethostbyname($config['host']), $message); + } + + public function testContextAdapter() { + $socket = new Context($this->_testConfig); + $this->assertTrue($socket->open()); + $response = $socket->send(); + $this->assertTrue($response instanceof \lithium\net\http\Response); + + $expected = 'www.google.com'; + $result = $response->host; + $this->assertEqual($expected, $result); + + $result = $response->body(); + $this->assertPattern("/<title[^>]*>Google<\/title>/im", (string) $result); + } + + public function testCurlAdapter() { + $socket = new Curl($this->_testConfig); + $this->assertTrue($socket->open()); + $response = $socket->send(); + $this->assertTrue($response instanceof \lithium\net\http\Response); + + $expected = 'www.google.com'; + $result = $response->host; + $this->assertEqual($expected, $result); + + $result = $response->body(); + $this->assertPattern("/<title[^>]*>Google<\/title>/im", (string) $result); + } + + public function testStreamAdapter() { + $socket = new Stream($this->_testConfig); + $this->assertTrue($socket->open()); + $response = $socket->send(); + $this->assertTrue($response instanceof \lithium\net\http\Response); + + $expected = 'www.google.com'; + $result = $response->host; + $this->assertEqual($expected, $result); + + $result = $response->body(); + $this->assertPattern("/<title[^>]*>Google<\/title>/im", (string) $result); + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/integration/net/http/ServiceTest.php b/libraries/lithium/tests/integration/net/http/ServiceTest.php index 067611d..e9405fb 100644 --- a/libraries/lithium/tests/integration/net/http/ServiceTest.php +++ b/libraries/lithium/tests/integration/net/http/ServiceTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -17,7 +17,7 @@ class ServiceTest extends \lithium\test\Integration { 'classes' => array('socket' => '\lithium\net\socket\Stream') )); $service->head(); - + $expected = array('code' => 200, 'message' => 'OK'); $result = $service->last->response->status; $this->assertEqual($expected, $result); @@ -28,7 +28,7 @@ class ServiceTest extends \lithium\test\Integration { 'classes' => array('socket' => '\lithium\net\socket\Context') )); $service->head(); - + $expected = array('code' => 200, 'message' => 'OK'); $result = $service->last->response->status; $this->assertEqual($expected, $result); @@ -39,9 +39,11 @@ class ServiceTest extends \lithium\test\Integration { 'classes' => array('socket' => '\lithium\net\socket\Curl') )); $service->head(); - + $expected = array('code' => 200, 'message' => 'OK'); $result = $service->last->response->status; $this->assertEqual($expected, $result); } } + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/integration/storage/CookieTest.php b/libraries/lithium/tests/integration/storage/CookieTest.php new file mode 100644 index 0000000..9278fcd --- /dev/null +++ b/libraries/lithium/tests/integration/storage/CookieTest.php @@ -0,0 +1,145 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\integration\storage; + +use lithium\storage\Session; + +class CookieTest extends \lithium\test\Unit { + + public function setUp() { + Session::reset(); + $cookies = array_keys($_COOKIE); + + foreach ($cookies as $cookie) { + setcookie($cookie, "", time()-1); + } + } + + public function tearDown() { + Session::reset(); + $cookies = array_keys($_COOKIE); + + foreach ($cookies as $cookie) { + setcookie($cookie, "", time()-1); + } + } + + public function testCookieWriteReadDelete() { + Session::config(array( + 'li3' => array( + 'adapter' => 'Cookie', + 'expiry' => '+1 day', + ) + )); + + Session::write('ns.testkey1', 'value1', array('name' => 'li3')); + Session::write('ns.testkey2', 'value2', array('name' => 'li3')); + Session::write('ns.testkey3', 'value3', array('name' => 'li3')); + + $this->assertCookie( + array('key' => 'ns.testkey1', 'value' => 'value1') + ); + $this->assertCookie( + array('key' => 'ns.testkey2', 'value' => 'value2') + ); + $this->assertCookie( + array('key' => 'ns.testkey3', 'value' => 'value3') + ); + + Session::delete('ns.testkey1', array('name' => 'li3')); + Session::delete('ns.testkey2', array('name' => 'li3')); + Session::delete('ns.testkey3', array('name' => 'li3')); + + $params = array('exires' => '-1 second', 'path' => '/'); + + $this->assertNoCookie(array('key' => 'ns.testkey1')); + $this->assertNoCookie(array('key' => 'ns.testkey2')); + $this->assertNoCookie(array('key' => 'ns.testkey3')); + } + + public function testStrategiesPhpAdapter() { + Session::config(array( + 'strategy' => array( + 'adapter' => 'Php', + 'strategies' => array('Hmac' => array('secret' => 'somesecretkey')) + ) + )); + + $key = 'test'; + $value = 'value'; + + Session::write($key, $value, array('name' => 'strategy')); + $result = Session::read($key, array('name' => 'strategy')); + + $this->assertEqual($value, $result); + $this->assertTrue(Session::delete($key, array('name' => 'strategy'))); + $result = Session::read($key, array('name' => 'strategy')); + $this->assertNull($result); + + Session::write($key, $value, array('name' => 'strategy')); + $result = Session::read($key, array('name' => 'strategy')); + $this->assertEqual($value, $result); + + $cache = $_SESSION; + $_SESSION['injectedkey'] = 'hax0r'; + $this->expectException('/Possible data tampering - HMAC signature does not match data./'); + $result = Session::read($key, array('name' => 'strategy')); + $_SESSION = $cache; + } + + public function testStrategiesCookieAdapter() { + $key = 'test_key'; + $value = 'test_value'; + + Session::config(array( + 'default' => array( + 'adapter' => 'Cookie', + 'strategies' => array('Hmac' => array('secret' => 'somesecretkey')), + ) + )); + + $result = Session::write($key, $value); + $this->assertTrue($result); + + $result = Session::read($key); + $this->assertEqual($value, $result); + + $this->assertTrue(Session::delete($key)); + + $result = Session::read($key); + $this->assertNull($result); + + Session::write($key, $value); + $result = Session::read($key); + $this->assertEqual($value, $result); + $this->assertTrue(Session::delete($key)); + } + + public function testHmacStrategy() { + $key = 'test'; + $value = 'value'; + $name = 'hmac_test'; + + Session::config(array( + 'default' => array( + 'adapter' => 'Cookie', + 'strategies' => array('Hmac' => array('secret' => 'somesecretkey')), + 'name' => $name + ) + )); + + $cache = $_COOKIE; + $_COOKIE[$name]['injectedkey'] = 'hax0r'; + $this->expectException('/Possible data tampering - HMAC signature does not match data./'); + $result = Session::read($key, array('name' => 'hmac')); + $_COOKIE = $cache; + } +} + +?> diff --git a/libraries/lithium/tests/integration/storage/SessionTest.php b/libraries/lithium/tests/integration/storage/SessionTest.php index 685963f..b2e1e7c 100644 --- a/libraries/lithium/tests/integration/storage/SessionTest.php +++ b/libraries/lithium/tests/integration/storage/SessionTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/integration/test/FilterTest.php b/libraries/lithium/tests/integration/test/FilterTest.php index a716195..7b9e5be 100644 --- a/libraries/lithium/tests/integration/test/FilterTest.php +++ b/libraries/lithium/tests/integration/test/FilterTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/action/MockCgiRequest.php b/libraries/lithium/tests/mocks/action/MockCgiRequest.php index 86620eb..6ae2818 100644 --- a/libraries/lithium/tests/mocks/action/MockCgiRequest.php +++ b/libraries/lithium/tests/mocks/action/MockCgiRequest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/action/MockControllerRequest.php b/libraries/lithium/tests/mocks/action/MockControllerRequest.php index 2fa63bd..7efb3c8 100644 --- a/libraries/lithium/tests/mocks/action/MockControllerRequest.php +++ b/libraries/lithium/tests/mocks/action/MockControllerRequest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/action/MockControllerResponse.php b/libraries/lithium/tests/mocks/action/MockControllerResponse.php index 063486d..d899718 100644 --- a/libraries/lithium/tests/mocks/action/MockControllerResponse.php +++ b/libraries/lithium/tests/mocks/action/MockControllerResponse.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/action/MockDispatcher.php b/libraries/lithium/tests/mocks/action/MockDispatcher.php index a7df326..8387a0c 100644 --- a/libraries/lithium/tests/mocks/action/MockDispatcher.php +++ b/libraries/lithium/tests/mocks/action/MockDispatcher.php @@ -8,7 +8,7 @@ namespace lithium\tests\mocks\action; -use \stdClass; +use stdClass; class MockDispatcher extends \lithium\action\Dispatcher { diff --git a/libraries/lithium/tests/mocks/action/MockIisRequest.php b/libraries/lithium/tests/mocks/action/MockIisRequest.php index c6731b8..7c483b7 100644 --- a/libraries/lithium/tests/mocks/action/MockIisRequest.php +++ b/libraries/lithium/tests/mocks/action/MockIisRequest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/action/MockMediaClass.php b/libraries/lithium/tests/mocks/action/MockMediaClass.php index 5b51151..b4db02f 100644 --- a/libraries/lithium/tests/mocks/action/MockMediaClass.php +++ b/libraries/lithium/tests/mocks/action/MockMediaClass.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/action/MockPostsController.php b/libraries/lithium/tests/mocks/action/MockPostsController.php index 5b4f724..03a1d3e 100644 --- a/libraries/lithium/tests/mocks/action/MockPostsController.php +++ b/libraries/lithium/tests/mocks/action/MockPostsController.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -27,7 +27,7 @@ class MockPostsController extends \lithium\action\Controller { } public function send() { - $this->redirect('/posts'); + $this->redirect('/posts', array('exit' => true)); } public function type($raw = false) { diff --git a/libraries/lithium/tests/mocks/action/MockRequestType.php b/libraries/lithium/tests/mocks/action/MockRequestType.php index 0a3ecd4..6731f05 100644 --- a/libraries/lithium/tests/mocks/action/MockRequestType.php +++ b/libraries/lithium/tests/mocks/action/MockRequestType.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/action/MockResponse.php b/libraries/lithium/tests/mocks/action/MockResponse.php index 6412bd8..f459245 100644 --- a/libraries/lithium/tests/mocks/action/MockResponse.php +++ b/libraries/lithium/tests/mocks/action/MockResponse.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/analysis/MockLoggerAdapter.php b/libraries/lithium/tests/mocks/analysis/MockLoggerAdapter.php index 3f8055c..aff1675 100644 --- a/libraries/lithium/tests/mocks/analysis/MockLoggerAdapter.php +++ b/libraries/lithium/tests/mocks/analysis/MockLoggerAdapter.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/console/MockCommand.php b/libraries/lithium/tests/mocks/console/MockCommand.php index 93f7ae3..70cc944 100644 --- a/libraries/lithium/tests/mocks/console/MockCommand.php +++ b/libraries/lithium/tests/mocks/console/MockCommand.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -32,7 +32,7 @@ class MockCommand extends \lithium\console\Command { * * @var boolean Describe value of lace. */ - public $lace; + public $lace = true; protected $_dontShow = null; diff --git a/libraries/lithium/tests/mocks/console/MockDispatcherCommand.php b/libraries/lithium/tests/mocks/console/MockDispatcherCommand.php index f1b83e3..8be9d32 100644 --- a/libraries/lithium/tests/mocks/console/MockDispatcherCommand.php +++ b/libraries/lithium/tests/mocks/console/MockDispatcherCommand.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/console/MockDispatcherRequest.php b/libraries/lithium/tests/mocks/console/MockDispatcherRequest.php index 128ff9d..2b48283 100644 --- a/libraries/lithium/tests/mocks/console/MockDispatcherRequest.php +++ b/libraries/lithium/tests/mocks/console/MockDispatcherRequest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/console/MockResponse.php b/libraries/lithium/tests/mocks/console/MockResponse.php index a02f526..39e99f8 100644 --- a/libraries/lithium/tests/mocks/console/MockResponse.php +++ b/libraries/lithium/tests/mocks/console/MockResponse.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/console/command/MockCommandHelp.php b/libraries/lithium/tests/mocks/console/command/MockCommandHelp.php index 3bc452c..ff4e8e8 100644 --- a/libraries/lithium/tests/mocks/console/command/MockCommandHelp.php +++ b/libraries/lithium/tests/mocks/console/command/MockCommandHelp.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -22,6 +22,13 @@ class MockCommandHelp extends \lithium\console\Command { public $long = 'default'; /** + * This is a boolean long param. + * + * @var boolean + */ + public $blong = true; + + /** * This is a short param. * * @var boolean diff --git a/libraries/lithium/tests/mocks/console/command/MockCreate.php b/libraries/lithium/tests/mocks/console/command/MockCreate.php index 743d15d..7739f20 100644 --- a/libraries/lithium/tests/mocks/console/command/MockCreate.php +++ b/libraries/lithium/tests/mocks/console/command/MockCreate.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/console/command/MockLibraryService.php b/libraries/lithium/tests/mocks/console/command/MockLibraryService.php index 11b9c18..7467dac 100644 --- a/libraries/lithium/tests/mocks/console/command/MockLibraryService.php +++ b/libraries/lithium/tests/mocks/console/command/MockLibraryService.php @@ -2,12 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\mocks\console\command; +use lithium\core\Libraries; use lithium\net\http\Response; class MockLibraryService extends \lithium\net\http\Service { @@ -58,6 +59,8 @@ class MockLibraryService extends \lithium\net\http\Service { } private function __data($type, $key = null) { + $resources = Libraries::get(true, 'resources'); + $plugins = array( array( 'name' => 'li3_lab', 'version' => '1.0', @@ -89,7 +92,7 @@ class MockLibraryService extends \lithium\net\http\Service { 'created' => '2009-11-30', 'updated' => '2009-11-30', 'rating' => '9.9', 'downloads' => '1000', 'sources' => array( - 'phar' => LITHIUM_APP_PATH . '/resources/tmp/tests/library_test_plugin.phar.gz' + 'phar' => "{$resources}/tmp/tests/library_test_plugin.phar.gz" ), 'requires' => array( 'li3_lab' => array('version' => '<=1.0') diff --git a/libraries/lithium/tests/mocks/core/MockAdapter.php b/libraries/lithium/tests/mocks/core/MockAdapter.php index 956f7f5..7bb223f 100644 --- a/libraries/lithium/tests/mocks/core/MockAdapter.php +++ b/libraries/lithium/tests/mocks/core/MockAdapter.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockCallable.php b/libraries/lithium/tests/mocks/core/MockCallable.php index 5549fa0..f9084c6 100644 --- a/libraries/lithium/tests/mocks/core/MockCallable.php +++ b/libraries/lithium/tests/mocks/core/MockCallable.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockExposed.php b/libraries/lithium/tests/mocks/core/MockExposed.php index a02f1f0..b4cc760 100644 --- a/libraries/lithium/tests/mocks/core/MockExposed.php +++ b/libraries/lithium/tests/mocks/core/MockExposed.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockInstantiator.php b/libraries/lithium/tests/mocks/core/MockInstantiator.php index d973487..e396004 100644 --- a/libraries/lithium/tests/mocks/core/MockInstantiator.php +++ b/libraries/lithium/tests/mocks/core/MockInstantiator.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockMethodFiltering.php b/libraries/lithium/tests/mocks/core/MockMethodFiltering.php index 9902979..65fbac4 100644 --- a/libraries/lithium/tests/mocks/core/MockMethodFiltering.php +++ b/libraries/lithium/tests/mocks/core/MockMethodFiltering.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockObjectConfiguration.php b/libraries/lithium/tests/mocks/core/MockObjectConfiguration.php index 3b9d271..094e7e5 100644 --- a/libraries/lithium/tests/mocks/core/MockObjectConfiguration.php +++ b/libraries/lithium/tests/mocks/core/MockObjectConfiguration.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockObjectForParents.php b/libraries/lithium/tests/mocks/core/MockObjectForParents.php index 390af4b..d32547e 100644 --- a/libraries/lithium/tests/mocks/core/MockObjectForParents.php +++ b/libraries/lithium/tests/mocks/core/MockObjectForParents.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockRequest.php b/libraries/lithium/tests/mocks/core/MockRequest.php index 389fcb7..a8870f3 100644 --- a/libraries/lithium/tests/mocks/core/MockRequest.php +++ b/libraries/lithium/tests/mocks/core/MockRequest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockStaticFilteringExtended.php b/libraries/lithium/tests/mocks/core/MockStaticFilteringExtended.php index bf38844..b4489b5 100644 --- a/libraries/lithium/tests/mocks/core/MockStaticFilteringExtended.php +++ b/libraries/lithium/tests/mocks/core/MockStaticFilteringExtended.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockStaticInstantiator.php b/libraries/lithium/tests/mocks/core/MockStaticInstantiator.php index 77eee35..823830a 100644 --- a/libraries/lithium/tests/mocks/core/MockStaticInstantiator.php +++ b/libraries/lithium/tests/mocks/core/MockStaticInstantiator.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockStaticMethodFiltering.php b/libraries/lithium/tests/mocks/core/MockStaticMethodFiltering.php index 4cbca2b..e81f30e 100644 --- a/libraries/lithium/tests/mocks/core/MockStaticMethodFiltering.php +++ b/libraries/lithium/tests/mocks/core/MockStaticMethodFiltering.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/core/MockStrategy.php b/libraries/lithium/tests/mocks/core/MockStrategy.php index 2c80e0d..6091562 100644 --- a/libraries/lithium/tests/mocks/core/MockStrategy.php +++ b/libraries/lithium/tests/mocks/core/MockStrategy.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/Companies.php b/libraries/lithium/tests/mocks/data/Companies.php new file mode 100644 index 0000000..5c3a5df --- /dev/null +++ b/libraries/lithium/tests/mocks/data/Companies.php @@ -0,0 +1,18 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\mocks\data; + +class Companies extends \lithium\data\Model { + + public $hasMany = array('Employees'); + + protected $_meta = array('connection' => 'test'); +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/Company.php b/libraries/lithium/tests/mocks/data/Company.php deleted file mode 100644 index 05d3772..0000000 --- a/libraries/lithium/tests/mocks/data/Company.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -/** - * Lithium: the most rad php framework - * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) - * @license http://opensource.org/licenses/bsd-license.php The BSD License - */ - -namespace lithium\tests\mocks\data; - -class Company extends \lithium\data\Model { - - public $hasMany = array('Employees'); - - protected $_meta = array('connection' => 'test'); -} - -?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/Employee.php b/libraries/lithium/tests/mocks/data/Employee.php deleted file mode 100644 index 616a665..0000000 --- a/libraries/lithium/tests/mocks/data/Employee.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Lithium: the most rad php framework - * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) - * @license http://opensource.org/licenses/bsd-license.php The BSD License - */ - -namespace lithium\tests\mocks\data; - -class Employee extends \lithium\data\Model { - - public $belongsTo = array('Company'); - - protected $_meta = array('connection' => 'test'); - - public function lastName($entity) { - $name = explode(' ', $entity->name); - return $name[1]; - } -} - -?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/Employees.php b/libraries/lithium/tests/mocks/data/Employees.php new file mode 100644 index 0000000..fa7e549 --- /dev/null +++ b/libraries/lithium/tests/mocks/data/Employees.php @@ -0,0 +1,23 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\mocks\data; + +class Employees extends \lithium\data\Model { + + public $belongsTo = array('Companies'); + + protected $_meta = array('connection' => 'test'); + + public function lastName($entity) { + $name = explode(' ', $entity->name); + return $name[1]; + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/MockBase.php b/libraries/lithium/tests/mocks/data/MockBase.php index e1d7794..b7ae501 100644 --- a/libraries/lithium/tests/mocks/data/MockBase.php +++ b/libraries/lithium/tests/mocks/data/MockBase.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD(http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD(http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/MockComment.php b/libraries/lithium/tests/mocks/data/MockComment.php index 69437b0..b05fedd 100644 --- a/libraries/lithium/tests/mocks/data/MockComment.php +++ b/libraries/lithium/tests/mocks/data/MockComment.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/MockCouchModel.php b/libraries/lithium/tests/mocks/data/MockCouchModel.php new file mode 100644 index 0000000..c87ef79 --- /dev/null +++ b/libraries/lithium/tests/mocks/data/MockCouchModel.php @@ -0,0 +1,18 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\mocks\data; + +class MockCouchModel extends \lithium\data\Model { + + protected $_schema = array( + 'someKey' => array() + ); +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/MockCreator.php b/libraries/lithium/tests/mocks/data/MockCreator.php index 57af9e9..e10cc00 100644 --- a/libraries/lithium/tests/mocks/data/MockCreator.php +++ b/libraries/lithium/tests/mocks/data/MockCreator.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/MockModel.php b/libraries/lithium/tests/mocks/data/MockModel.php index 266c54a..25d67e4 100644 --- a/libraries/lithium/tests/mocks/data/MockModel.php +++ b/libraries/lithium/tests/mocks/data/MockModel.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/MockPost.php b/libraries/lithium/tests/mocks/data/MockPost.php index c7a445b..38240a5 100644 --- a/libraries/lithium/tests/mocks/data/MockPost.php +++ b/libraries/lithium/tests/mocks/data/MockPost.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD(http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD(http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -12,6 +12,8 @@ class MockPost extends \lithium\tests\mocks\data\MockBase { public $hasMany = array('MockComment'); + public static $connection = null; + public static function resetSchema() { static::_object()->_schema = array(); } @@ -23,6 +25,13 @@ class MockPost extends \lithium\tests\mocks\data\MockBase { public static function instances() { return array_keys(static::$_instances); } + + public static function &connection() { + if (static::$connection) { + return static::$connection; + } + return parent::connection(); + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/MockPostForValidates.php b/libraries/lithium/tests/mocks/data/MockPostForValidates.php index d97c6ec..da33778 100644 --- a/libraries/lithium/tests/mocks/data/MockPostForValidates.php +++ b/libraries/lithium/tests/mocks/data/MockPostForValidates.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/MockPostObject.php b/libraries/lithium/tests/mocks/data/MockPostObject.php index 9a22ab3..767c904 100644 --- a/libraries/lithium/tests/mocks/data/MockPostObject.php +++ b/libraries/lithium/tests/mocks/data/MockPostObject.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD(http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD(http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/MockSource.php b/libraries/lithium/tests/mocks/data/MockSource.php index f66de37..604c2b2 100644 --- a/libraries/lithium/tests/mocks/data/MockSource.php +++ b/libraries/lithium/tests/mocks/data/MockSource.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -13,9 +13,9 @@ use lithium\util\Inflector; class MockSource extends \lithium\data\Source { protected $_classes = array( - 'entity' => '\lithium\data\entity\Record', - 'set' => '\lithium\data\collection\RecordSet', - 'relationship' => '\lithium\data\model\Relationship' + 'entity' => 'lithium\data\entity\Record', + 'set' => 'lithium\data\collection\RecordSet', + 'relationship' => 'lithium\data\model\Relationship' ); private $_mockPosts = array( @@ -129,7 +129,7 @@ class MockSource extends \lithium\data\Source { } - public function cast($model, array $data = array(), array $options = array()) { + public function cast($entity, array $data = array(), array $options = array()) { $defaults = array('first' => false); $options += $defaults; return $options['first'] ? reset($data) : $data; diff --git a/libraries/lithium/tests/mocks/data/MockTag.php b/libraries/lithium/tests/mocks/data/MockTag.php index 2003a10..6abfaf8 100644 --- a/libraries/lithium/tests/mocks/data/MockTag.php +++ b/libraries/lithium/tests/mocks/data/MockTag.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/MockTagging.php b/libraries/lithium/tests/mocks/data/MockTagging.php index 99d48b6..2ce4bae 100644 --- a/libraries/lithium/tests/mocks/data/MockTagging.php +++ b/libraries/lithium/tests/mocks/data/MockTagging.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/collection/MockRecordSet.php b/libraries/lithium/tests/mocks/data/collection/MockRecordSet.php index d93ac87..1aa267f 100644 --- a/libraries/lithium/tests/mocks/data/collection/MockRecordSet.php +++ b/libraries/lithium/tests/mocks/data/collection/MockRecordSet.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/model/MockDatabase.php b/libraries/lithium/tests/mocks/data/model/MockDatabase.php index 67b968f..8738661 100644 --- a/libraries/lithium/tests/mocks/data/model/MockDatabase.php +++ b/libraries/lithium/tests/mocks/data/model/MockDatabase.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -41,7 +41,7 @@ class MockDatabase extends \lithium\data\source\Database { return "'{$value}'"; } - public function cast($model, array $data, array $options = array()) { + public function cast($entity, array $data, array $options = array()) { $defaults = array('first' => false); $options += $defaults; return $options['first'] ? reset($data) : $data; diff --git a/libraries/lithium/tests/mocks/data/model/MockDatabaseComment.php b/libraries/lithium/tests/mocks/data/model/MockDatabaseComment.php index 1be5a61..94806df 100644 --- a/libraries/lithium/tests/mocks/data/model/MockDatabaseComment.php +++ b/libraries/lithium/tests/mocks/data/model/MockDatabaseComment.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/model/MockDatabasePost.php b/libraries/lithium/tests/mocks/data/model/MockDatabasePost.php index 03fab74..b121fee 100644 --- a/libraries/lithium/tests/mocks/data/model/MockDatabasePost.php +++ b/libraries/lithium/tests/mocks/data/model/MockDatabasePost.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/model/MockDocumentMultipleKey.php b/libraries/lithium/tests/mocks/data/model/MockDocumentMultipleKey.php index 8235c0f..a33bb9a 100644 --- a/libraries/lithium/tests/mocks/data/model/MockDocumentMultipleKey.php +++ b/libraries/lithium/tests/mocks/data/model/MockDocumentMultipleKey.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/model/MockDocumentPost.php b/libraries/lithium/tests/mocks/data/model/MockDocumentPost.php index db28c38..e2dbc89 100644 --- a/libraries/lithium/tests/mocks/data/model/MockDocumentPost.php +++ b/libraries/lithium/tests/mocks/data/model/MockDocumentPost.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -20,7 +20,10 @@ class MockDocumentPost extends \lithium\data\Model { public static function __init() {} public static function schema($field = null) { - return array('_id' => array('type' => 'id')); + return array( + '_id' => array('type' => 'id'), + 'foo.bar' => array('type' => 'int') + ); } public function ret($record, $param1 = null, $param2 = null) { diff --git a/libraries/lithium/tests/mocks/data/model/MockDocumentSource.php b/libraries/lithium/tests/mocks/data/model/MockDocumentSource.php index 8dbc166..40ef566 100644 --- a/libraries/lithium/tests/mocks/data/model/MockDocumentSource.php +++ b/libraries/lithium/tests/mocks/data/model/MockDocumentSource.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -45,54 +45,79 @@ class MockDocumentSource extends \lithium\data\Source { return $this->result[$this->point++]; } - public function cast($model, array $data, array $options = array()) { + public function cast($entity, array $data, array $options = array()) { $defaults = array('schema' => null, 'first' => false, 'pathKey' => null, 'arrays' => true); $options += $defaults; + $model = null; - if ($model && !$options['schema']) { - $options['schema'] = $model::schema(); + if (!$data) { + return $data; } + if ($entity && !$options['schema']) { + $options['schema'] = $entity->schema() ?: array('_id' => array('type' => 'id')); + } + if ($entity) { + $model = $entity->model(); + } + $schema = $options['schema']; + unset($options['schema']); + $handlers = array( 'id' => function($v) { return is_string($v) && preg_match('/^[0-9a-f]{24}$/', $v) ? new MongoId($v) : $v; }, 'date' => function($v) { - return new MongoDate(is_numeric($v) ? intval($v) : strtotime($v)); + $v = is_numeric($v) ? intval($v) : strtotime($v); + return (time() == $v) ? new MongoDate() : new MongoDate($v); }, - 'regex' => function($v) { - return new MongoRegex($v); - } + 'regex' => function($v) { return new MongoRegex($v); }, + 'integer' => function($v) { return (integer) $v; }, + 'float' => function($v) { return (float) $v; }, + 'boolean' => function($v) { return (boolean) $v; }, + 'code' => function($v) { return new MongoCode($v); }, + 'binary' => function($v) { return new MongoBinData($v); }, ); $typeMap = array( - 'MongoId' => 'id', - 'MongoDate' => 'date', - 'datetime' => 'date', - 'timestamp' => 'date', + 'MongoId' => 'id', + 'MongoDate' => 'date', + 'MongoCode' => 'code', + 'MongoBinData' => 'binary', + 'datetime' => 'date', + 'timestamp' => 'date', + 'int' => 'integer' ); foreach ($data as $key => $value) { if (is_object($value)) { continue; } - $schema = isset($options['schema'][$key]) ? $options['schema'][$key] : array(); - $schema += array('type' => null, 'array' => null); - $type = isset($typeMap[$schema['type']]) ? $typeMap[$schema['type']] : $schema['type']; - $isArray = (is_array($value) && $schema['array'] !== false); + $path = is_int($key) ? null : $key; + $path = $options['pathKey'] ? trim("{$options['pathKey']}.{$path}", '.') : $path; + $field = (isset($schema[$path]) ? $schema[$path] : array()); + $field += array('type' => null, 'array' => null); + $type = isset($typeMap[$field['type']]) ? $typeMap[$field['type']] : $field['type']; + $isObject = ($type == 'object'); + $isArray = (is_array($value) && $field['array'] !== false && !$isObject); if (isset($handlers[$type])) { $handler = $handlers[$type]; $value = $isArray ? array_map($handler, $value) : $handler($value); } - if ($options['arrays']) { - if (is_array($value)) { - $arrayType = (array_keys($value) === range(0, count($value) - 1)); - $options = $arrayType ? array('class' => 'array') + $options : $options; - $value = $this->item($model, $value, $options); - } elseif ($schema['array']) { - $value = $this->item($model, array($value), array('class' => 'array') + $options); - } + if (!$options['arrays']) { + $data[$key] = $value; + continue; + } + $pathKey = $path; + + if (is_array($value)) { + $arrayType = !$isObject && (array_keys($value) === range(0, count($value) - 1)); + $opts = $arrayType ? array('class' => 'array') + $options : $options; + $value = $this->item($model, $value, compact('pathKey') + $opts); + } elseif ($field['array']) { + $opts = array('class' => 'array') + $options; + $value = $this->item($model, array($value), compact('pathKey') + $opts); } $data[$key] = $value; } diff --git a/libraries/lithium/tests/mocks/data/model/MockQueryComment.php b/libraries/lithium/tests/mocks/data/model/MockQueryComment.php index 94de285..6805db2 100644 --- a/libraries/lithium/tests/mocks/data/model/MockQueryComment.php +++ b/libraries/lithium/tests/mocks/data/model/MockQueryComment.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/model/MockQueryPost.php b/libraries/lithium/tests/mocks/data/model/MockQueryPost.php index bc38ec9..e5ad8d1 100644 --- a/libraries/lithium/tests/mocks/data/model/MockQueryPost.php +++ b/libraries/lithium/tests/mocks/data/model/MockQueryPost.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/model/mock_database/MockResult.php b/libraries/lithium/tests/mocks/data/model/mock_database/MockResult.php index 5762d8a..e2f28a2 100644 --- a/libraries/lithium/tests/mocks/data/model/mock_database/MockResult.php +++ b/libraries/lithium/tests/mocks/data/model/mock_database/MockResult.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/Galleries.php b/libraries/lithium/tests/mocks/data/source/Galleries.php index e9f73b9..b739fbf 100644 --- a/libraries/lithium/tests/mocks/data/source/Galleries.php +++ b/libraries/lithium/tests/mocks/data/source/Galleries.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/Images.php b/libraries/lithium/tests/mocks/data/source/Images.php index 3bf71c0..068bdd6 100644 --- a/libraries/lithium/tests/mocks/data/source/Images.php +++ b/libraries/lithium/tests/mocks/data/source/Images.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/MockHttpModel.php b/libraries/lithium/tests/mocks/data/source/MockHttpModel.php index 154e4b5..072d273 100644 --- a/libraries/lithium/tests/mocks/data/source/MockHttpModel.php +++ b/libraries/lithium/tests/mocks/data/source/MockHttpModel.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/MockMongoConnection.php b/libraries/lithium/tests/mocks/data/source/MockMongoConnection.php index 6c034d2..e45d02f 100644 --- a/libraries/lithium/tests/mocks/data/source/MockMongoConnection.php +++ b/libraries/lithium/tests/mocks/data/source/MockMongoConnection.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/MockMongoPost.php b/libraries/lithium/tests/mocks/data/source/MockMongoPost.php index 7ecea0c..8535e25 100644 --- a/libraries/lithium/tests/mocks/data/source/MockMongoPost.php +++ b/libraries/lithium/tests/mocks/data/source/MockMongoPost.php @@ -2,12 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\mocks\data\source; +use lithium\data\source\MongoDb; + class MockMongoPost extends \lithium\data\Model { protected $_meta = array( @@ -15,12 +17,34 @@ class MockMongoPost extends \lithium\data\Model { 'source' => 'posts' ); + protected $_connection; + + protected $_useRealConnection = true; + public static function schema($field = null) { if (is_array($field)) { return static::_object()->_schema = $field; } return parent::schema($field); } + + public static function &connection() { + $self = static::_object(); + + if ($self->_useRealConnection) { + return parent::connection(); + } + if (!$self->_connection) { + $self->_connection = new MongoDb(array('autoConnect' => false)); + } + return $self->_connection; + } + + public static function resetConnection($mock) { + $self = static::_object(); + $self->_connection = null; + $self->_useRealConnection = !$mock; + } } ?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/source/MockMongoSource.php b/libraries/lithium/tests/mocks/data/source/MockMongoSource.php new file mode 100644 index 0000000..abf159b --- /dev/null +++ b/libraries/lithium/tests/mocks/data/source/MockMongoSource.php @@ -0,0 +1,40 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\mocks\data\source; + +use MongoId; +use lithium\tests\mocks\data\source\mongo_db\MockResult; + +class MockMongoSource extends \lithium\core\Object { + + public $resultSets = array(); + + public $queries = array(); + + public function __get($name) { + return $this; + } + + public function insert(&$data, $options) { + $this->queries[] = compact('data', 'options'); + $result = current($this->resultSets); + next($this->resultSets); + $data['_id'] = new MongoId(); + return $result; + } + + public function find($conditions, $fields) { + $this->queries[] = compact('conditions', 'fields'); + $result = new MockResult(array('data' => current($this->resultSets))); + next($this->resultSets); + return $result; + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/data/source/database/adapter/MockAdapter.php b/libraries/lithium/tests/mocks/data/source/database/adapter/MockAdapter.php index 55b274a..edd3962 100644 --- a/libraries/lithium/tests/mocks/data/source/database/adapter/MockAdapter.php +++ b/libraries/lithium/tests/mocks/data/source/database/adapter/MockAdapter.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/database/adapter/MockMySql.php b/libraries/lithium/tests/mocks/data/source/database/adapter/MockMySql.php index 74a482d..d6e1d40 100644 --- a/libraries/lithium/tests/mocks/data/source/database/adapter/MockMySql.php +++ b/libraries/lithium/tests/mocks/data/source/database/adapter/MockMySql.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD,http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD,http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/database/adapter/MockSqlite3.php b/libraries/lithium/tests/mocks/data/source/database/adapter/MockSqlite3.php index 06f4442..74c0148 100644 --- a/libraries/lithium/tests/mocks/data/source/database/adapter/MockSqlite3.php +++ b/libraries/lithium/tests/mocks/data/source/database/adapter/MockSqlite3.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/http/adapter/MockCouchPost.php b/libraries/lithium/tests/mocks/data/source/http/adapter/MockCouchPost.php index 0d3f417..1c07cd2 100644 --- a/libraries/lithium/tests/mocks/data/source/http/adapter/MockCouchPost.php +++ b/libraries/lithium/tests/mocks/data/source/http/adapter/MockCouchPost.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/data/source/http/adapter/MockSocket.php b/libraries/lithium/tests/mocks/data/source/http/adapter/MockSocket.php index 38b3d66..7b8fbea 100644 --- a/libraries/lithium/tests/mocks/data/source/http/adapter/MockSocket.php +++ b/libraries/lithium/tests/mocks/data/source/http/adapter/MockSocket.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -53,7 +53,7 @@ class MockSocket extends \lithium\net\Socket { return true; } - public function send($message, array $options = array()) { + public function send($message = null, array $options = array()) { $message = $this->read(); return new $options['classes']['response'](compact('message')); } diff --git a/libraries/lithium/tests/mocks/data/source/mongo_db/MockResult.php b/libraries/lithium/tests/mocks/data/source/mongo_db/MockResult.php index f4b6b65..72799e2 100644 --- a/libraries/lithium/tests/mocks/data/source/mongo_db/MockResult.php +++ b/libraries/lithium/tests/mocks/data/source/mongo_db/MockResult.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -10,22 +10,42 @@ namespace lithium\tests\mocks\data\source\mongo_db; class MockResult extends \lithium\data\source\mongo_db\Result { + protected $_autoConfig = array('data'); + protected $_data = array( - false, array('_id' => '4c8f86167675abfabdbf0300', 'title' => 'bar'), array('_id' => '5c8f86167675abfabdbf0301', 'title' => 'foo'), array('_id' => '6c8f86167675abfabdbf0302', 'title' => 'dib') ); + public function hasNext() { + if (!is_array($this->_data)) { + return false; + } + return key($this->_data) !== null && key($this->_data) < count($this->_data); + } + + public function getNext() { + $result = current($this->_data); + next($this->_data); + return $result; + } + public function next() { return $this->_next(); } + public function __call($method, array $params) { + return $this; + } + protected function _close() { } protected function _next() { - return next($this->_data) ?: null; + $result = current($this->_data) ?: null; + next($this->_data); + return $result; } } diff --git a/libraries/lithium/tests/mocks/g11n/catalog/MockAdapter.php b/libraries/lithium/tests/mocks/g11n/catalog/MockAdapter.php index 5105f3a..1a4159a 100644 --- a/libraries/lithium/tests/mocks/g11n/catalog/MockAdapter.php +++ b/libraries/lithium/tests/mocks/g11n/catalog/MockAdapter.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/g11n/catalog/adapter/MockGettext.php b/libraries/lithium/tests/mocks/g11n/catalog/adapter/MockGettext.php index 629385e..d3661d4 100644 --- a/libraries/lithium/tests/mocks/g11n/catalog/adapter/MockGettext.php +++ b/libraries/lithium/tests/mocks/g11n/catalog/adapter/MockGettext.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/net/http/MockSocket.php b/libraries/lithium/tests/mocks/net/http/MockSocket.php index 01f90ed..61903eb 100644 --- a/libraries/lithium/tests/mocks/net/http/MockSocket.php +++ b/libraries/lithium/tests/mocks/net/http/MockSocket.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -32,30 +32,17 @@ class MockSocket extends \lithium\net\Socket { } public function read() { - if (is_object($this->data)) { - $data = "HTTP/1.1 200 OK\r\n" . - join("\r\n", $this->data->headers()) . - "\r\n\r\n" . - $this->data->body(); - } - return $data; + return $this->data; } public function write($data) { + if (!is_object($data)) { + $data = $this->_instance($this->_classes['request'], (array) $data + $this->_config); + } $this->data = $data; return true; } - public function send($message, array $options = array()) { - $defaults = array('response' => $this->_classes['response']); - $options += $defaults; - - if ($this->write($message)) { - $body = $this->read(); - return new $options['response'](compact('message')); - } - } - public function timeout($time) { return true; } diff --git a/libraries/lithium/tests/mocks/net/http/Template.php b/libraries/lithium/tests/mocks/net/http/Template.php index dcfcd64..263b65d 100644 --- a/libraries/lithium/tests/mocks/net/http/Template.php +++ b/libraries/lithium/tests/mocks/net/http/Template.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/security/auth/adapter/MockHttp.php b/libraries/lithium/tests/mocks/security/auth/adapter/MockHttp.php index 594a1c2..99fa802 100644 --- a/libraries/lithium/tests/mocks/security/auth/adapter/MockHttp.php +++ b/libraries/lithium/tests/mocks/security/auth/adapter/MockHttp.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/storage/cache/strategy/MockConfigurizer.php b/libraries/lithium/tests/mocks/storage/cache/strategy/MockConfigurizer.php index b78e3a3..b90cf92 100644 --- a/libraries/lithium/tests/mocks/storage/cache/strategy/MockConfigurizer.php +++ b/libraries/lithium/tests/mocks/storage/cache/strategy/MockConfigurizer.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/storage/cache/strategy/MockSerializer.php b/libraries/lithium/tests/mocks/storage/cache/strategy/MockSerializer.php index 84cd9b7..874e5ab 100644 --- a/libraries/lithium/tests/mocks/storage/cache/strategy/MockSerializer.php +++ b/libraries/lithium/tests/mocks/storage/cache/strategy/MockSerializer.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/storage/session/strategy/MockCookieSession.php b/libraries/lithium/tests/mocks/storage/session/strategy/MockCookieSession.php new file mode 100644 index 0000000..ba5e2b4 --- /dev/null +++ b/libraries/lithium/tests/mocks/storage/session/strategy/MockCookieSession.php @@ -0,0 +1,43 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\mocks\storage\session\strategy; + +class MockCookieSession extends \lithium\core\Object { + + protected static $_secret = 'foobar'; + + protected static $_data = array('one' => 'foo', 'two' => 'bar'); + + public static function read($key = null, array $options = array()) { + if (isset(static::$_data[$key])) { + return static::$_data[$key]; + } + return static::$_data; + } + + public static function write($key, $value = null, array $options = array()) { + static::$_data[$key] = $value; + return $value; + } + + public static function reset() { + return static::$_data = array('one' => 'foo', 'two' => 'bar'); + } + + /** + * Method for returning data currently stored in this mock. + * + * @return array + */ + public static function data() { + return static::$_data; + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/template/MockHelper.php b/libraries/lithium/tests/mocks/template/MockHelper.php index d67a5aa..35c9166 100644 --- a/libraries/lithium/tests/mocks/template/MockHelper.php +++ b/libraries/lithium/tests/mocks/template/MockHelper.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -11,6 +11,7 @@ namespace lithium\tests\mocks\template; class MockHelper extends \lithium\template\Helper { protected $_strings = array('link' => '<a href="{:url}"{:options}>{:title}</a>'); + /** * Hack to expose protected properties for testing. * diff --git a/libraries/lithium/tests/mocks/template/MockRenderer.php b/libraries/lithium/tests/mocks/template/MockRenderer.php index 1ce3282..5ac3f46 100644 --- a/libraries/lithium/tests/mocks/template/MockRenderer.php +++ b/libraries/lithium/tests/mocks/template/MockRenderer.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/template/MockView.php b/libraries/lithium/tests/mocks/template/MockView.php new file mode 100644 index 0000000..7609377 --- /dev/null +++ b/libraries/lithium/tests/mocks/template/MockView.php @@ -0,0 +1,18 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace lithium\tests\mocks\template; + +class MockView extends \lithium\template\View { + + public function renderer() { + return $this->_config['renderer']; + } +} + +?> \ No newline at end of file diff --git a/libraries/lithium/tests/mocks/template/helper/MockFormPost.php b/libraries/lithium/tests/mocks/template/helper/MockFormPost.php index 29681ba..66ae96d 100644 --- a/libraries/lithium/tests/mocks/template/helper/MockFormPost.php +++ b/libraries/lithium/tests/mocks/template/helper/MockFormPost.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/template/helper/MockFormRenderer.php b/libraries/lithium/tests/mocks/template/helper/MockFormRenderer.php index d066348..97b25eb 100644 --- a/libraries/lithium/tests/mocks/template/helper/MockFormRenderer.php +++ b/libraries/lithium/tests/mocks/template/helper/MockFormRenderer.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/template/helper/MockHtmlRenderer.php b/libraries/lithium/tests/mocks/template/helper/MockHtmlRenderer.php index 2c093f2..8183fc2 100644 --- a/libraries/lithium/tests/mocks/template/helper/MockHtmlRenderer.php +++ b/libraries/lithium/tests/mocks/template/helper/MockHtmlRenderer.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/test/MockFilterClass.php b/libraries/lithium/tests/mocks/test/MockFilterClass.php index eb52af0..71db405 100644 --- a/libraries/lithium/tests/mocks/test/MockFilterClass.php +++ b/libraries/lithium/tests/mocks/test/MockFilterClass.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/test/MockFilterClassTest.php b/libraries/lithium/tests/mocks/test/MockFilterClassTest.php index 6cfc727..a5f21af 100644 --- a/libraries/lithium/tests/mocks/test/MockFilterClassTest.php +++ b/libraries/lithium/tests/mocks/test/MockFilterClassTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/test/MockIntegrationTest.php b/libraries/lithium/tests/mocks/test/MockIntegrationTest.php index 40a691a..4d5dfba 100644 --- a/libraries/lithium/tests/mocks/test/MockIntegrationTest.php +++ b/libraries/lithium/tests/mocks/test/MockIntegrationTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/test/MockUnitTest.php b/libraries/lithium/tests/mocks/test/MockUnitTest.php index 4720fc6..bf364bc 100644 --- a/libraries/lithium/tests/mocks/test/MockUnitTest.php +++ b/libraries/lithium/tests/mocks/test/MockUnitTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/test/cases/MockSkipThrowsException.php b/libraries/lithium/tests/mocks/test/cases/MockSkipThrowsException.php index ee8fdb1..013fa64 100644 --- a/libraries/lithium/tests/mocks/test/cases/MockSkipThrowsException.php +++ b/libraries/lithium/tests/mocks/test/cases/MockSkipThrowsException.php @@ -2,13 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\mocks\test\cases; -use \Exception; +use Exception; class MockSkipThrowsException extends \lithium\test\Unit { diff --git a/libraries/lithium/tests/mocks/test/cases/MockTest.php b/libraries/lithium/tests/mocks/test/cases/MockTest.php index 024dc5a..9bce9dc 100644 --- a/libraries/lithium/tests/mocks/test/cases/MockTest.php +++ b/libraries/lithium/tests/mocks/test/cases/MockTest.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/test/cases/MockTestErrorHandling.php b/libraries/lithium/tests/mocks/test/cases/MockTestErrorHandling.php index 8caa9f4..8ebd70c 100644 --- a/libraries/lithium/tests/mocks/test/cases/MockTestErrorHandling.php +++ b/libraries/lithium/tests/mocks/test/cases/MockTestErrorHandling.php @@ -2,13 +2,13 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\tests\mocks\test\cases; -use \Exception; +use Exception; class MockTestErrorHandling extends \lithium\test\Unit { diff --git a/libraries/lithium/tests/mocks/util/MockCollectionMarker.php b/libraries/lithium/tests/mocks/util/MockCollectionMarker.php index f0c8155..e1798a0 100644 --- a/libraries/lithium/tests/mocks/util/MockCollectionMarker.php +++ b/libraries/lithium/tests/mocks/util/MockCollectionMarker.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/util/MockCollectionObject.php b/libraries/lithium/tests/mocks/util/MockCollectionObject.php index c820ba2..8807257 100644 --- a/libraries/lithium/tests/mocks/util/MockCollectionObject.php +++ b/libraries/lithium/tests/mocks/util/MockCollectionObject.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/util/MockCollectionStringCast.php b/libraries/lithium/tests/mocks/util/MockCollectionStringCast.php index 5af3cc2..0dd9b58 100644 --- a/libraries/lithium/tests/mocks/util/MockCollectionStringCast.php +++ b/libraries/lithium/tests/mocks/util/MockCollectionStringCast.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/util/MockFilters.php b/libraries/lithium/tests/mocks/util/MockFilters.php index f246319..d13d8d3 100644 --- a/libraries/lithium/tests/mocks/util/MockFilters.php +++ b/libraries/lithium/tests/mocks/util/MockFilters.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/tests/mocks/util/MockStringObject.php b/libraries/lithium/tests/mocks/util/MockStringObject.php index 510efd2..e093c67 100644 --- a/libraries/lithium/tests/mocks/util/MockStringObject.php +++ b/libraries/lithium/tests/mocks/util/MockStringObject.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/util/Collection.php b/libraries/lithium/util/Collection.php index 3c22fac..ddabf61 100644 --- a/libraries/lithium/util/Collection.php +++ b/libraries/lithium/util/Collection.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ diff --git a/libraries/lithium/util/Inflector.php b/libraries/lithium/util/Inflector.php index 3addfed..1b33aa4 100644 --- a/libraries/lithium/util/Inflector.php +++ b/libraries/lithium/util/Inflector.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * Copyright 2010, Cake Software Foundation, Inc. (http://cakefoundation.org) * @license http://opensource.org/licenses/mit-license.php The MIT License */ diff --git a/libraries/lithium/util/Set.php b/libraries/lithium/util/Set.php index 8b8def3..574bdf4 100644 --- a/libraries/lithium/util/Set.php +++ b/libraries/lithium/util/Set.php @@ -2,9 +2,8 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) - * Copyright 2009, Cake Software Foundation, Inc. (http://cakefoundation.org) - * @license http://opensource.org/licenses/mit-license.php The MIT License + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License */ namespace lithium\util; @@ -33,27 +32,26 @@ class Set { * Add the keys/values in `$array2` that are not found in `$array` onto the end of `$array`. * * @param mixed $array Original array. - * @param mixed $array2 Second array to add onto the Original. - * @return array A Blended array of keys and values. + * @param mixed $array2 Second array to add onto the original. + * @return array An array containing all the keys of the second array not already present in the + * first. */ - public static function blend($array, $array2) { - if (empty($array) && !empty($array2)) { + public static function append(array $array, array $array2) { + if (!$array && $array2) { return $array2; } - if (!empty($array) && !empty($array2)) { - foreach ($array2 as $key => $value) { - if (!isset($array[$key])) { - $array[$key] = $value; - } elseif (is_array($value)) { - $array[$key] = static::blend($array[$key], $array2[$key]); - } + foreach ($array2 as $key => $value) { + if (!isset($array[$key])) { + $array[$key] = $value; + } elseif (is_array($value)) { + $array[$key] = static::append($array[$key], $array2[$key]); } } return $array; } /** - * Checks if a particular path is set in an array + * Checks if a particular path is set in an array. * * @param mixed $data Data to check on. * @param mixed $path A dot-delimited string. @@ -63,9 +61,8 @@ class Set { if (!$path) { return $data; } - if (!is_array($path)) { - $path = explode('.', $path); - } + $path = is_array($path) ? $path : explode('.', $path); + foreach ($path as $i => $key) { if (is_numeric($key) && intval($key) > 0 || $key === '0') { $key = intval($key); @@ -94,7 +91,7 @@ class Set { * @return array Combined array. */ public static function combine($data, $path1 = null, $path2 = null, $groupPath = null) { - if (empty($data)) { + if (!$data) { return array(); } if (is_object($data)) { @@ -139,20 +136,23 @@ class Set { } /** - * Determines if `val2` is contained in `val1` + * Determines if the array elements in `$array2` are wholly contained within `$array1`. Works + * recursively. * - * @param array $val1 First value. - * @param array $val2 Second value. - * @return boolean true if `$val1` contains `$val2`, `false` otherwise. + * @param array $array1 First value. + * @param array $array2 Second value. + * @return boolean Returns `true` if `$array1` wholly contains the keys and values of `$array2`, + * otherwise, returns `false`. Returns `false` if either array is empty. */ - public static function contains($val1, $val2) { - if (empty($val1) || empty($val2)) { + public static function contains(array $array1, array $array2) { + if (!$array1 || !$array2) { return false; } - foreach ((array) $val2 as $key => $val) { - if (is_numeric($key)) { - static::contains($val, $val1); - } elseif (!isset($val1[$key]) || $val1[$key] != $val) { + foreach ($array2 as $key => $val) { + if (!isset($array1[$key]) || $array1[$key] != $val) { + return false; + } + if (is_array($val) && !static::contains($array1[$key], $val)) { return false; } } @@ -165,58 +165,49 @@ class Set { * * @param array $data Array to count dimensions on. * @param array $options - * @param integer $count Start the depth count at this number. * @return integer The number of dimensions in `$array`. */ - public static function depth($data, $options = array(), $count = 0) { - if (empty($data)) { + public static function depth($data, array $options = array()) { + $defaults = array('all' => false, 'count' => 0); + $options += $defaults; + + if (!$data) { return 0; } - if (!is_array($options)) { - $options = array('all' => $options, 'count' => $count); + + if (!$options['all']) { + return (is_array(reset($data))) ? static::depth(reset($data)) + 1 : 1; } - $defaults = array('all' => false, 'count' => 0); - $options += $defaults; + $depth = array($options['count']); - if (!empty($options['all'])) { - $depth = array($options['count']); - if (is_array($data) && reset($data) !== false) { - foreach ($data as $value) { - $depth[] = static::depth($value, array( - 'all' => $options['all'], - 'count' => $options['count'] + 1 - )); - } + if (is_array($data) && reset($data) !== false) { + foreach ($data as $value) { + $depth[] = static::depth($value, array( + 'all' => $options['all'], + 'count' => $options['count'] + 1 + )); } - return max($depth); } - if (is_array(reset($data))) { - return static::depth(reset($data)) + 1; - } - return 1; + return max($depth); } /** * Computes the difference between two arrays. * - * @param mixed $val1 First value. - * @param mixed $val2 Second value. + * @param array $val1 First value. + * @param array $val2 Second value. * @return array Computed difference. */ - public static function diff($val1, $val2 = null) { - if (!$val1) { - return (array) $val2; - } elseif (!$val2) { - return (array) $val1; + public static function diff(array $val1, array $val2) { + if (!$val1 || !$val2) { + return $val2 ?: $val1; } $out = array(); foreach ($val1 as $key => $val) { $exists = isset($val2[$key]); - if ($exists && $val2[$key] != $val) { - $out[$key] = $val; - } elseif (!$exists) { + if (($exists && $val2[$key] != $val) || !$exists) { $out[$key] = $val; } unset($val2[$key]); @@ -253,23 +244,26 @@ class Set { * disabled for higher XPath-ness. * @return array An array of matched items. */ - public static function extract($data, $path = null, array $options = array()) { - if (empty($data)) { + public static function extract(array $data, $path = null, array $options = array()) { + if (!$data) { return array(); } + if (is_string($data)) { $tmp = $path; $path = $data; $data = $tmp; unset($tmp); } + if ($path === '/') { return array_filter($data, function($data) { return ($data === 0 || $data === '0' || !empty($data)); }); } $contexts = $data; - $options = array_merge(array('flatten' => true), $options); + $defaults = array('flatten' => true); + $options += $defaults; if (!isset($contexts[0])) { $contexts = array($data); @@ -279,11 +273,13 @@ class Set { do { $token = array_shift($tokens); $conditions = false; + if (preg_match_all('/\[([^=]+=\/[^\/]+\/|[^\]]+)\]/', $token, $m)) { $conditions = $m[1]; $token = substr($token, 0, strpos($token, '[')); } $matches = array(); + foreach ($contexts as $key => $context) { if (!isset($context['trace'])) { $context = array('trace' => array(null), 'item' => $context, 'key' => $key); @@ -293,7 +289,7 @@ class Set { $context['trace'][] = $context['key']; } $parent = join('/', $context['trace']) . '/.'; - $context['item'] = static::extract($parent, $data); + $context['item'] = static::extract($data, $parent); $context['key'] = array_pop($context['trace']); if (isset($context['trace'][1]) && $context['trace'][1] > 0) { $context['item'] = $context['item'][0]; @@ -365,8 +361,9 @@ class Set { foreach ($conditions as $condition) { $filtered = array(); $length = count($matches); + foreach ($matches as $i => $match) { - if (static::matches(array($condition), $match['item'], $i + 1, $length)) { + if (static::matches($match['item'], array($condition), $i + 1, $length)) { $filtered[] = $match; } } @@ -403,26 +400,52 @@ class Set { * - `'path'`: Starting point (defaults to null). * @return array */ - public static function flatten($data, $options = array()) { - $result = array(); - - if (!is_array($options)) { - $options = array('separator' => $options); - } + public static function flatten($data, array $options = array()) { $defaults = array('separator' => '.', 'path' => null); $options += $defaults; + $result = array(); if (!is_null($options['path'])) { $options['path'] .= $options['separator']; } foreach ($data as $key => $val) { - if (is_array($val)) { - $result += (array) static::flatten($val, array( - 'separator' => $options['separator'], - 'path' => $options['path'] . $key - )); - } else { + if (!is_array($val)) { $result[$options['path'] . $key] = $val; + continue; + } + $opts = array('separator' => $options['separator'], 'path' => $options['path'] . $key); + $result += (array) static::flatten($val, $opts); + } + return $result; + } + + /** + * Accepts a one-dimensional array where the keys are separated by a delimiter. + * + * @param array $data The one-dimensional array to expand. + * @param array $options The options used when expanding the array: + * - `'separator'` _string_: The delimiter to use when separating keys. Defaults + * to `'.'`. + * @return array Returns a multi-dimensional array expanded from a one dimensional dot-separated + * array. + */ + public static function expand(array $data, array $options = array()) { + $defaults = array('separator' => '.'); + $options += $defaults; + $result = array(); + + foreach ($data as $key => $val) { + if (strpos($key, $options['separator']) === false) { + $result[$key] = $val; + continue; + } + list($path, $key) = explode($options['separator'], $key, 2); + $path = is_numeric($path) ? intval($path) : $path; + $result[$path][$key] = $val; + } + foreach ($result as $key => $value) { + if (is_array($value)) { + $result[$key] = static::expand($value, $options); } } return $result; @@ -468,17 +491,19 @@ class Set { } $out[] = $formatted; } - } else { - $count2 = count($data); - for ($j = 0; $j < $count; $j++) { - $args = array(); - for ($i = 0; $i < $count2; $i++) { - if (isset($data[$i][$j])) { - $args[] = $data[$i][$j]; - } + return $out; + } + $count2 = count($data); + + for ($j = 0; $j < $count; $j++) { + $args = array(); + + for ($i = 0; $i < $count2; $i++) { + if (isset($data[$i][$j])) { + $args[] = $data[$i][$j]; } - $out[] = vsprintf($format, $args); } + $out[] = vsprintf($format, $args); } return $out; } @@ -491,7 +516,7 @@ class Set { * @param array $data Data to insert. * @return array */ - public static function insert($list, $path, $data = null) { + public static function insert($list, $path, $data = array()) { if (!is_array($path)) { $path = explode('.', $path); } @@ -543,18 +568,18 @@ class Set { * This function can be used to see if a single item or a given XPath * match certain conditions. * - * @param mixed $conditions An array of condition strings or an XPath expression. * @param array $data An array of data to execute the match on. + * @param mixed $conditions An array of condition strings or an XPath expression. * @param integer $i Optional: The 'nth'-number of the item being matched. * @param integer $length * @return boolean */ - public static function matches($conditions, $data = array(), $i = null, $length = null) { - if (empty($conditions)) { + public static function matches($data = array(), $conditions, $i = null, $length = null) { + if (!$conditions) { return true; } - if (is_string($conditions) || is_string($data)) { - return !!static::extract($data, $conditions); + if (is_string($conditions)) { + return (boolean) static::extract($data, $conditions); } foreach ($conditions as $condition) { if ($condition === ':last') { @@ -615,18 +640,15 @@ class Set { * with an unlimited amount of arguments and typecasts non-array parameters * into arrays. * - * @param array $arr1 The base array. - * @param array $arr2 The array to be merged on top of the base array. + * @param array $array1 The base array. + * @param array $array2 The array to be merged on top of the base array. * @return array Merged array of all passed params. */ - public static function merge($arr1, $arr2 = null) { - $args = array($arr1, $arr2); + public static function merge(array $array1, array $array2) { + $args = array($array1, $array2); - if (empty($arr1) && empty($arr2)) { - return array(); - } - if (empty($arr1) || empty($arr2)) { - return empty($arr1) ? (array) $arr2 : (array) $arr1; + if (!$array1 || !$array2) { + return $array1 ?: $array2; } $result = (array) current($args); @@ -656,39 +678,37 @@ class Set { public static function normalize($list, $assoc = true, $sep = ',', $trim = true) { if (is_string($list)) { $list = explode($sep, $list); - if ($trim) { - foreach ($list as $key => $value) { - $list[$key] = trim($value); - } - } - if ($assoc) { - return static::normalize($list); - } - } elseif (is_array($list)) { - $keys = array_keys($list); - $count = count($keys); - $numeric = true; + $list = ($trim) ? array_map('trim', $list) : $list; + return ($assoc) ? static::normalize($list) : $list; + } - if (!$assoc) { - for ($i = 0; $i < $count; $i++) { - if (!is_int($keys[$i])) { - $numeric = false; - break; - } + if (!is_array($list)) { + return $list; + } + + $keys = array_keys($list); + $count = count($keys); + $numeric = true; + + if (!$assoc) { + for ($i = 0; $i < $count; $i++) { + if (!is_int($keys[$i])) { + $numeric = false; + break; } } + } - if (!$numeric || $assoc) { - $newList = array(); - for ($i = 0; $i < $count; $i++) { - if (is_int($keys[$i]) && is_scalar($list[$keys[$i]])) { - $newList[$list[$keys[$i]]] = null; - } else { - $newList[$keys[$i]] = $list[$keys[$i]]; - } + if (!$numeric || $assoc) { + $newList = array(); + for ($i = 0; $i < $count; $i++) { + if (is_int($keys[$i]) && is_scalar($list[$keys[$i]])) { + $newList[$list[$keys[$i]]] = null; + } else { + $newList[$keys[$i]] = $list[$keys[$i]]; } - $list = $newList; } + $list = $newList; } return $list; } diff --git a/libraries/lithium/util/String.php b/libraries/lithium/util/String.php index 8ae1da1..7d0b476 100644 --- a/libraries/lithium/util/String.php +++ b/libraries/lithium/util/String.php @@ -2,15 +2,14 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) - * Copyright 2009, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/mit-license.php The MIT License */ namespace lithium\util; -use \Closure; -use \Exception; +use Closure; +use Exception; /** * String manipulation utility class. Includes functionality for hashing, UUID generation, @@ -18,42 +17,105 @@ use \Exception; * */ class String { + /** + * UUID related constants + */ + const clearVer = 15; // 00001111 Clears all bits of version byte + const version4 = 64; // 01000000 Sets the version bit + const clearVar = 63; // 00111111 Clears relevant bits of variant byte + const varRFC = 128; // 10000000 The RFC 4122 variant /** - * Generates a random UUID. + * A file pointer towards urandom if available, else false * - * @param mixed $context Used to determine the values for `'SERVER_ADDR'`, `'HOST'` - * and `'HOSTNAME'`. Either a closure which is passed the requested context values, an - * object with properties for each value or an array keyed by requested context value. - * @return string An RFC 4122-compliant UUID. - * @link http://www.ietf.org/rfc/rfc4122.txt + * @var resource|false */ - public static function uuid($context) { - $val = function($value) use ($context) { - switch (true) { - case is_object($context) && is_callable($context): - $result = $context($value); - break; - case is_object($context): - $result = isset($context->$value) ? $context->$value : null; - break; - case is_array($context): - $result = isset($context[$value]) ? $context[$value] : null; - break; + protected static $_urandom; + + /** + * Seeds the random generator if it has yet to be done. + * + * @return boolean Success. + */ + public static function seed() { + // Seeding more than once means less entropy, not more, so bail + if (isset(static::$_urandom)) { + return false; + } + + // Use urandom if the device is available + if (is_readable('/dev/urandom')) { + static::$_urandom = fopen('/dev/urandom', 'rb'); + // Else seed PHP's mt_rand() + } else { + $seed = function() { + list($usec, $sec) = explode(' ', microtime()); + $seed = (float) $sec + ((float) $usec * 100000); + if (function_exists('getmypid')) { + $seed .= getmypid(); + } + return $seed; + }; + mt_srand($seed()); + static::$_urandom = false; + } + + return true; + } + + /** + * Generates random bytes for use in UUIDs and password salts. + * + * The method seeds the random source automatically. It uses + * /dev/urandom if the latter is available; `md_rand()` if not. + * + * It can also be used to generate arbitrary bits: + * + * {{{ + * $bits = bin2hex(String::random(8)); // 64 bits + * }}} + * + * @param integer $bytes The number of bytes to generate + * @param string Random bytes + */ + public static function random($bytes) { + if (!isset(static::$_urandom)) { + static::seed(); + } + + if (static::$_urandom) { + $rand = fread(static::$_urandom, $bytes); + } else { + $rand = ''; + for ($i = 0; $i < $bytes; $i++) { + $rand .= chr(mt_rand(0, 255)); } - return $result; - }; - - $node = static::_hostname($val); - $pid = function_exists('zend_thread_id') ? zend_thread_id() : getmypid(); - $pid = (!$pid || $pid > 65535) ? mt_rand(0, 0xfff) | 0x4000 : $pid; - list($timeMid, $timeLow) = explode(' ', microtime()); - - return sprintf( - "%08x-%04x-%04x-%02x%02x-%04x%08x", - (integer) $timeLow, (integer) substr($timeMid, 2) & 0xffff, mt_rand(0, 0xfff) | 0x4000, - mt_rand(0, 0x3f) | 0x80, mt_rand(0, 0xff), $pid, $node - ); + } + + return $rand; + } + + /** + * Generates an RFC 4122-compliant version 4 UUID. + * + * @return string The string representation of an RFC 4122-compliant, version 4 UUID. + * @link http://www.ietf.org/rfc/rfc4122.txt + */ + public static function uuid() { + $uuid = static::random(16); + + // Set version + $uuid[6] = chr(ord($uuid[6]) & static::clearVer | static::version4); + + // Set variant + $uuid[8] = chr(ord($uuid[8]) & static::clearVar | static::varRFC); + + // Return the uuid's string representation + return bin2hex(substr($uuid, 0, 4)) . '-' + . bin2hex(substr($uuid, 4, 2)) . '-' + . bin2hex(substr($uuid, 6, 2)) . '-' + . bin2hex(substr($uuid, 8, 2)) . '-' + . bin2hex(substr($uuid, 10, 6)); } /** @@ -95,6 +157,269 @@ class String { } /** + * Hashes a password using PHP's `crypt()` and an optional salt. If no + * salt is supplied, a cryptographically strong salt will be generated + * using `String::genSalt()`. + * + * Using this function is the proper way to hash a password. Using naive + * methods such as `String::hash()` is fine to check a file's integrity, + * but fundamentally insecure for passwords, due to the invariable lack + * of a cryptographically strong salt. + * + * Moreover, `String::hashPassword()`'s cryptographically strong salts + * ensure that: + * + * - Two identical passwords will not be hashed the same way. + * - `String::genSalt()`'s count interator can later be increased (assuming + * BF or XDES is available) within Lithium or your application, without + * invalidating existing password hashes. + * + * Usage: + * + * {{{ + * // Hash a password before storing it: + * $hashed = String::hashPassword($password); + * + * // Check a password by comparing it to its hashed value: + * $check = String::checkPassword($password, $hashed); + * + * // Use a stronger custom salt: + * $salt = String::genSalt('bf', 16); // 2^16 iterations + * $hashed = String::hashPassword($password, $salt); // Very slow + * $check = String::checkPassword($password, $hashed); // Very slow + * + * // Forward/backward compatibility + * $salt1 = String::genSalt('bf', 6); + * $salt2 = String::genSalt('bf', 12); + * $hashed1 = String::hashPassword($password, $salt1); // Fast + * $hashed2 = String::hashPassword($password, $salt2); // Slow + * $check1 = String::checkPassword($password, $hashed1); // True + * $check2 = String::checkPassword($password, $hashed2); // True + * }}} + * + * @see lithium\util\String::genSalt() + * @param string $password The password to hash. + * @param string $salt Optional. The salt string. + * @return string The hashed password. + * The result's length will be: + * - 60 chars for Blowfish hashes + * - 20 chars for XDES hashes + * - 34 chars for MD5 hashes + **/ + public static function hashPassword($password, $salt = null) { + return crypt($password, $salt ?: static::genSalt()); + } + + /** + * Compares a password and its hashed value using PHP's `crypt()`. + * + * @see lithium\util\String::hashPassword() + * @see lithium\util\String::genSalt() + * @param string $password The password to check + * @param string $hash The hashed password to compare + * @return boolean Whether the password is correct or not + */ + public static function checkPassword($password, $hash) { + return $hash == crypt($password, $hash); + } + + /** + * Generates a cryptographically strong salt, using the best available + * method (tries Blowfish, then XDES, and fallbacks to MD5), for use in + * `String::hashPassword()`. + * + * Blowfish and XDES are adaptive hashing algorithms. MD5 is not. Adaptive + * hashing algorithms are designed in such a way that when computers get + * faster, you can tune the algorithm to be slower by increasing the number + * of hash iterations, without introducing incompatibility with existing + * passwords. + * + * To pick an appropriate iteration count for adaptive algorithms, consider + * that the original DES crypt was designed to have the speed of 4 hashes + * per second on the hardware of that time. Slower than 4 hashes per second + * would probably dampen usability. Faster than 100 hashes per second is + * probably too fast. The defaults generate about 10 hashes per second + * using a dual-core 2.2GHz CPU. + * + * Note1: this salt generator is different from naive salt implementations + * (e.g. `md5(microtime())`) that are invariably found in OSS PHP applications, + * in that it uses all of the available bits of entropy for the supplied salt + * method. + * + * Note2: this method should not be used as custom salts, for instance in a + * custom password hasher. Indeed, salts are prefixed with information expected + * by PHP's `crypt()`. To get an arbitrarily long, cryptographically strong salt + * consisting in random sequences of alpha numeric characters, combine + * `String::random()` and `String::encode64()` instead. + * + * @link http://php.net/manual/en/function.crypt.php + * @link http://www.postgresql.org/docs/9.0/static/pgcrypto.html + * @see lithium\util\String::hashPassword() + * @param string $type The hash type. Optional. Defaults to '`bf`'. + * Supported values include: + * - `'bf'`: Blowfish (128 salt bits, adaptive, max 72 chars) + * - `'xdes'`: XDES (24 salt bits, adaptive, max 8 chars) + * - `'md5'`: MD5 (48 salt bits, non-adaptive, unlimited length) + * @param integer $count Optional. The base-2 logarithm of the iteration + * count, for adaptive algorithms. Defaults to: + * - `10` for Blowfish + * - `18` for XDES + * @return string The salt string. + */ + public static function genSalt($type = null, $count = null) { + switch (true) { + case CRYPT_BLOWFISH == 1 && (!$type || $type === 'bf'): + return static::_genSaltBF($count); + case CRYPT_EXT_DES == 1 && (!$type || $type === 'xdes'): + return static::_genSaltXDES($count); + default: + return static::_genSaltMD5(); + } + } + + /** + * Encodes bytes into an `./0-9A-Za-z` alphabet, for use as salt when + * hashing passwords. + * + * Note: this is not the same as RFC 1421, or `base64_encode()`, which + * uses an `+/0-9A-Za-z` alphabet. + * + * This function can be combined with `String::random()` to generate random + * sequences of `./0-9A-Za-z` characters: + * + * {{{ + * $salt = String::encode64(String::random(8)); // 64 bits + * }}} + * + * @see lithium\util\String::random() + * @param string $input The input bytes. + * @return string The same bytes in the `/.0-9A-Za-z` alphabet. + */ + public static function encode64($input) { + $base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + $i = 0; + + $count = strlen($input); + $output = ''; + + do { + $value = ord($input[$i++]); + $output .= $base64[$value & 0x3f]; + + if ($i < $count) { + $value |= ord($input[$i]) << 8; + } + $output .= $base64[($value >> 6) & 0x3f]; + + if ($i++ >= $count) { + break; + } + if ($i < $count) { + $value |= ord($input[$i]) << 16; + } + $output .= $base64[($value >> 12) & 0x3f]; + + if ($i++ >= $count) { + break; + } + $output .= $base64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return $output; + } + + /** + * Generates a Blowfish salt for use in `String::hashPassword()`. + * + * @param integer $count The base-2 logarithm of the iteration count. + * Defaults to `10`. Can be `4` to `31`. + * @return string $salt + */ + protected static function _genSaltBf($count = 10) { + $count = (integer) $count; + if ($count < 4 || $count > 31) { + $count = 10; + } + + // We don't use the encode64() method here because it could result + // in 2 bits less of entropy depending on the last char. + $base64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $i = 0; + + $input = static::random(16); // 128 bits of salt + $output = ''; + + do { + $c1 = ord($input[$i++]); + $output .= $base64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $base64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $base64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $base64[$c1]; + $output .= $base64[$c2 & 0x3f]; + } while (1); + + return '$2a$' + // zeroize $count + . chr(ord('0') + $count / 10) . chr(ord('0') + $count % 10) + . '$' . $output; + } + + /** + * Generates an Extended DES salt for use in `String::hashPassword()`. + * + * @param integer $count The base-2 logarithm of the iteration count. + * Defaults to `18`. Can be `1` to `24`. 1 will be stripped + * from the non-log value, e.g. 2^18 - 1, to ensure we don't + * use a weak DES key. + * @return string The XDES salt. + */ + protected static function _genSaltXDES($count = 18) { + $count = (integer) $count; + if ($count < 1 || $count > 24) { + $count = 16; + } + + // Count should be odd to not reveal weak DES keys + $count = (1 << $count) - 1; + + $base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + $output = '_' + // iterations + . $base64[$count & 0x3f] + . $base64[($count >> 6) & 0x3f] + . $base64[($count >> 12) & 0x3f] + . $base64[($count >> 18) & 0x3f] + // 24 bits of salt + . static::encode64(static::random(3)); + + return $output; + } + + /** + * Generates an MD5 salt for use in `String::hashPassword()`. + * + * @return string The MD5 salt. + */ + protected static function _genSaltMD5() { + $output = '$1$' + // 48 bits of salt + . static::encode64(static::random(6)); + return $output; + } + + /** * Replaces variable placeholders inside a string with any given data. Each key * in the `$data` array corresponds to a variable placeholder name in `$str`. * @@ -343,46 +668,6 @@ class String { } return empty($results) ? array() : array_map('trim', $results); } - - /** - * Used by `String::uuid()` to get the hostname from request context data. Uses fallbacks to get - * the current host name or IP, depending on what values are available. - * - * @param mixed $context An array (i.e. `$_SERVER`), `Request` object, or anonymous function - * containing host data. - * @return string Returns the host name or IP for use in generating a UUID. - */ - protected static function _hostname($context) { - $node = $context('SERVER_ADDR'); - - if (strpos($node, ':') !== false) { - if (substr_count($node, '::')) { - $pad = str_repeat(':0000', 8 - substr_count($node, ':')); - $node = str_replace('::', $pad . ':', $node); - } - $node = explode(':', $node); - $ipv6 = ''; - - foreach ($node as $id) { - $ipv6 .= str_pad(base_convert($id, 16, 2), 16, 0, STR_PAD_LEFT); - } - $node = base_convert($ipv6, 2, 10); - $node = (strlen($node) < 38) ? null : crc32($node); - } elseif (empty($node)) { - $host = $context('HOSTNAME'); - $host = $host ?: $context('HOST'); - - if (!empty($host)) { - $ip = gethostbyname($host); - $node = ($ip === $host) ? crc32($host) : $node = ip2long($ip); - } - } elseif ($node !== '127.0.0.1') { - $node = ip2long($node); - } else { - $node = crc32(rand()); - } - return $node; - } } ?> \ No newline at end of file diff --git a/libraries/lithium/util/Validator.php b/libraries/lithium/util/Validator.php index 7c2e516..b31fce4 100644 --- a/libraries/lithium/util/Validator.php +++ b/libraries/lithium/util/Validator.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */ @@ -120,7 +120,7 @@ use InvalidArgumentException; * - `inList`: Checks that a value is in a pre-defined list of values. This validator accepts one * option, `'list'`, which is an array containing acceptable values. * - * - `regex`: Checks that a value appears to be a /-delimited regular expression, possibly + * - `regex`: Checks that a value appears to be a valid regular expression, possibly * containing PCRE-compatible options flags. * * - `uuid`: Checks that a value is a valid UUID. @@ -235,10 +235,11 @@ class Validator extends \lithium\core\StaticObject { 'notEmpty' => '/[^\s]+/m', 'phone' => '/^\+?[0-9\(\)\-]{10,20}$/', 'postalCode' => '/(^|\A\b)[A-Z0-9\s\-]{5,}($|\b\z)/i', - 'regex' => '/^\/(.+)\/[gimsxu]*$/', + 'regex' => '/^(?:([^[:alpha:]\\\\{<\[\(])(.+)(?:\1))|(?:{(.+)})|(?:<(.+)>)|' . + '(?:\[(.+)\])|(?:\((.+)\))[gimsxu]*$/', 'time' => '%^((0?[1-9]|1[012])(:[0-5]\d){0,2}([AP]M|[ap]m))$|^([01]\d|2[0-3])' . '(:[0-5]\d){0,2}$%', - 'boolean' => function($value) { + 'boolean' => function($value) { $bool = is_bool($value); $filter = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); return ($bool || $filter !== null); @@ -298,7 +299,7 @@ class Validator extends \lithium\core\StaticObject { } return is_finite($value); }, - 'uuid' => "/{$alnum}{8}-{$alnum}{4}-{$alnum}{4}-{$alnum}{4}-{$alnum}{12}/", + 'uuid' => "/^{$alnum}{8}-{$alnum}{4}-{$alnum}{4}-{$alnum}{4}-{$alnum}{12}$/", 'email' => function($value) { return filter_var($value, FILTER_VALIDATE_EMAIL); }, @@ -433,6 +434,7 @@ class Validator extends \lithium\core\StaticObject { ); $errors = array(); $events = (array) (isset($options['events']) ? $options['events'] : null); + $values = Set::flatten($values); foreach ($rules as $field => $rules) { $rules = is_string($rules) ? array('message' => $rules) : $rules; @@ -543,10 +545,11 @@ class Validator extends \lithium\core\StaticObject { * @param string $options * @return boolean Returns `true` or `false` indicating whether the validation rule check * succeeded or failed. + * @filter */ public static function rule($rule, $value, $format = 'any', array $options = array()) { if (!isset(static::$_rules[$rule])) { - throw new InvalidArgumentException("Rule '{$rule}' is not a validation rule"); + throw new InvalidArgumentException("Rule `{$rule}` is not a validation rule."); } $defaults = isset(static::$_options[$rule]) ? static::$_options[$rule] : array(); $options = (array) $options + $defaults + static::$_options['defaults']; diff --git a/libraries/lithium/util/collection/Filters.php b/libraries/lithium/util/collection/Filters.php index 59ff497..acb596c 100644 --- a/libraries/lithium/util/collection/Filters.php +++ b/libraries/lithium/util/collection/Filters.php @@ -2,7 +2,7 @@ /** * Lithium: the most rad php framework * - * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org) + * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org) * @license http://opensource.org/licenses/bsd-license.php The BSD License */