Added Kohana v3.0.8
This commit is contained in:
parent
27aee719b0
commit
64bdbdc981
21
.htaccess
Normal file
21
.htaccess
Normal file
@ -0,0 +1,21 @@
|
||||
# Turn on URL rewriting
|
||||
RewriteEngine On
|
||||
|
||||
# Installation directory
|
||||
RewriteBase /kohana/
|
||||
|
||||
# Protect hidden files from being viewed
|
||||
<Files .*>
|
||||
Order Deny,Allow
|
||||
Deny From All
|
||||
</Files>
|
||||
|
||||
# Protect application and system files from being viewed
|
||||
RewriteRule ^(?:application|modules|system)\b.* index.php/$0 [L]
|
||||
|
||||
# Allow any files or directories that exist to be displayed directly
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
# Rewrite all other URLs to index.php/URL
|
||||
RewriteRule .* index.php/$0 [PT]
|
102
application/bootstrap.php
Normal file
102
application/bootstrap.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
//-- Environment setup --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the default time zone.
|
||||
*
|
||||
* @see http://kohanaframework.org/guide/using.configuration
|
||||
* @see http://php.net/timezones
|
||||
*/
|
||||
date_default_timezone_set('America/Chicago');
|
||||
|
||||
/**
|
||||
* Set the default locale.
|
||||
*
|
||||
* @see http://kohanaframework.org/guide/using.configuration
|
||||
* @see http://php.net/setlocale
|
||||
*/
|
||||
setlocale(LC_ALL, 'en_US.utf-8');
|
||||
|
||||
/**
|
||||
* Enable the Kohana auto-loader.
|
||||
*
|
||||
* @see http://kohanaframework.org/guide/using.autoloading
|
||||
* @see http://php.net/spl_autoload_register
|
||||
*/
|
||||
spl_autoload_register(array('Kohana', 'auto_load'));
|
||||
|
||||
/**
|
||||
* Enable the Kohana auto-loader for unserialization.
|
||||
*
|
||||
* @see http://php.net/spl_autoload_call
|
||||
* @see http://php.net/manual/var.configuration.php#unserialize-callback-func
|
||||
*/
|
||||
ini_set('unserialize_callback_func', 'spl_autoload_call');
|
||||
|
||||
//-- Configuration and initialization -----------------------------------------
|
||||
|
||||
/**
|
||||
* Initialize Kohana, setting the default options.
|
||||
*
|
||||
* The following options are available:
|
||||
*
|
||||
* - string base_url path, and optionally domain, of your application NULL
|
||||
* - string index_file name of your index file, usually "index.php" index.php
|
||||
* - string charset internal character set used for input and output utf-8
|
||||
* - string cache_dir set the internal cache directory APPPATH/cache
|
||||
* - boolean errors enable or disable error handling TRUE
|
||||
* - boolean profile enable or disable internal profiling TRUE
|
||||
* - boolean caching enable or disable internal caching FALSE
|
||||
*/
|
||||
Kohana::init(array(
|
||||
'base_url' => '/',
|
||||
));
|
||||
|
||||
/**
|
||||
* Attach the file write to logging. Multiple writers are supported.
|
||||
*/
|
||||
Kohana::$log->attach(new Kohana_Log_File(APPPATH.'logs'));
|
||||
|
||||
/**
|
||||
* Attach a file reader to config. Multiple readers are supported.
|
||||
*/
|
||||
Kohana::$config->attach(new Kohana_Config_File);
|
||||
|
||||
/**
|
||||
* Enable modules. Modules are referenced by a relative or absolute path.
|
||||
*/
|
||||
Kohana::modules(array(
|
||||
// 'auth' => MODPATH.'auth', // Basic authentication
|
||||
// 'cache' => MODPATH.'cache', // Caching with multiple backends
|
||||
// 'codebench' => MODPATH.'codebench', // Benchmarking tool
|
||||
// 'database' => MODPATH.'database', // Database access
|
||||
// 'image' => MODPATH.'image', // Image manipulation
|
||||
// 'orm' => MODPATH.'orm', // Object Relationship Mapping
|
||||
// 'oauth' => MODPATH.'oauth', // OAuth authentication
|
||||
// 'pagination' => MODPATH.'pagination', // Paging of results
|
||||
// 'unittest' => MODPATH.'unittest', // Unit testing
|
||||
// 'userguide' => MODPATH.'userguide', // User guide and API documentation
|
||||
));
|
||||
|
||||
/**
|
||||
* Set the routes. Each route must have a minimum of a name, a URI and a set of
|
||||
* defaults for the URI.
|
||||
*/
|
||||
Route::set('default', '(<controller>(/<action>(/<id>)))')
|
||||
->defaults(array(
|
||||
'controller' => 'welcome',
|
||||
'action' => 'index',
|
||||
));
|
||||
|
||||
if ( ! defined('SUPPRESS_REQUEST'))
|
||||
{
|
||||
/**
|
||||
* Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO'].
|
||||
* If no source is specified, the URI will be automatically detected.
|
||||
*/
|
||||
echo Request::instance()
|
||||
->execute()
|
||||
->send_headers()
|
||||
->response;
|
||||
}
|
10
application/classes/controller/welcome.php
Normal file
10
application/classes/controller/welcome.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Controller_Welcome extends Controller {
|
||||
|
||||
public function action_index()
|
||||
{
|
||||
$this->request->response = 'hello, world!';
|
||||
}
|
||||
|
||||
} // End Welcome
|
14
includes/kohana/LICENSE.md
Normal file
14
includes/kohana/LICENSE.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Kohana License Agreement
|
||||
|
||||
This license is a legal agreement between you and the Kohana Team for the use of Kohana Framework (the "Software"). By obtaining the Software you agree to comply with the terms and conditions of this license.
|
||||
|
||||
Copyright (c) 2007-2010 Kohana Team
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Kohana nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
3
includes/kohana/README.md
Normal file
3
includes/kohana/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Kohana PHP Framework, version 3.0 (dev)
|
||||
|
||||
This is the current development version of [Kohana](http://kohanaframework.org/).
|
209
includes/kohana/install.php
Normal file
209
includes/kohana/install.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
// Sanity check, install should only be checked from index.php
|
||||
defined('SYSPATH') or exit('Install tests must be loaded from within index.php!');
|
||||
|
||||
// Clear out the cache to prevent errors. This typically happens on Windows/FastCGI.
|
||||
clearstatcache(TRUE);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Kohana Installation</title>
|
||||
|
||||
<style type="text/css">
|
||||
body { width: 42em; margin: 0 auto; font-family: sans-serif; background: #fff; font-size: 1em; }
|
||||
h1 { letter-spacing: -0.04em; }
|
||||
h1 + p { margin: 0 0 2em; color: #333; font-size: 90%; font-style: italic; }
|
||||
code { font-family: monaco, monospace; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
table th,
|
||||
table td { padding: 0.4em; text-align: left; vertical-align: top; }
|
||||
table th { width: 12em; font-weight: normal; }
|
||||
table tr:nth-child(odd) { background: #eee; }
|
||||
table td.pass { color: #191; }
|
||||
table td.fail { color: #911; }
|
||||
#results { padding: 0.8em; color: #fff; font-size: 1.5em; }
|
||||
#results.pass { background: #191; }
|
||||
#results.fail { background: #911; }
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Environment Tests</h1>
|
||||
|
||||
<p>
|
||||
The following tests have been run to determine if <a href="http://kohanaframework.org/">Kohana</a> will work in your environment.
|
||||
If any of the tests have failed, consult the <a href="http://kohanaframework.org/guide/about.install">documentation</a>
|
||||
for more information on how to correct the problem.
|
||||
</p>
|
||||
|
||||
<?php $failed = FALSE ?>
|
||||
|
||||
<table cellspacing="0">
|
||||
<tr>
|
||||
<th>PHP Version</th>
|
||||
<?php if (version_compare(PHP_VERSION, '5.2.3', '>=')): ?>
|
||||
<td class="pass"><?php echo PHP_VERSION ?></td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">Kohana requires PHP 5.2.3 or newer, this version is <?php echo PHP_VERSION ?>.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>System Directory</th>
|
||||
<?php if (is_dir(SYSPATH) AND is_file(SYSPATH.'classes/kohana'.EXT)): ?>
|
||||
<td class="pass"><?php echo SYSPATH ?></td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">The configured <code>system</code> directory does not exist or does not contain required files.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Application Directory</th>
|
||||
<?php if (is_dir(APPPATH) AND is_file(APPPATH.'bootstrap'.EXT)): ?>
|
||||
<td class="pass"><?php echo APPPATH ?></td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">The configured <code>application</code> directory does not exist or does not contain required files.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cache Directory</th>
|
||||
<?php if (is_dir(APPPATH) AND is_dir(APPPATH.'cache') AND is_writable(APPPATH.'cache')): ?>
|
||||
<td class="pass"><?php echo APPPATH.'cache/' ?></td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">The <code><?php echo APPPATH.'cache/' ?></code> directory is not writable.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Logs Directory</th>
|
||||
<?php if (is_dir(APPPATH) AND is_dir(APPPATH.'logs') AND is_writable(APPPATH.'logs')): ?>
|
||||
<td class="pass"><?php echo APPPATH.'logs/' ?></td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">The <code><?php echo APPPATH.'logs/' ?></code> directory is not writable.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>PCRE UTF-8</th>
|
||||
<?php if ( ! @preg_match('/^.$/u', 'ñ')): $failed = TRUE ?>
|
||||
<td class="fail"><a href="http://php.net/pcre">PCRE</a> has not been compiled with UTF-8 support.</td>
|
||||
<?php elseif ( ! @preg_match('/^\pL$/u', 'ñ')): $failed = TRUE ?>
|
||||
<td class="fail"><a href="http://php.net/pcre">PCRE</a> has not been compiled with Unicode property support.</td>
|
||||
<?php else: ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>SPL Enabled</th>
|
||||
<?php if (function_exists('spl_autoload_register')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">PHP <a href="http://www.php.net/spl">SPL</a> is either not loaded or not compiled in.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Reflection Enabled</th>
|
||||
<?php if (class_exists('ReflectionClass')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">PHP <a href="http://www.php.net/reflection">reflection</a> is either not loaded or not compiled in.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Filters Enabled</th>
|
||||
<?php if (function_exists('filter_list')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">The <a href="http://www.php.net/filter">filter</a> extension is either not loaded or not compiled in.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Iconv Extension Loaded</th>
|
||||
<?php if (extension_loaded('iconv')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">The <a href="http://php.net/iconv">iconv</a> extension is not loaded.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php if (extension_loaded('mbstring')): ?>
|
||||
<tr>
|
||||
<th>Mbstring Not Overloaded</th>
|
||||
<?php if (ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING): $failed = TRUE ?>
|
||||
<td class="fail">The <a href="http://php.net/mbstring">mbstring</a> extension is overloading PHP's native string functions.</td>
|
||||
<?php else: ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
<tr>
|
||||
<th>Character Type (CTYPE) Extension</th>
|
||||
<?php if ( ! function_exists('ctype_digit')): $failed = TRUE ?>
|
||||
<td class="fail">The <a href="http://php.net/ctype">ctype</a> extension is not enabled.</td>
|
||||
<?php else: ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>URI Determination</th>
|
||||
<?php if (isset($_SERVER['REQUEST_URI']) OR isset($_SERVER['PHP_SELF']) OR isset($_SERVER['PATH_INFO'])): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: $failed = TRUE ?>
|
||||
<td class="fail">Neither <code>$_SERVER['REQUEST_URI']</code>, <code>$_SERVER['PHP_SELF']</code>, or <code>$_SERVER['PATH_INFO']</code> is available.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<?php if ($failed === TRUE): ?>
|
||||
<p id="results" class="fail">✘ Kohana may not work correctly with your environment.</p>
|
||||
<?php else: ?>
|
||||
<p id="results" class="pass">✔ Your environment passed all requirements.<br />
|
||||
Remove or rename the <code>install<?php echo EXT ?></code> file now.</p>
|
||||
<?php endif ?>
|
||||
|
||||
<h1>Optional Tests</h1>
|
||||
|
||||
<p>
|
||||
The following extensions are not required to run the Kohana core, but if enabled can provide access to additional classes.
|
||||
</p>
|
||||
|
||||
<table cellspacing="0">
|
||||
<tr>
|
||||
<th>cURL Enabled</th>
|
||||
<?php if (extension_loaded('curl')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: ?>
|
||||
<td class="fail">Kohana requires <a href="http://php.net/curl">cURL</a> for the Remote class.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>mcrypt Enabled</th>
|
||||
<?php if (extension_loaded('mcrypt')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: ?>
|
||||
<td class="fail">Kohana requires <a href="http://php.net/mcrypt">mcrypt</a> for the Encrypt class.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GD Enabled</th>
|
||||
<?php if (function_exists('gd_info')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: ?>
|
||||
<td class="fail">Kohana requires <a href="http://php.net/gd">GD</a> v2 for the Image class.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>PDO Enabled</th>
|
||||
<?php if (class_exists('PDO')): ?>
|
||||
<td class="pass">Pass</td>
|
||||
<?php else: ?>
|
||||
<td class="fail">Kohana can use <a href="http://php.net/pdo">PDO</a> to support additional databases.</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
3
includes/kohana/modules/auth/classes/auth.php
Normal file
3
includes/kohana/modules/auth/classes/auth.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
abstract class Auth extends Kohana_Auth { }
|
3
includes/kohana/modules/auth/classes/auth/file.php
Normal file
3
includes/kohana/modules/auth/classes/auth/file.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
class Auth_File extends Kohana_Auth_File { }
|
3
includes/kohana/modules/auth/classes/auth/orm.php
Normal file
3
includes/kohana/modules/auth/classes/auth/orm.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
class Auth_ORM extends Kohana_Auth_ORM { }
|
241
includes/kohana/modules/auth/classes/kohana/auth.php
Normal file
241
includes/kohana/modules/auth/classes/kohana/auth.php
Normal file
@ -0,0 +1,241 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* User authorization library. Handles user login and logout, as well as secure
|
||||
* password hashing.
|
||||
*
|
||||
* @package Kohana/Auth
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2007-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
abstract class Kohana_Auth {
|
||||
|
||||
// Auth instances
|
||||
protected static $_instance;
|
||||
|
||||
/**
|
||||
* Singleton pattern
|
||||
*
|
||||
* @return Auth
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
if ( ! isset(Auth::$_instance))
|
||||
{
|
||||
// Load the configuration for this type
|
||||
$config = Kohana::config('auth');
|
||||
|
||||
if ( ! $type = $config->get('driver'))
|
||||
{
|
||||
$type = 'ORM';
|
||||
}
|
||||
|
||||
// Set the session class name
|
||||
$class = 'Auth_'.ucfirst($type);
|
||||
|
||||
// Create a new session instance
|
||||
Auth::$_instance = new $class($config);
|
||||
}
|
||||
|
||||
return Auth::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of Auth.
|
||||
*
|
||||
* @return Auth
|
||||
*/
|
||||
public static function factory($config = array())
|
||||
{
|
||||
return new Auth($config);
|
||||
}
|
||||
|
||||
protected $_session;
|
||||
|
||||
protected $_config;
|
||||
|
||||
/**
|
||||
* Loads Session and configuration options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($config = array())
|
||||
{
|
||||
// Clean up the salt pattern and split it into an array
|
||||
$config['salt_pattern'] = preg_split('/,\s*/', Kohana::config('auth')->get('salt_pattern'));
|
||||
|
||||
// Save the config in the object
|
||||
$this->_config = $config;
|
||||
|
||||
$this->_session = Session::instance();
|
||||
}
|
||||
|
||||
abstract protected function _login($username, $password, $remember);
|
||||
|
||||
abstract public function password($username);
|
||||
|
||||
abstract public function check_password($password);
|
||||
|
||||
/**
|
||||
* Gets the currently logged in user from the session.
|
||||
* Returns FALSE if no user is currently logged in.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_user()
|
||||
{
|
||||
return $this->_session->get($this->_config['session_key'], FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to log in a user by using an ORM object and plain-text password.
|
||||
*
|
||||
* @param string username to log in
|
||||
* @param string password to check against
|
||||
* @param boolean enable autologin
|
||||
* @return boolean
|
||||
*/
|
||||
public function login($username, $password, $remember = FALSE)
|
||||
{
|
||||
if (empty($password))
|
||||
return FALSE;
|
||||
|
||||
if (is_string($password))
|
||||
{
|
||||
// Get the salt from the stored password
|
||||
$salt = $this->find_salt($this->password($username));
|
||||
|
||||
// Create a hashed password using the salt from the stored password
|
||||
$password = $this->hash_password($password, $salt);
|
||||
}
|
||||
|
||||
return $this->_login($username, $password, $remember);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out a user by removing the related session variables.
|
||||
*
|
||||
* @param boolean completely destroy the session
|
||||
* @param boolean remove all tokens for user
|
||||
* @return boolean
|
||||
*/
|
||||
public function logout($destroy = FALSE, $logout_all = FALSE)
|
||||
{
|
||||
if ($destroy === TRUE)
|
||||
{
|
||||
// Destroy the session completely
|
||||
$this->_session->destroy();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the user from the session
|
||||
$this->_session->delete($this->_config['session_key']);
|
||||
|
||||
// Regenerate session_id
|
||||
$this->_session->regenerate();
|
||||
}
|
||||
|
||||
// Double check
|
||||
return ! $this->logged_in();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is an active session. Optionally allows checking for a
|
||||
* specific role.
|
||||
*
|
||||
* @param string role name
|
||||
* @return mixed
|
||||
*/
|
||||
public function logged_in($role = NULL)
|
||||
{
|
||||
return FALSE !== $this->get_user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hashed password from a plaintext password, inserting salt
|
||||
* based on the configured salt pattern.
|
||||
*
|
||||
* @param string plaintext password
|
||||
* @return string hashed password string
|
||||
*/
|
||||
public function hash_password($password, $salt = FALSE)
|
||||
{
|
||||
if ($salt === FALSE)
|
||||
{
|
||||
// Create a salt seed, same length as the number of offsets in the pattern
|
||||
$salt = substr($this->hash(uniqid(NULL, TRUE)), 0, count($this->_config['salt_pattern']));
|
||||
}
|
||||
|
||||
// Password hash that the salt will be inserted into
|
||||
$hash = $this->hash($salt.$password);
|
||||
|
||||
// Change salt to an array
|
||||
$salt = str_split($salt, 1);
|
||||
|
||||
// Returned password
|
||||
$password = '';
|
||||
|
||||
// Used to calculate the length of splits
|
||||
$last_offset = 0;
|
||||
|
||||
foreach ($this->_config['salt_pattern'] as $offset)
|
||||
{
|
||||
// Split a new part of the hash off
|
||||
$part = substr($hash, 0, $offset - $last_offset);
|
||||
|
||||
// Cut the current part out of the hash
|
||||
$hash = substr($hash, $offset - $last_offset);
|
||||
|
||||
// Add the part to the password, appending the salt character
|
||||
$password .= $part.array_shift($salt);
|
||||
|
||||
// Set the last offset to the current offset
|
||||
$last_offset = $offset;
|
||||
}
|
||||
|
||||
// Return the password, with the remaining hash appended
|
||||
return $password.$hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a hash, using the configured method.
|
||||
*
|
||||
* @param string string to hash
|
||||
* @return string
|
||||
*/
|
||||
public function hash($str)
|
||||
{
|
||||
return hash($this->_config['hash_method'], $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the salt from a password, based on the configured salt pattern.
|
||||
*
|
||||
* @param string hashed password
|
||||
* @return string
|
||||
*/
|
||||
public function find_salt($password)
|
||||
{
|
||||
$salt = '';
|
||||
|
||||
foreach ($this->_config['salt_pattern'] as $i => $offset)
|
||||
{
|
||||
// Find salt characters, take a good long look...
|
||||
$salt .= substr($password, $offset + $i, 1);
|
||||
}
|
||||
|
||||
return $salt;
|
||||
}
|
||||
|
||||
protected function complete_login($user)
|
||||
{
|
||||
// Regenerate session_id
|
||||
$this->_session->regenerate();
|
||||
|
||||
// Store username in session
|
||||
$this->_session->set($this->_config['session_key'], $user);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
} // End Auth
|
88
includes/kohana/modules/auth/classes/kohana/auth/file.php
Normal file
88
includes/kohana/modules/auth/classes/kohana/auth/file.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* File Auth driver.
|
||||
* [!!] this Auth driver does not support roles nor autologin.
|
||||
*
|
||||
* @package Kohana/Auth
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2007-2008 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
class Kohana_Auth_File extends Auth {
|
||||
|
||||
// User list
|
||||
protected $_users;
|
||||
|
||||
/**
|
||||
* Constructor loads the user list into the class.
|
||||
*/
|
||||
public function __construct($config = array())
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
// Load user list
|
||||
$this->_users = Arr::get($config, 'users', array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a user in.
|
||||
*
|
||||
* @param string username
|
||||
* @param string password
|
||||
* @param boolean enable autologin (not supported)
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _login($username, $password, $remember)
|
||||
{
|
||||
if (isset($this->_users[$username]) AND $this->_users[$username] === $password)
|
||||
{
|
||||
// Complete the login
|
||||
return $this->complete_login($username);
|
||||
}
|
||||
|
||||
// Login failed
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a user to be logged in, without specifying a password.
|
||||
*
|
||||
* @param mixed username
|
||||
* @return boolean
|
||||
*/
|
||||
public function force_login($username)
|
||||
{
|
||||
// Complete the login
|
||||
return $this->complete_login($username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored password for a username.
|
||||
*
|
||||
* @param mixed username
|
||||
* @return string
|
||||
*/
|
||||
public function password($username)
|
||||
{
|
||||
return Arr::get($this->_users, $username, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare password with original (plain text). Works for current (logged in) user
|
||||
*
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function check_password($password)
|
||||
{
|
||||
$username = $this->get_user();
|
||||
|
||||
if ($username === FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return ($password === $this->password($username));
|
||||
}
|
||||
|
||||
} // End Auth File
|
288
includes/kohana/modules/auth/classes/kohana/auth/orm.php
Normal file
288
includes/kohana/modules/auth/classes/kohana/auth/orm.php
Normal file
@ -0,0 +1,288 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* ORM Auth driver.
|
||||
*
|
||||
* @package Kohana/Auth
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2007-2008 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
class Kohana_Auth_ORM extends Auth {
|
||||
|
||||
/**
|
||||
* Checks if a session is active.
|
||||
*
|
||||
* @param mixed role name string, role ORM object, or array with role names
|
||||
* @return boolean
|
||||
*/
|
||||
public function logged_in($role = NULL)
|
||||
{
|
||||
$status = FALSE;
|
||||
|
||||
// Get the user from the session
|
||||
$user = $this->get_user();
|
||||
|
||||
if (is_object($user) AND $user instanceof Model_User AND $user->loaded())
|
||||
{
|
||||
// Everything is okay so far
|
||||
$status = TRUE;
|
||||
|
||||
if ( ! empty($role))
|
||||
{
|
||||
// Multiple roles to check
|
||||
if (is_array($role))
|
||||
{
|
||||
// Check each role
|
||||
foreach ($role as $_role)
|
||||
{
|
||||
if ( ! is_object($_role))
|
||||
{
|
||||
$_role = ORM::factory('role', array('name' => $_role));
|
||||
}
|
||||
|
||||
// If the user doesn't have the role
|
||||
if ( ! $user->has('roles', $_role))
|
||||
{
|
||||
// Set the status false and get outta here
|
||||
$status = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Single role to check
|
||||
else
|
||||
{
|
||||
if ( ! is_object($role))
|
||||
{
|
||||
// Load the role
|
||||
$role = ORM::factory('role', array('name' => $role));
|
||||
}
|
||||
|
||||
// Check that the user has the given role
|
||||
$status = $user->has('roles', $role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a user in.
|
||||
*
|
||||
* @param string username
|
||||
* @param string password
|
||||
* @param boolean enable autologin
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _login($user, $password, $remember)
|
||||
{
|
||||
if ( ! is_object($user))
|
||||
{
|
||||
$username = $user;
|
||||
|
||||
// Load the user
|
||||
$user = ORM::factory('user');
|
||||
$user->where($user->unique_key($username), '=', $username)->find();
|
||||
}
|
||||
|
||||
// If the passwords match, perform a login
|
||||
if ($user->has('roles', ORM::factory('role', array('name' => 'login'))) AND $user->password === $password)
|
||||
{
|
||||
if ($remember === TRUE)
|
||||
{
|
||||
// Create a new autologin token
|
||||
$token = ORM::factory('user_token');
|
||||
|
||||
// Set token data
|
||||
$token->user_id = $user->id;
|
||||
$token->expires = time() + $this->_config['lifetime'];
|
||||
$token->save();
|
||||
|
||||
// Set the autologin cookie
|
||||
Cookie::set('authautologin', $token->token, $this->_config['lifetime']);
|
||||
}
|
||||
|
||||
// Finish the login
|
||||
$this->complete_login($user);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Login failed
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a user to be logged in, without specifying a password.
|
||||
*
|
||||
* @param mixed username string, or user ORM object
|
||||
* @param boolean mark the session as forced
|
||||
* @return boolean
|
||||
*/
|
||||
public function force_login($user, $mark_session_as_forced = FALSE)
|
||||
{
|
||||
if ( ! is_object($user))
|
||||
{
|
||||
$username = $user;
|
||||
|
||||
// Load the user
|
||||
$user = ORM::factory('user');
|
||||
$user->where($user->unique_key($username), '=', $username)->find();
|
||||
}
|
||||
|
||||
if ($mark_session_as_forced === TRUE)
|
||||
{
|
||||
// Mark the session as forced, to prevent users from changing account information
|
||||
$this->_session->set('auth_forced', TRUE);
|
||||
}
|
||||
|
||||
// Run the standard completion
|
||||
$this->complete_login($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a user in, based on the authautologin cookie.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function auto_login()
|
||||
{
|
||||
if ($token = Cookie::get('authautologin'))
|
||||
{
|
||||
// Load the token and user
|
||||
$token = ORM::factory('user_token', array('token' => $token));
|
||||
|
||||
if ($token->loaded() AND $token->user->loaded())
|
||||
{
|
||||
if ($token->user_agent === sha1(Request::$user_agent))
|
||||
{
|
||||
// Save the token to create a new unique token
|
||||
$token->save();
|
||||
|
||||
// Set the new token
|
||||
Cookie::set('authautologin', $token->token, $token->expires - time());
|
||||
|
||||
// Complete the login with the found data
|
||||
$this->complete_login($token->user);
|
||||
|
||||
// Automatic login was successful
|
||||
return $token->user;
|
||||
}
|
||||
|
||||
// Token is invalid
|
||||
$token->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently logged in user from the session (with auto_login check).
|
||||
* Returns FALSE if no user is currently logged in.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_user()
|
||||
{
|
||||
$user = parent::get_user();
|
||||
|
||||
if ($user === FALSE)
|
||||
{
|
||||
// check for "remembered" login
|
||||
$user = $this->auto_login();
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a user out and remove any autologin cookies.
|
||||
*
|
||||
* @param boolean completely destroy the session
|
||||
* @param boolean remove all tokens for user
|
||||
* @return boolean
|
||||
*/
|
||||
public function logout($destroy = FALSE, $logout_all = FALSE)
|
||||
{
|
||||
// Set by force_login()
|
||||
$this->_session->delete('auth_forced');
|
||||
|
||||
if ($token = Cookie::get('authautologin'))
|
||||
{
|
||||
// Delete the autologin cookie to prevent re-login
|
||||
Cookie::delete('authautologin');
|
||||
|
||||
// Clear the autologin token from the database
|
||||
$token = ORM::factory('user_token', array('token' => $token));
|
||||
|
||||
if ($token->loaded() AND $logout_all)
|
||||
{
|
||||
ORM::factory('user_token')->where('user_id', '=', $token->user_id)->delete_all();
|
||||
}
|
||||
elseif ($token->loaded())
|
||||
{
|
||||
$token->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return parent::logout($destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored password for a username.
|
||||
*
|
||||
* @param mixed username string, or user ORM object
|
||||
* @return string
|
||||
*/
|
||||
public function password($user)
|
||||
{
|
||||
if ( ! is_object($user))
|
||||
{
|
||||
$username = $user;
|
||||
|
||||
// Load the user
|
||||
$user = ORM::factory('user');
|
||||
$user->where($user->unique_key($username), '=', $username)->find();
|
||||
}
|
||||
|
||||
return $user->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the login for a user by incrementing the logins and setting
|
||||
* session data: user_id, username, roles.
|
||||
*
|
||||
* @param object user ORM object
|
||||
* @return void
|
||||
*/
|
||||
protected function complete_login($user)
|
||||
{
|
||||
$user->complete_login();
|
||||
|
||||
return parent::complete_login($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare password with original (hashed). Works for current (logged in) user
|
||||
*
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function check_password($password)
|
||||
{
|
||||
$user = $this->get_user();
|
||||
|
||||
if ($user === FALSE)
|
||||
{
|
||||
// nothing to compare
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$hash = $this->hash_password($password, $this->find_salt($user->password));
|
||||
|
||||
return $hash == $user->password;
|
||||
}
|
||||
|
||||
} // End Auth ORM
|
27
includes/kohana/modules/auth/classes/model/auth/role.php
Normal file
27
includes/kohana/modules/auth/classes/model/auth/role.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Default auth role
|
||||
*
|
||||
* @package Kohana/Auth
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2007-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
class Model_Auth_Role extends ORM {
|
||||
|
||||
// Relationships
|
||||
protected $_has_many = array('users' => array('through' => 'roles_users'));
|
||||
|
||||
// Validation rules
|
||||
protected $_rules = array(
|
||||
'name' => array(
|
||||
'not_empty' => NULL,
|
||||
'min_length' => array(4),
|
||||
'max_length' => array(32),
|
||||
),
|
||||
'description' => array(
|
||||
'max_length' => array(255),
|
||||
),
|
||||
);
|
||||
|
||||
} // End Auth Role Model
|
244
includes/kohana/modules/auth/classes/model/auth/user.php
Normal file
244
includes/kohana/modules/auth/classes/model/auth/user.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Default auth user
|
||||
*
|
||||
* @package Kohana/Auth
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2007-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
class Model_Auth_User extends ORM {
|
||||
|
||||
// Relationships
|
||||
protected $_has_many = array(
|
||||
'user_tokens' => array('model' => 'user_token'),
|
||||
'roles' => array('model' => 'role', 'through' => 'roles_users'),
|
||||
);
|
||||
|
||||
// Validation rules
|
||||
protected $_rules = array(
|
||||
'username' => array(
|
||||
'not_empty' => NULL,
|
||||
'min_length' => array(4),
|
||||
'max_length' => array(32),
|
||||
'regex' => array('/^[-\pL\pN_.]++$/uD'),
|
||||
),
|
||||
'password' => array(
|
||||
'not_empty' => NULL,
|
||||
'min_length' => array(5),
|
||||
'max_length' => array(42),
|
||||
),
|
||||
'password_confirm' => array(
|
||||
'matches' => array('password'),
|
||||
),
|
||||
'email' => array(
|
||||
'not_empty' => NULL,
|
||||
'min_length' => array(4),
|
||||
'max_length' => array(127),
|
||||
'email' => NULL,
|
||||
),
|
||||
);
|
||||
|
||||
// Validation callbacks
|
||||
protected $_callbacks = array(
|
||||
'username' => array('username_available'),
|
||||
'email' => array('email_available'),
|
||||
);
|
||||
|
||||
// Field labels
|
||||
protected $_labels = array(
|
||||
'username' => 'username',
|
||||
'email' => 'email address',
|
||||
'password' => 'password',
|
||||
'password_confirm' => 'password confirmation',
|
||||
);
|
||||
|
||||
// Columns to ignore
|
||||
protected $_ignored_columns = array('password_confirm');
|
||||
|
||||
/**
|
||||
* Validates login information from an array, and optionally redirects
|
||||
* after a successful login.
|
||||
*
|
||||
* @param array values to check
|
||||
* @param string URI or URL to redirect to
|
||||
* @return boolean
|
||||
*/
|
||||
public function login(array & $array, $redirect = FALSE)
|
||||
{
|
||||
$fieldname = $this->unique_key($array['username']);
|
||||
$array = Validate::factory($array)
|
||||
->label('username', $this->_labels[$fieldname])
|
||||
->label('password', $this->_labels['password'])
|
||||
->filter(TRUE, 'trim')
|
||||
->rules('username', $this->_rules[$fieldname])
|
||||
->rules('password', $this->_rules['password']);
|
||||
|
||||
// Get the remember login option
|
||||
$remember = isset($array['remember']);
|
||||
|
||||
// Login starts out invalid
|
||||
$status = FALSE;
|
||||
|
||||
if ($array->check())
|
||||
{
|
||||
// Attempt to load the user
|
||||
$this->where($fieldname, '=', $array['username'])->find();
|
||||
|
||||
if ($this->loaded() AND Auth::instance()->login($this, $array['password'], $remember))
|
||||
{
|
||||
if (is_string($redirect))
|
||||
{
|
||||
// Redirect after a successful login
|
||||
Request::instance()->redirect($redirect);
|
||||
}
|
||||
|
||||
// Login is successful
|
||||
$status = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
$array->error('username', 'invalid');
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an array for a matching password and password_confirm field,
|
||||
* and optionally redirects after a successful save.
|
||||
*
|
||||
* @param array values to check
|
||||
* @param string URI or URL to redirect to
|
||||
* @return boolean
|
||||
*/
|
||||
public function change_password(array & $array, $redirect = FALSE)
|
||||
{
|
||||
$array = Validate::factory($array)
|
||||
->label('password', $this->_labels['password'])
|
||||
->label('password_confirm', $this->_labels['password_confirm'])
|
||||
->filter(TRUE, 'trim')
|
||||
->rules('password', $this->_rules['password'])
|
||||
->rules('password_confirm', $this->_rules['password_confirm']);
|
||||
|
||||
if ($status = $array->check())
|
||||
{
|
||||
// Change the password
|
||||
$this->password = $array['password'];
|
||||
|
||||
if ($status = $this->save() AND is_string($redirect))
|
||||
{
|
||||
// Redirect to the success page
|
||||
Request::instance()->redirect($redirect);
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the login for a user by incrementing the logins and saving login timestamp
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function complete_login()
|
||||
{
|
||||
if ( ! $this->_loaded)
|
||||
{
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the number of logins
|
||||
$this->logins = new Database_Expression('logins + 1');
|
||||
|
||||
// Set the last login date
|
||||
$this->last_login = time();
|
||||
|
||||
// Save the user
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the reverse of unique_key_exists() by triggering error if username exists.
|
||||
* Validation callback.
|
||||
*
|
||||
* @param Validate Validate object
|
||||
* @param string field name
|
||||
* @return void
|
||||
*/
|
||||
public function username_available(Validate $array, $field)
|
||||
{
|
||||
if ($this->unique_key_exists($array[$field], 'username'))
|
||||
{
|
||||
$array->error($field, 'username_available', array($array[$field]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the reverse of unique_key_exists() by triggering error if email exists.
|
||||
* Validation callback.
|
||||
*
|
||||
* @param Validate Validate object
|
||||
* @param string field name
|
||||
* @return void
|
||||
*/
|
||||
public function email_available(Validate $array, $field)
|
||||
{
|
||||
if ($this->unique_key_exists($array[$field], 'email'))
|
||||
{
|
||||
$array->error($field, 'email_available', array($array[$field]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a unique key value exists in the database.
|
||||
*
|
||||
* @param mixed the value to test
|
||||
* @param string field name
|
||||
* @return boolean
|
||||
*/
|
||||
public function unique_key_exists($value, $field = NULL)
|
||||
{
|
||||
if ($field === NULL)
|
||||
{
|
||||
// Automatically determine field by looking at the value
|
||||
$field = $this->unique_key($value);
|
||||
}
|
||||
|
||||
return (bool) DB::select(array('COUNT("*")', 'total_count'))
|
||||
->from($this->_table_name)
|
||||
->where($field, '=', $value)
|
||||
->where($this->_primary_key, '!=', $this->pk())
|
||||
->execute($this->_db)
|
||||
->get('total_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a model use both email and username as unique identifiers for login
|
||||
*
|
||||
* @param string unique value
|
||||
* @return string field name
|
||||
*/
|
||||
public function unique_key($value)
|
||||
{
|
||||
return Validate::email($value) ? 'email' : 'username';
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current object. Will hash password if it was changed.
|
||||
*
|
||||
* @return ORM
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if (array_key_exists('password', $this->_changed))
|
||||
{
|
||||
$this->_object['password'] = Auth::instance()->hash_password($this->_object['password']);
|
||||
}
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
} // End Auth User Model
|
101
includes/kohana/modules/auth/classes/model/auth/user/token.php
Normal file
101
includes/kohana/modules/auth/classes/model/auth/user/token.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Default auth user toke
|
||||
*
|
||||
* @package Kohana/Auth
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2007-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
class Model_Auth_User_Token extends ORM {
|
||||
|
||||
// Relationships
|
||||
protected $_belongs_to = array('user' => array());
|
||||
|
||||
// Current timestamp
|
||||
protected $_now;
|
||||
|
||||
/**
|
||||
* Handles garbage collection and deleting of expired objects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($id = NULL)
|
||||
{
|
||||
parent::__construct($id);
|
||||
|
||||
// Set the now, we use this a lot
|
||||
$this->_now = time();
|
||||
|
||||
if (mt_rand(1, 100) === 1)
|
||||
{
|
||||
// Do garbage collection
|
||||
$this->delete_expired();
|
||||
}
|
||||
|
||||
if ($this->expires < $this->_now)
|
||||
{
|
||||
// This object has expired
|
||||
$this->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload saving to set the created time and to create a new token
|
||||
* when the object is saved.
|
||||
*
|
||||
* @return ORM
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if ($this->loaded() === FALSE)
|
||||
{
|
||||
// Set the created time, token, and hash of the user agent
|
||||
$this->created = $this->_now;
|
||||
$this->user_agent = sha1(Request::$user_agent);
|
||||
}
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
// Generate a new token
|
||||
$this->token = $this->create_token();
|
||||
|
||||
try
|
||||
{
|
||||
return parent::save();
|
||||
}
|
||||
catch (Kohana_Database_Exception $e)
|
||||
{
|
||||
// Collision occurred, token is not unique
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all expired tokens.
|
||||
*
|
||||
* @return ORM
|
||||
*/
|
||||
public function delete_expired()
|
||||
{
|
||||
// Delete all expired tokens
|
||||
DB::delete($this->_table_name)
|
||||
->where('expires', '<', $this->_now)
|
||||
->execute($this->_db);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new unique token.
|
||||
*
|
||||
* @return string
|
||||
* @uses Text::random
|
||||
*/
|
||||
protected function create_token()
|
||||
{
|
||||
// Create a random token
|
||||
return Text::random('alnum', 32);
|
||||
}
|
||||
|
||||
} // End Auth User Token Model
|
7
includes/kohana/modules/auth/classes/model/role.php
Normal file
7
includes/kohana/modules/auth/classes/model/role.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
class Model_Role extends Model_Auth_Role {
|
||||
|
||||
// This class can be replaced or extended
|
||||
|
||||
} // End Role Model
|
7
includes/kohana/modules/auth/classes/model/user.php
Normal file
7
includes/kohana/modules/auth/classes/model/user.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
class Model_User extends Model_Auth_User {
|
||||
|
||||
// This class can be replaced or extended
|
||||
|
||||
} // End User Model
|
@ -0,0 +1,7 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
class Model_User_Token extends Model_Auth_User_Token {
|
||||
|
||||
// This class can be replaced or extended
|
||||
|
||||
} // End User Token Model
|
16
includes/kohana/modules/auth/config/auth.php
Normal file
16
includes/kohana/modules/auth/config/auth.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
return array(
|
||||
|
||||
'driver' => 'ORM',
|
||||
'hash_method' => 'sha1',
|
||||
'salt_pattern' => '1, 3, 5, 9, 14, 15, 20, 21, 28, 30',
|
||||
'lifetime' => 1209600,
|
||||
'session_key' => 'auth_user',
|
||||
|
||||
// Username/password combinations for the Auth File driver
|
||||
'users' => array(
|
||||
// 'admin' => 'b3154acf3a344170077d11bdb5fff31532f679a1919e716a02',
|
||||
),
|
||||
|
||||
);
|
48
includes/kohana/modules/auth/mysql.sql
Normal file
48
includes/kohana/modules/auth/mysql.sql
Normal file
@ -0,0 +1,48 @@
|
||||
CREATE TABLE IF NOT EXISTS `roles` (
|
||||
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(32) NOT NULL,
|
||||
`description` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_name` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
INSERT INTO `roles` (`id`, `name`, `description`) VALUES(1, 'login', 'Login privileges, granted after account confirmation');
|
||||
INSERT INTO `roles` (`id`, `name`, `description`) VALUES(2, 'admin', 'Administrative user, has access to everything.');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `roles_users` (
|
||||
`user_id` int(10) UNSIGNED NOT NULL,
|
||||
`role_id` int(10) UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`user_id`,`role_id`),
|
||||
KEY `fk_role_id` (`role_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(127) NOT NULL,
|
||||
`username` varchar(32) NOT NULL DEFAULT '',
|
||||
`password` char(50) NOT NULL,
|
||||
`logins` int(10) UNSIGNED NOT NULL DEFAULT '0',
|
||||
`last_login` int(10) UNSIGNED,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_username` (`username`),
|
||||
UNIQUE KEY `uniq_email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `user_tokens` (
|
||||
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) UNSIGNED NOT NULL,
|
||||
`user_agent` varchar(40) NOT NULL,
|
||||
`token` varchar(32) NOT NULL,
|
||||
`created` int(10) UNSIGNED NOT NULL,
|
||||
`expires` int(10) UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_token` (`token`),
|
||||
KEY `fk_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
ALTER TABLE `roles_users`
|
||||
ADD CONSTRAINT `roles_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT `roles_users_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE `user_tokens`
|
||||
ADD CONSTRAINT `user_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE;
|
53
includes/kohana/modules/auth/postgresql.sql
Normal file
53
includes/kohana/modules/auth/postgresql.sql
Normal file
@ -0,0 +1,53 @@
|
||||
CREATE TABLE roles
|
||||
(
|
||||
id serial,
|
||||
"name" varchar(32) NOT NULL,
|
||||
description text NOT NULL,
|
||||
CONSTRAINT roles_id_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT roles_name_key UNIQUE (name)
|
||||
);
|
||||
|
||||
CREATE TABLE roles_users
|
||||
(
|
||||
user_id integer,
|
||||
role_id integer
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
(
|
||||
id serial,
|
||||
email varchar(318) NOT NULL,
|
||||
username varchar(32) NOT NULL,
|
||||
"password" varchar(50) NOT NULL,
|
||||
logins integer NOT NULL DEFAULT 0,
|
||||
last_login integer,
|
||||
CONSTRAINT users_id_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT users_username_key UNIQUE (username),
|
||||
CONSTRAINT users_email_key UNIQUE (email),
|
||||
CONSTRAINT users_logins_check CHECK (logins >= 0)
|
||||
);
|
||||
|
||||
CREATE TABLE user_tokens
|
||||
(
|
||||
id serial,
|
||||
user_id integer NOT NULL,
|
||||
user_agent varchar(40) NOT NULL,
|
||||
token character varying(32) NOT NULL,
|
||||
created integer NOT NULL,
|
||||
expires integer NOT NULL,
|
||||
CONSTRAINT user_tokens_id_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT user_tokens_token_key UNIQUE (token)
|
||||
);
|
||||
|
||||
CREATE INDEX user_id_idx ON roles_users (user_id);
|
||||
CREATE INDEX role_id_idx ON roles_users (role_id);
|
||||
|
||||
ALTER TABLE roles_users
|
||||
ADD CONSTRAINT user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT role_id_fkey FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE user_tokens
|
||||
ADD CONSTRAINT user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
INSERT INTO roles (name, description) VALUES ('login', 'Login privileges, granted after account confirmation');
|
||||
INSERT INTO roles (name, description) VALUES ('admin', 'Administrative user, has access to everything.');
|
60
includes/kohana/modules/cache/README.md
vendored
Normal file
60
includes/kohana/modules/cache/README.md
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
Kohana Cache library
|
||||
====================
|
||||
|
||||
The cache library for Kohana 3 provides a simple interface to the most common cache solutions. Developers are free to add their own caching solutions that follow the cache design pattern defined within this module.
|
||||
|
||||
Supported cache solutions
|
||||
-------------------------
|
||||
|
||||
Currently this module supports the following cache methods.
|
||||
|
||||
1. APC
|
||||
2. eAccelerator
|
||||
3. Memcache
|
||||
4. Memcached-tags (Supports tags)
|
||||
5. SQLite (Supports tags)
|
||||
6. File
|
||||
7. Xcache
|
||||
|
||||
Planned support
|
||||
---------------
|
||||
|
||||
In the near future, additional support for the following methods will be included.
|
||||
|
||||
1. Memcached
|
||||
|
||||
Introduction to caching
|
||||
-----------------------
|
||||
|
||||
To use caching to the maximum potential, your application should be designed with caching in mind from the outset. In general, the most effective caches contain lots of small collections of data that are the result of expensive computational operations, such as searching through a large data set.
|
||||
|
||||
There are many different caching methods available for PHP, from the very basic file based caching to opcode caching in eAccelerator and APC. Caching engines that use physical memory over disk based storage are always faster, however many do not support more advanced features such as tagging.
|
||||
|
||||
Using Cache
|
||||
-----------
|
||||
|
||||
To use Kohana Cache, download and extract the latest stable release of Kohana Cache from [Github](http://github.com/samsoir/kohana-cache). Place the module into your Kohana instances modules folder. Finally enable the module within the application bootstrap within the section entitled _modules_.
|
||||
|
||||
Quick example
|
||||
-------------
|
||||
|
||||
The following is a quick example of how to use Kohana Cache. The example is using the SQLite driver.
|
||||
|
||||
<?php
|
||||
// Get a Sqlite Cache instance
|
||||
$mycache = Cache::instance('sqlite');
|
||||
|
||||
// Create some data
|
||||
$data = array('foo' => 'bar', 'apples' => 'pear', 'BDFL' => 'Shadowhand');
|
||||
|
||||
// Save the data to cache, with an id of test_id and a lifetime of 10 minutes
|
||||
$mycache->set('test_id', $data, 600);
|
||||
|
||||
// Retrieve the data from cache
|
||||
$retrieved_data = $mycache->get('test_id');
|
||||
|
||||
// Remove the cache item
|
||||
$mycache->delete('test_id');
|
||||
|
||||
// Clear the cache of all stored items
|
||||
$mycache->delete_all();
|
3
includes/kohana/modules/cache/classes/cache.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
abstract class Cache extends Kohana_Cache {}
|
3
includes/kohana/modules/cache/classes/cache/apc.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache/apc.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Cache_Apc extends Kohana_Cache_Apc {}
|
3
includes/kohana/modules/cache/classes/cache/eaccelerator.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache/eaccelerator.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Cache_Eaccelerator extends Kohana_Cache_Eaccelerator {}
|
3
includes/kohana/modules/cache/classes/cache/file.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache/file.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Cache_File extends Kohana_Cache_File {}
|
3
includes/kohana/modules/cache/classes/cache/memcache.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache/memcache.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Cache_Memcache extends Kohana_Cache_Memcache {}
|
3
includes/kohana/modules/cache/classes/cache/memcachetag.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache/memcachetag.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Cache_MemcacheTag extends Kohana_Cache_MemcacheTag {}
|
3
includes/kohana/modules/cache/classes/cache/sqlite.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache/sqlite.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Cache_Sqlite extends Kohana_Cache_Sqlite {}
|
3
includes/kohana/modules/cache/classes/cache/xcache.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/cache/xcache.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Cache_Xcache extends Kohana_Cache_Xcache {}
|
256
includes/kohana/modules/cache/classes/kohana/cache.php
vendored
Normal file
256
includes/kohana/modules/cache/classes/kohana/cache.php
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Kohana Cache provides a common interface to a variety of caching engines. Tags are
|
||||
* supported where available natively to the cache system. Kohana Cache supports multiple
|
||||
* instances of cache engines through a grouped singleton pattern.
|
||||
*
|
||||
* ### Supported cache engines
|
||||
*
|
||||
* * [APC](http://php.net/manual/en/book.apc.php)
|
||||
* * [eAccelerator](http://eaccelerator.net/)
|
||||
* * File
|
||||
* * [Memcache](http://memcached.org/)
|
||||
* * [Memcached-tags](http://code.google.com/p/memcached-tags/)
|
||||
* * [SQLite](http://www.sqlite.org/)
|
||||
* * [Xcache](http://xcache.lighttpd.net/)
|
||||
*
|
||||
* ### Introduction to caching
|
||||
*
|
||||
* Caching should be implemented with consideration. Generally, caching the result of resources
|
||||
* is faster than reprocessing them. Choosing what, how and when to cache is vital. PHP APC is
|
||||
* presently one of the fastest caching systems available, closely followed by Memcache. SQLite
|
||||
* and File caching are two of the slowest cache methods, however usually faster than reprocessing
|
||||
* a complex set of instructions.
|
||||
*
|
||||
* Caching engines that use memory are considerably faster than the file based alternatives. But
|
||||
* memory is limited whereas disk space is plentiful. If caching large datasets it is best to use
|
||||
* file caching.
|
||||
*
|
||||
* ### Configuration settings
|
||||
*
|
||||
* Kohana Cache uses configuration groups to create cache instances. A configuration group can
|
||||
* use any supported driver, with successive groups using the same driver type if required.
|
||||
*
|
||||
* #### Configuration example
|
||||
*
|
||||
* Below is an example of a _memcache_ server configuration.
|
||||
*
|
||||
* return array(
|
||||
* 'default' => array( // Default group
|
||||
* 'driver' => 'memcache', // using Memcache driver
|
||||
* 'servers' => array( // Available server definitions
|
||||
* array(
|
||||
* 'host' => 'localhost',
|
||||
* 'port' => 11211,
|
||||
* 'persistent' => FALSE
|
||||
* )
|
||||
* ),
|
||||
* 'compression' => FALSE, // Use compression?
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* In cases where only one cache group is required, if the group is named `default` there is
|
||||
* no need to pass the group name when instantiating a cache instance.
|
||||
*
|
||||
* #### General cache group configuration settings
|
||||
*
|
||||
* Below are the settings available to all types of cache driver.
|
||||
*
|
||||
* Name | Required | Description
|
||||
* -------------- | -------- | ---------------------------------------------------------------
|
||||
* driver | __YES__ | (_string_) The driver type to use
|
||||
*
|
||||
* Details of the settings specific to each driver are available within the drivers documentation.
|
||||
*
|
||||
* ### System requirements
|
||||
*
|
||||
* * Kohana 3.0.x
|
||||
* * PHP 5.2.4 or greater
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @version 2.0
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
abstract class Kohana_Cache {
|
||||
|
||||
const DEFAULT_EXPIRE = 3600;
|
||||
|
||||
/**
|
||||
* @var string default driver to use
|
||||
*/
|
||||
public static $default = 'file';
|
||||
|
||||
/**
|
||||
* @var Kohana_Cache instances
|
||||
*/
|
||||
public static $instances = array();
|
||||
|
||||
/**
|
||||
* Creates a singleton of a Kohana Cache group. If no group is supplied
|
||||
* the __default__ cache group is used.
|
||||
*
|
||||
* // Create an instance of the default group
|
||||
* $default_group = Cache::instance();
|
||||
*
|
||||
* // Create an instance of a group
|
||||
* $foo_group = Cache::instance('foo');
|
||||
*
|
||||
* // Access an instantiated group directly
|
||||
* $foo_group = Cache::$instances['default'];
|
||||
*
|
||||
* @param string the name of the cache group to use [Optional]
|
||||
* @return Kohana_Cache
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public static function instance($group = NULL)
|
||||
{
|
||||
// If there is no group supplied
|
||||
if ($group === NULL)
|
||||
{
|
||||
// Use the default setting
|
||||
$group = Cache::$default;
|
||||
}
|
||||
|
||||
if (isset(Cache::$instances[$group]))
|
||||
{
|
||||
// Return the current group if initiated already
|
||||
return Cache::$instances[$group];
|
||||
}
|
||||
|
||||
$config = Kohana::config('cache');
|
||||
|
||||
if ( ! $config->offsetExists($group))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Failed to load Kohana Cache group: :group', array(':group' => $group));
|
||||
}
|
||||
|
||||
$config = $config->get($group);
|
||||
|
||||
// Create a new cache type instance
|
||||
$cache_class = 'Cache_'.ucfirst($config['driver']);
|
||||
Cache::$instances[$group] = new $cache_class($config);
|
||||
|
||||
// Return the instance
|
||||
return Cache::$instances[$group];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Kohana_Config
|
||||
*/
|
||||
protected $_config;
|
||||
|
||||
/**
|
||||
* Ensures singleton pattern is observed, loads the default expiry
|
||||
*
|
||||
* @param array configuration
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
$this->_config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload the __clone() method to prevent cloning
|
||||
*
|
||||
* @return void
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Cloning of Kohana_Cache objects is forbidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached value entry by id.
|
||||
*
|
||||
* // Retrieve cache entry from default group
|
||||
* $data = Cache::instance()->get('foo');
|
||||
*
|
||||
* // Retrieve cache entry from default group and return 'bar' if miss
|
||||
* $data = Cache::instance()->get('foo', 'bar');
|
||||
*
|
||||
* // Retrieve cache entry from memcache group
|
||||
* $data = Cache::instance('memcache')->get('foo');
|
||||
*
|
||||
* @param string id of cache to entry
|
||||
* @param string default value to return if cache miss
|
||||
* @return mixed
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
abstract public function get($id, $default = NULL);
|
||||
|
||||
/**
|
||||
* Set a value to cache with id and lifetime
|
||||
*
|
||||
* $data = 'bar';
|
||||
*
|
||||
* // Set 'bar' to 'foo' in default group, using default expiry
|
||||
* Cache::instance()->set('foo', $data);
|
||||
*
|
||||
* // Set 'bar' to 'foo' in default group for 30 seconds
|
||||
* Cache::instance()->set('foo', $data, 30);
|
||||
*
|
||||
* // Set 'bar' to 'foo' in memcache group for 10 minutes
|
||||
* if (Cache::instance('memcache')->set('foo', $data, 600))
|
||||
* {
|
||||
* // Cache was set successfully
|
||||
* return
|
||||
* }
|
||||
*
|
||||
* @param string id of cache entry
|
||||
* @param string data to set to cache
|
||||
* @param integer lifetime in seconds
|
||||
* @return boolean
|
||||
*/
|
||||
abstract public function set($id, $data, $lifetime = 3600);
|
||||
|
||||
/**
|
||||
* Delete a cache entry based on id
|
||||
*
|
||||
* // Delete 'foo' entry from the default group
|
||||
* Cache::instance()->delete('foo');
|
||||
*
|
||||
* // Delete 'foo' entry from the memcache group
|
||||
* Cache::instance('memcache')->delete('foo')
|
||||
*
|
||||
* @param string id to remove from cache
|
||||
* @return boolean
|
||||
*/
|
||||
abstract public function delete($id);
|
||||
|
||||
/**
|
||||
* Delete all cache entries.
|
||||
*
|
||||
* Beware of using this method when
|
||||
* using shared memory cache systems, as it will wipe every
|
||||
* entry within the system for all clients.
|
||||
*
|
||||
* // Delete all cache entries in the default group
|
||||
* Cache::instance()->delete_all();
|
||||
*
|
||||
* // Delete all cache entries in the memcache group
|
||||
* Cache::instance('memcache')->delete_all();
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
abstract public function delete_all();
|
||||
|
||||
/**
|
||||
* Replaces troublesome characters with underscores.
|
||||
*
|
||||
* // Sanitize a cache id
|
||||
* $id = $this->_sanitize_id($id);
|
||||
*
|
||||
* @param string id of cache to sanitize
|
||||
* @return string
|
||||
*/
|
||||
protected function _sanitize_id($id)
|
||||
{
|
||||
// Change slashes and spaces to underscores
|
||||
return str_replace(array('/', '\\', ' '), '_', $id);
|
||||
}
|
||||
}
|
||||
// End Kohana_Cache
|
133
includes/kohana/modules/cache/classes/kohana/cache/apc.php
vendored
Normal file
133
includes/kohana/modules/cache/classes/kohana/cache/apc.php
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* [Kohana Cache](api/Kohana_Cache) APC driver. Provides an opcode based
|
||||
* driver for the Kohana Cache library.
|
||||
*
|
||||
* ### Configuration example
|
||||
*
|
||||
* Below is an example of an _apc_ server configuration.
|
||||
*
|
||||
* return array(
|
||||
* 'apc' => array( // Driver group
|
||||
* 'driver' => 'apc', // using APC driver
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* In cases where only one cache group is required, if the group is named `default` there is
|
||||
* no need to pass the group name when instantiating a cache instance.
|
||||
*
|
||||
* #### General cache group configuration settings
|
||||
*
|
||||
* Below are the settings available to all types of cache driver.
|
||||
*
|
||||
* Name | Required | Description
|
||||
* -------------- | -------- | ---------------------------------------------------------------
|
||||
* driver | __YES__ | (_string_) The driver type to use
|
||||
*
|
||||
* ### System requirements
|
||||
*
|
||||
* * Kohana 3.0.x
|
||||
* * PHP 5.2.4 or greater
|
||||
* * APC PHP extension
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Cache_Apc extends Cache {
|
||||
|
||||
/**
|
||||
* Check for existence of the APC extension This method cannot be invoked externally. The driver must
|
||||
* be instantiated using the `Cache::instance()` method.
|
||||
*
|
||||
* @param array configuration
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
if ( ! extension_loaded('apc'))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('PHP APC extension is not available.');
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached value entry by id.
|
||||
*
|
||||
* // Retrieve cache entry from apc group
|
||||
* $data = Cache::instance('apc')->get('foo');
|
||||
*
|
||||
* // Retrieve cache entry from apc group and return 'bar' if miss
|
||||
* $data = Cache::instance('apc')->get('foo', 'bar');
|
||||
*
|
||||
* @param string id of cache to entry
|
||||
* @param string default value to return if cache miss
|
||||
* @return mixed
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function get($id, $default = NULL)
|
||||
{
|
||||
return (($data = apc_fetch($this->_sanitize_id($id))) === FALSE) ? $default : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value to cache with id and lifetime
|
||||
*
|
||||
* $data = 'bar';
|
||||
*
|
||||
* // Set 'bar' to 'foo' in apc group, using default expiry
|
||||
* Cache::instance('apc')->set('foo', $data);
|
||||
*
|
||||
* // Set 'bar' to 'foo' in apc group for 30 seconds
|
||||
* Cache::instance('apc')->set('foo', $data, 30);
|
||||
*
|
||||
* @param string id of cache entry
|
||||
* @param string data to set to cache
|
||||
* @param integer lifetime in seconds
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($id, $data, $lifetime = NULL)
|
||||
{
|
||||
if ($lifetime === NULL)
|
||||
{
|
||||
$lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
|
||||
}
|
||||
|
||||
return apc_store($this->_sanitize_id($id), $data, $lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cache entry based on id
|
||||
*
|
||||
* // Delete 'foo' entry from the apc group
|
||||
* Cache::instance('apc')->delete('foo');
|
||||
*
|
||||
* @param string id to remove from cache
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
return apc_delete($this->_sanitize_id($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cache entries.
|
||||
*
|
||||
* Beware of using this method when
|
||||
* using shared memory cache systems, as it will wipe every
|
||||
* entry within the system for all clients.
|
||||
*
|
||||
* // Delete all cache entries in the apc group
|
||||
* Cache::instance('apc')->delete_all();
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
return apc_clear_cache('user');
|
||||
}
|
||||
}
|
133
includes/kohana/modules/cache/classes/kohana/cache/eaccelerator.php
vendored
Normal file
133
includes/kohana/modules/cache/classes/kohana/cache/eaccelerator.php
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* [Kohana Cache](api/Kohana_Cache) Eaccelerator driver. Provides an opcode based
|
||||
* driver for the Kohana Cache library.
|
||||
*
|
||||
* ### Configuration example
|
||||
*
|
||||
* Below is an example of an _eaccelerator_ server configuration.
|
||||
*
|
||||
* return array(
|
||||
* 'eaccelerator' => array( // Driver group
|
||||
* 'driver' => 'eaccelerator', // using Eaccelerator driver
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* In cases where only one cache group is required, if the group is named `default` there is
|
||||
* no need to pass the group name when instantiating a cache instance.
|
||||
*
|
||||
* #### General cache group configuration settings
|
||||
*
|
||||
* Below are the settings available to all types of cache driver.
|
||||
*
|
||||
* Name | Required | Description
|
||||
* -------------- | -------- | ---------------------------------------------------------------
|
||||
* driver | __YES__ | (_string_) The driver type to use
|
||||
*
|
||||
* ### System requirements
|
||||
*
|
||||
* * Kohana 3.0.x
|
||||
* * PHP 5.2.4 or greater
|
||||
* * Eaccelerator PHP extension
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Cache_Eaccelerator extends Cache {
|
||||
|
||||
/**
|
||||
* Check for existence of the eAccelerator extension This method cannot be invoked externally. The driver must
|
||||
* be instantiated using the `Cache::instance()` method.
|
||||
*
|
||||
* @param array configuration
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
if ( ! extension_loaded('eaccelerator'))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('PHP eAccelerator extension is not available.');
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached value entry by id.
|
||||
*
|
||||
* // Retrieve cache entry from eaccelerator group
|
||||
* $data = Cache::instance('eaccelerator')->get('foo');
|
||||
*
|
||||
* // Retrieve cache entry from eaccelerator group and return 'bar' if miss
|
||||
* $data = Cache::instance('eaccelerator')->get('foo', 'bar');
|
||||
*
|
||||
* @param string id of cache to entry
|
||||
* @param string default value to return if cache miss
|
||||
* @return mixed
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function get($id, $default = NULL)
|
||||
{
|
||||
return (($data = eaccelerator_get($this->_sanitize_id($id))) === FALSE) ? $default : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value to cache with id and lifetime
|
||||
*
|
||||
* $data = 'bar';
|
||||
*
|
||||
* // Set 'bar' to 'foo' in eaccelerator group, using default expiry
|
||||
* Cache::instance('eaccelerator')->set('foo', $data);
|
||||
*
|
||||
* // Set 'bar' to 'foo' in eaccelerator group for 30 seconds
|
||||
* Cache::instance('eaccelerator')->set('foo', $data, 30);
|
||||
*
|
||||
* @param string id of cache entry
|
||||
* @param string data to set to cache
|
||||
* @param integer lifetime in seconds
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($id, $data, $lifetime = NULL)
|
||||
{
|
||||
if ($lifetime === NULL)
|
||||
{
|
||||
$lifetime = time() + Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
|
||||
}
|
||||
|
||||
return eaccelerator_put($this->_sanitize_id($id), $data, $lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cache entry based on id
|
||||
*
|
||||
* // Delete 'foo' entry from the eaccelerator group
|
||||
* Cache::instance('eaccelerator')->delete('foo');
|
||||
*
|
||||
* @param string id to remove from cache
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
return eaccelerator_rm($this->_sanitize_id($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cache entries.
|
||||
*
|
||||
* Beware of using this method when
|
||||
* using shared memory cache systems, as it will wipe every
|
||||
* entry within the system for all clients.
|
||||
*
|
||||
* // Delete all cache entries in the eaccelerator group
|
||||
* Cache::instance('eaccelerator')->delete_all();
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
return eaccelerator_clean();
|
||||
}
|
||||
}
|
3
includes/kohana/modules/cache/classes/kohana/cache/exception.php
vendored
Normal file
3
includes/kohana/modules/cache/classes/kohana/cache/exception.php
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Kohana_Cache_Exception extends Kohana_Exception {}
|
445
includes/kohana/modules/cache/classes/kohana/cache/file.php
vendored
Normal file
445
includes/kohana/modules/cache/classes/kohana/cache/file.php
vendored
Normal file
@ -0,0 +1,445 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* [Kohana Cache](api/Kohana_Cache) File driver. Provides a file based
|
||||
* driver for the Kohana Cache library. This is one of the slowest
|
||||
* caching methods.
|
||||
*
|
||||
* ### Configuration example
|
||||
*
|
||||
* Below is an example of a _file_ server configuration.
|
||||
*
|
||||
* return array(
|
||||
* 'file' => array( // File driver group
|
||||
* 'driver' => 'file', // using File driver
|
||||
* 'cache_dir' => APPPATH.'cache/.kohana_cache', // Cache location
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* In cases where only one cache group is required, if the group is named `default` there is
|
||||
* no need to pass the group name when instantiating a cache instance.
|
||||
*
|
||||
* #### General cache group configuration settings
|
||||
*
|
||||
* Below are the settings available to all types of cache driver.
|
||||
*
|
||||
* Name | Required | Description
|
||||
* -------------- | -------- | ---------------------------------------------------------------
|
||||
* driver | __YES__ | (_string_) The driver type to use
|
||||
* cache_dir | __NO__ | (_string_) The cache directory to use for this cache instance
|
||||
*
|
||||
* ### System requirements
|
||||
*
|
||||
* * Kohana 3.0.x
|
||||
* * PHP 5.2.4 or greater
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Cache_File extends Cache implements Kohana_Cache_GarbageCollect {
|
||||
|
||||
// !!! NOTICE !!!
|
||||
// THIS CONSTANT IS USED BY THE FILE CACHE CLASS
|
||||
// INTERNALLY. USE THE CONFIGURATION FILE TO
|
||||
// REDEFINE THE CACHE DIRECTORY.
|
||||
const CACHE_DIR = 'cache/.kohana_cache';
|
||||
|
||||
/**
|
||||
* Creates a hashed filename based on the string. This is used
|
||||
* to create shorter unique IDs for each cache filename.
|
||||
*
|
||||
* // Create the cache filename
|
||||
* $filename = Cache_File::filename($this->_sanitize_id($id));
|
||||
*
|
||||
* @param string string to hash into filename
|
||||
* @return string
|
||||
*/
|
||||
protected static function filename($string)
|
||||
{
|
||||
return sha1($string).'.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string the caching directory
|
||||
*/
|
||||
protected $_cache_dir;
|
||||
|
||||
/**
|
||||
* Constructs the file cache driver. This method cannot be invoked externally. The file cache driver must
|
||||
* be instantiated using the `Cache::instance()` method.
|
||||
*
|
||||
* @param array config
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
// Setup parent
|
||||
parent::__construct($config);
|
||||
|
||||
try
|
||||
{
|
||||
$directory = Arr::get($this->_config, 'cache_dir', APPPATH.Cache_File::CACHE_DIR);
|
||||
$this->_cache_dir = new RecursiveDirectoryIterator($directory);
|
||||
}
|
||||
catch (UnexpectedValueException $e)
|
||||
{
|
||||
if ( ! mkdir($directory, 0777, TRUE))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Failed to create the defined cache directory : :directory', array(':directory' => $directory));
|
||||
}
|
||||
chmod($directory, 0777);
|
||||
$this->_cache_dir = new RecursiveDirectoryIterator($directory);
|
||||
}
|
||||
|
||||
// If the defined directory is a file, get outta here
|
||||
if ($this->_cache_dir->isFile())
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Unable to create cache directory as a file already exists : :resource', array(':resource' => $this->_cache_dir->getRealPath()));
|
||||
}
|
||||
|
||||
// Check the read status of the directory
|
||||
if ( ! $this->_cache_dir->isReadable())
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Unable to read from the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath()));
|
||||
}
|
||||
|
||||
// Check the write status of the directory
|
||||
if ( ! $this->_cache_dir->isWritable())
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Unable to write to the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached value entry by id.
|
||||
*
|
||||
* // Retrieve cache entry from file group
|
||||
* $data = Cache::instance('file')->get('foo');
|
||||
*
|
||||
* // Retrieve cache entry from file group and return 'bar' if miss
|
||||
* $data = Cache::instance('file')->get('foo', 'bar');
|
||||
*
|
||||
* @param string id of cache to entry
|
||||
* @param string default value to return if cache miss
|
||||
* @return mixed
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function get($id, $default = NULL)
|
||||
{
|
||||
$filename = Cache_File::filename($this->_sanitize_id($id));
|
||||
$directory = $this->_resolve_directory($filename);
|
||||
|
||||
// Wrap operations in try/catch to handle notices
|
||||
try
|
||||
{
|
||||
// Open file
|
||||
$file = new SplFileInfo($directory.$filename);
|
||||
|
||||
// If file does not exist
|
||||
if ( ! $file->getRealPath())
|
||||
{
|
||||
// Return default value
|
||||
return $default;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open the file and extract the json
|
||||
$json = $file->openFile()->current();
|
||||
|
||||
// Decode the json into PHP object
|
||||
$data = json_decode($json);
|
||||
|
||||
// Test the expiry
|
||||
if ($data->expiry < time())
|
||||
{
|
||||
// Delete the file
|
||||
$this->_delete_file($file, NULL, TRUE);
|
||||
|
||||
// Return default value
|
||||
return $default;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ($data->type === 'string') ? $data->payload : unserialize($data->payload);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (ErrorException $e)
|
||||
{
|
||||
// Handle ErrorException caused by failed unserialization
|
||||
if ($e->getCode() === E_NOTICE)
|
||||
{
|
||||
throw new Kohana_Cache_Exception(__METHOD__.' failed to unserialize cached object with message : '.$e->getMessage());
|
||||
}
|
||||
|
||||
// Otherwise throw the exception
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value to cache with id and lifetime
|
||||
*
|
||||
* $data = 'bar';
|
||||
*
|
||||
* // Set 'bar' to 'foo' in file group, using default expiry
|
||||
* Cache::instance('file')->set('foo', $data);
|
||||
*
|
||||
* // Set 'bar' to 'foo' in file group for 30 seconds
|
||||
* Cache::instance('file')->set('foo', $data, 30);
|
||||
*
|
||||
* @param string id of cache entry
|
||||
* @param string data to set to cache
|
||||
* @param integer lifetime in seconds
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($id, $data, $lifetime = NULL)
|
||||
{
|
||||
$filename = Cache_File::filename($this->_sanitize_id($id));
|
||||
$directory = $this->_resolve_directory($filename);
|
||||
|
||||
// If lifetime is NULL
|
||||
if ($lifetime === NULL)
|
||||
{
|
||||
// Set to the default expiry
|
||||
$lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
|
||||
}
|
||||
|
||||
// Open directory
|
||||
$dir = new SplFileInfo($directory);
|
||||
|
||||
// If the directory path is not a directory
|
||||
if ( ! $dir->isDir())
|
||||
{
|
||||
// Create the directory
|
||||
if ( ! mkdir($directory, 0777, TRUE))
|
||||
{
|
||||
throw new Kohana_Cache_Exception(__METHOD__.' unable to create directory : :directory', array(':directory' => $directory));
|
||||
}
|
||||
|
||||
// chmod to solve potential umask issues
|
||||
chmod($directory, 0777);
|
||||
}
|
||||
|
||||
// Open file to inspect
|
||||
$resouce = new SplFileInfo($directory.$filename);
|
||||
$file = $resouce->openFile('w');
|
||||
|
||||
try
|
||||
{
|
||||
$type = gettype($data);
|
||||
|
||||
// Serialize the data
|
||||
$data = json_encode((object) array(
|
||||
'payload' => ($type === 'string') ? $data : serialize($data),
|
||||
'expiry' => time() + $lifetime,
|
||||
'type' => $type
|
||||
));
|
||||
|
||||
$size = strlen($data);
|
||||
}
|
||||
catch (ErrorException $e)
|
||||
{
|
||||
// If serialize through an error exception
|
||||
if ($e->getCode() === E_NOTICE)
|
||||
{
|
||||
// Throw a caching error
|
||||
throw new Kohana_Cache_Exception(__METHOD__.' failed to serialize data for caching with message : '.$e->getMessage());
|
||||
}
|
||||
|
||||
// Else rethrow the error exception
|
||||
throw $e;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$file->fwrite($data, $size);
|
||||
return (bool) $file->fflush();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cache entry based on id
|
||||
*
|
||||
* // Delete 'foo' entry from the file group
|
||||
* Cache::instance('file')->delete('foo');
|
||||
*
|
||||
* @param string id to remove from cache
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
$filename = Cache_File::filename($this->_sanitize_id($id));
|
||||
$directory = $this->_resolve_directory($filename);
|
||||
|
||||
return $this->_delete_file(new SplFileInfo($directory.$filename), NULL, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cache entries.
|
||||
*
|
||||
* Beware of using this method when
|
||||
* using shared memory cache systems, as it will wipe every
|
||||
* entry within the system for all clients.
|
||||
*
|
||||
* // Delete all cache entries in the file group
|
||||
* Cache::instance('file')->delete_all();
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
return $this->_delete_file($this->_cache_dir, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collection method that cleans any expired
|
||||
* cache entries from the cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function garbage_collect()
|
||||
{
|
||||
$this->_delete_file($this->_cache_dir, TRUE, FALSE, TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes files recursively and returns FALSE on any errors
|
||||
*
|
||||
* // Delete a file or folder whilst retaining parent directory and ignore all errors
|
||||
* $this->_delete_file($folder, TRUE, TRUE);
|
||||
*
|
||||
* @param SplFileInfo file
|
||||
* @param boolean retain the parent directory
|
||||
* @param boolean ignore_errors to prevent all exceptions interrupting exec
|
||||
* @param boolean only expired files
|
||||
* @return boolean
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function _delete_file(SplFileInfo $file, $retain_parent_directory = FALSE, $ignore_errors = FALSE, $only_expired = FALSE)
|
||||
{
|
||||
// Allow graceful error handling
|
||||
try
|
||||
{
|
||||
// If is file
|
||||
if ($file->isFile())
|
||||
{
|
||||
try
|
||||
{
|
||||
// If only expired is not set
|
||||
if ($only_expired === FALSE)
|
||||
{
|
||||
// We want to delete the file
|
||||
$delete = TRUE;
|
||||
}
|
||||
// Otherwise...
|
||||
else
|
||||
{
|
||||
// Assess the file expiry to flag it for deletion
|
||||
$json = $file->openFile('r')->current();
|
||||
$data = json_decode($json);
|
||||
$delete = $data->expiry < time();
|
||||
}
|
||||
|
||||
// If the delete flag is set
|
||||
if ($delete === TRUE)
|
||||
{
|
||||
// Try to delete
|
||||
unlink($file->getRealPath());
|
||||
}
|
||||
}
|
||||
catch (ErrorException $e)
|
||||
{
|
||||
// Catch any delete file warnings
|
||||
if ($e->getCode() === E_WARNING)
|
||||
{
|
||||
throw new Kohana_Cache_Exception(__METHOD__.' failed to delete file : :file', array(':file' => $file->getRealPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Else, is directory
|
||||
else if ($file->isDir())
|
||||
{
|
||||
// Create new DirectoryIterator
|
||||
$files = new DirectoryIterator($file->getPathname());
|
||||
|
||||
// Iterate over each entry
|
||||
while ($files->valid())
|
||||
{
|
||||
// Extract the entry name
|
||||
$name = $files->getFilename();
|
||||
|
||||
// If the name is not a dot
|
||||
if ($name != '.' and $name != '..')
|
||||
{
|
||||
// Create new file resource
|
||||
$fp = new SplFileInfo($files->getRealPath());
|
||||
// Delete the file
|
||||
$this->_delete_file($fp);
|
||||
}
|
||||
|
||||
// Move the file pointer on
|
||||
$files->next();
|
||||
}
|
||||
|
||||
// If set to retain parent directory, return now
|
||||
if ($retain_parent_directory)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Remove the files iterator
|
||||
// (fixes Windows PHP which has permission issues with open iterators)
|
||||
unset($files);
|
||||
|
||||
// Try to remove the parent directory
|
||||
return rmdir($file->getRealPath());
|
||||
}
|
||||
catch (ErrorException $e)
|
||||
{
|
||||
// Catch any delete directory warnings
|
||||
if ($e->getCode() === E_WARNING)
|
||||
{
|
||||
throw new Kohana_Cache_Exception(__METHOD__.' failed to delete directory : :directory', array(':directory' => $file->getRealPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Catch all exceptions
|
||||
catch (Exception $e)
|
||||
{
|
||||
// If ignore_errors is on
|
||||
if ($ignore_errors === TRUE)
|
||||
{
|
||||
// Return
|
||||
return FALSE;
|
||||
}
|
||||
// Throw exception
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the cache directory real path from the filename
|
||||
*
|
||||
* // Get the realpath of the cache folder
|
||||
* $realpath = $this->_resolve_directory($filename);
|
||||
*
|
||||
* @param string filename to resolve
|
||||
* @return string
|
||||
*/
|
||||
protected function _resolve_directory($filename)
|
||||
{
|
||||
return $this->_cache_dir->getRealPath().DIRECTORY_SEPARATOR.$filename[0].$filename[1].DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
23
includes/kohana/modules/cache/classes/kohana/cache/garbagecollect.php
vendored
Normal file
23
includes/kohana/modules/cache/classes/kohana/cache/garbagecollect.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Garbage Collection interface for caches that have no GC methods
|
||||
* of their own, such as [Cache_File] and [Cache_Sqlite]. Memory based
|
||||
* cache systems clean their own caches periodically.
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @version 2.0
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
* @since 3.0.8
|
||||
*/
|
||||
interface Kohana_Cache_GarbageCollect {
|
||||
/**
|
||||
* Garbage collection method that cleans any expired
|
||||
* cache entries from the cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function garbage_collect();
|
||||
}
|
311
includes/kohana/modules/cache/classes/kohana/cache/memcache.php
vendored
Normal file
311
includes/kohana/modules/cache/classes/kohana/cache/memcache.php
vendored
Normal file
@ -0,0 +1,311 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* [Kohana Cache](api/Kohana_Cache) Memcache driver,
|
||||
*
|
||||
* ### Supported cache engines
|
||||
*
|
||||
* * [Memcache](http://www.php.net/manual/en/book.memcache.php)
|
||||
* * [Memcached-tags](http://code.google.com/p/memcached-tags/)
|
||||
*
|
||||
* ### Configuration example
|
||||
*
|
||||
* Below is an example of a _memcache_ server configuration.
|
||||
*
|
||||
* return array(
|
||||
* 'default' => array( // Default group
|
||||
* 'driver' => 'memcache', // using Memcache driver
|
||||
* 'servers' => array( // Available server definitions
|
||||
* // First memcache server server
|
||||
* array(
|
||||
* 'host' => 'localhost',
|
||||
* 'port' => 11211,
|
||||
* 'persistent' => FALSE
|
||||
* 'weight' => 1,
|
||||
* 'timeout' => 1,
|
||||
* 'retry_interval' => 15,
|
||||
* 'status' => TRUE,
|
||||
* 'failure_callback' => array('className', 'classMethod')
|
||||
* ),
|
||||
* // Second memcache server
|
||||
* array(
|
||||
* 'host' => '192.168.1.5',
|
||||
* 'port' => 22122,
|
||||
* 'persistent' => TRUE
|
||||
* )
|
||||
* ),
|
||||
* 'compression' => FALSE, // Use compression?
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* In cases where only one cache group is required, if the group is named `default` there is
|
||||
* no need to pass the group name when instantiating a cache instance.
|
||||
*
|
||||
* #### General cache group configuration settings
|
||||
*
|
||||
* Below are the settings available to all types of cache driver.
|
||||
*
|
||||
* Name | Required | Description
|
||||
* -------------- | -------- | ---------------------------------------------------------------
|
||||
* driver | __YES__ | (_string_) The driver type to use
|
||||
* servers | __YES__ | (_array_) Associative array of server details, must include a __host__ key. (see _Memcache server configuration_ below)
|
||||
* compression | __NO__ | (_boolean_) Use data compression when caching
|
||||
*
|
||||
* #### Memcache server configuration
|
||||
*
|
||||
* The following settings should be used when defining each memcache server
|
||||
*
|
||||
* Name | Required | Description
|
||||
* ---------------- | -------- | ---------------------------------------------------------------
|
||||
* host | __YES__ | (_string_) The host of the memcache server, i.e. __localhost__; or __127.0.0.1__; or __memcache.domain.tld__
|
||||
* port | __NO__ | (_integer_) Point to the port where memcached is listening for connections. Set this parameter to 0 when using UNIX domain sockets. Default __11211__
|
||||
* persistent | __NO__ | (_boolean_) Controls the use of a persistent connection. Default __TRUE__
|
||||
* weight | __NO__ | (_integer_) Number of buckets to create for this server which in turn control its probability of it being selected. The probability is relative to the total weight of all servers. Default __1__
|
||||
* timeout | __NO__ | (_integer_) Value in seconds which will be used for connecting to the daemon. Think twice before changing the default value of 1 second - you can lose all the advantages of caching if your connection is too slow. Default __1__
|
||||
* retry_interval | __NO__ | (_integer_) Controls how often a failed server will be retried, the default value is 15 seconds. Setting this parameter to -1 disables automatic retry. Default __15__
|
||||
* status | __NO__ | (_boolean_) Controls if the server should be flagged as online. Default __TRUE__
|
||||
* failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback)_) Allows the user to specify a callback function to run upon encountering an error. The callback is run before failover is attempted. The function takes two parameters, the hostname and port of the failed server. Default __NULL__
|
||||
*
|
||||
* ### System requirements
|
||||
*
|
||||
* * Kohana 3.0.x
|
||||
* * PHP 5.2.4 or greater
|
||||
* * Memcache (plus Memcached-tags for native tagging support)
|
||||
* * Zlib
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @version 2.0
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Cache_Memcache extends Cache {
|
||||
|
||||
// Memcache has a maximum cache lifetime of 30 days
|
||||
const CACHE_CEILING = 2592000;
|
||||
|
||||
/**
|
||||
* Memcache resource
|
||||
*
|
||||
* @var Memcache
|
||||
*/
|
||||
protected $_memcache;
|
||||
|
||||
/**
|
||||
* Flags to use when storing values
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_flags;
|
||||
|
||||
/**
|
||||
* Constructs the memcache Kohana_Cache object
|
||||
*
|
||||
* @param array configuration
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
// Check for the memcache extention
|
||||
if ( ! extension_loaded('memcache'))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Memcache PHP extention not loaded');
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
|
||||
// Setup Memcache
|
||||
$this->_memcache = new Memcache;
|
||||
|
||||
// Load servers from configuration
|
||||
$servers = Arr::get($this->_config, 'servers', NULL);
|
||||
|
||||
if ( ! $servers)
|
||||
{
|
||||
// Throw an exception if no server found
|
||||
throw new Kohana_Cache_Exception('No Memcache servers defined in configuration');
|
||||
}
|
||||
|
||||
// Setup default server configuration
|
||||
$config = array(
|
||||
'host' => 'localhost',
|
||||
'port' => 11211,
|
||||
'persistent' => FALSE,
|
||||
'weight' => 1,
|
||||
'timeout' => 1,
|
||||
'retry_interval' => 15,
|
||||
'status' => TRUE,
|
||||
'failure_callback' => array($this, '_failed_request'),
|
||||
);
|
||||
|
||||
// Add the memcache servers to the pool
|
||||
foreach ($servers as $server)
|
||||
{
|
||||
// Merge the defined config with defaults
|
||||
$server += $config;
|
||||
|
||||
if ( ! $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], $server['weight'], $server['timeout'], $server['retry_interval'], $server['status'], $server['failure_callback']))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Memcache could not connect to host \':host\' using port \':port\'', array(':host' => $server['host'], ':port' => $server['port']));
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the flags
|
||||
$this->_flags = Arr::get($this->_config, 'compression', FALSE) ? MEMCACHE_COMPRESSED : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached value entry by id.
|
||||
*
|
||||
* // Retrieve cache entry from memcache group
|
||||
* $data = Cache::instance('memcache')->get('foo');
|
||||
*
|
||||
* // Retrieve cache entry from memcache group and return 'bar' if miss
|
||||
* $data = Cache::instance('memcache')->get('foo', 'bar');
|
||||
*
|
||||
* @param string id of cache to entry
|
||||
* @param string default value to return if cache miss
|
||||
* @return mixed
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function get($id, $default = NULL)
|
||||
{
|
||||
// Get the value from Memcache
|
||||
$value = $this->_memcache->get($this->_sanitize_id($id));
|
||||
|
||||
// If the value wasn't found, normalise it
|
||||
if ($value === FALSE)
|
||||
{
|
||||
$value = (NULL === $default) ? NULL : $default;
|
||||
}
|
||||
|
||||
// Return the value
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value to cache with id and lifetime
|
||||
*
|
||||
* $data = 'bar';
|
||||
*
|
||||
* // Set 'bar' to 'foo' in memcache group for 10 minutes
|
||||
* if (Cache::instance('memcache')->set('foo', $data, 600))
|
||||
* {
|
||||
* // Cache was set successfully
|
||||
* return
|
||||
* }
|
||||
*
|
||||
* @param string id of cache entry
|
||||
* @param mixed data to set to cache
|
||||
* @param integer lifetime in seconds, maximum value 2592000
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($id, $data, $lifetime = 3600)
|
||||
{
|
||||
// If the lifetime is greater than the ceiling
|
||||
if ($lifetime > Cache_Memcache::CACHE_CEILING)
|
||||
{
|
||||
// Set the lifetime to maximum cache time
|
||||
$lifetime = Cache_Memcache::CACHE_CEILING + time();
|
||||
}
|
||||
// Else if the lifetime is greater than zero
|
||||
elseif ($lifetime > 0)
|
||||
{
|
||||
$lifetime += time();
|
||||
}
|
||||
// Else
|
||||
else
|
||||
{
|
||||
// Normalise the lifetime
|
||||
$lifetime = 0;
|
||||
}
|
||||
|
||||
// Set the data to memcache
|
||||
return $this->_memcache->set($this->_sanitize_id($id), $data, $this->_flags, $lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cache entry based on id
|
||||
*
|
||||
* // Delete the 'foo' cache entry immediately
|
||||
* Cache::instance('memcache')->delete('foo');
|
||||
*
|
||||
* // Delete the 'bar' cache entry after 30 seconds
|
||||
* Cache::instance('memcache')->delete('bar', 30);
|
||||
*
|
||||
* @param string id of entry to delete
|
||||
* @param integer timeout of entry, if zero item is deleted immediately, otherwise the item will delete after the specified value in seconds
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($id, $timeout = 0)
|
||||
{
|
||||
// Delete the id
|
||||
return $this->_memcache->delete($this->_sanitize_id($id), $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cache entries.
|
||||
*
|
||||
* Beware of using this method when
|
||||
* using shared memory cache systems, as it will wipe every
|
||||
* entry within the system for all clients.
|
||||
*
|
||||
* // Delete all cache entries in the default group
|
||||
* Cache::instance('memcache')->delete_all();
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
$result = $this->_memcache->flush();
|
||||
|
||||
// We must sleep after flushing, or overwriting will not work!
|
||||
// @see http://php.net/manual/en/function.memcache-flush.php#81420
|
||||
sleep(1);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method for Memcache::failure_callback to use if any Memcache call
|
||||
* on a particular server fails. This method switches off that instance of the
|
||||
* server if the configuration setting `instant_death` is set to `TRUE`.
|
||||
*
|
||||
* @param string hostname
|
||||
* @param integer port
|
||||
* @return void|boolean
|
||||
* @since 3.0.8
|
||||
*/
|
||||
protected function _failed_request($hostname, $port)
|
||||
{
|
||||
if ( ! $this->_config['instant_death'])
|
||||
return;
|
||||
|
||||
// Setup non-existent host
|
||||
$host = FALSE;
|
||||
|
||||
// Get host settings from configuration
|
||||
foreach ($this->_config['servers'] as $server)
|
||||
{
|
||||
if ($hostname == $server['host'] and $port == $server['port'])
|
||||
{
|
||||
$host = $server;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $host)
|
||||
return;
|
||||
else
|
||||
{
|
||||
return $this->_memcache->setServerParams(
|
||||
$host['host'],
|
||||
$host['port'],
|
||||
$host['timeout'],
|
||||
$host['retry_interval'],
|
||||
FALSE,
|
||||
array($this, '_failed_request'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
76
includes/kohana/modules/cache/classes/kohana/cache/memcachetag.php
vendored
Normal file
76
includes/kohana/modules/cache/classes/kohana/cache/memcachetag.php
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* See [Kohana_Cache_Memcache]
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @version 2.0
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Cache_MemcacheTag extends Cache_Memcache implements Kohana_Cache_Tagging {
|
||||
|
||||
/**
|
||||
* Constructs the memcache object
|
||||
*
|
||||
* @param array configuration
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
if ( ! method_exists($this->_memcache, 'tag_add'))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Memcached-tags PHP plugin not present. Please see http://code.google.com/p/memcached-tags/ for more information');
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value based on an id with tags
|
||||
*
|
||||
* @param string id
|
||||
* @param mixed data
|
||||
* @param integer lifetime [Optional]
|
||||
* @param array tags [Optional]
|
||||
* @return boolean
|
||||
*/
|
||||
public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL)
|
||||
{
|
||||
$result = $this->set($id, $data, $lifetime);
|
||||
|
||||
if ($result and $tags)
|
||||
{
|
||||
foreach ($tags as $tag)
|
||||
{
|
||||
$this->_memcache->tag_add($tag, $id);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache entries based on a tag
|
||||
*
|
||||
* @param string tag
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete_tag($tag)
|
||||
{
|
||||
return $this->_memcache->tag_delete($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find cache entries based on a tag
|
||||
*
|
||||
* @param string tag
|
||||
* @return void
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function find($tag)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Memcached-tags does not support finding by tag');
|
||||
}
|
||||
}
|
336
includes/kohana/modules/cache/classes/kohana/cache/sqlite.php
vendored
Normal file
336
includes/kohana/modules/cache/classes/kohana/cache/sqlite.php
vendored
Normal file
@ -0,0 +1,336 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Kohana Cache Sqlite Driver
|
||||
*
|
||||
* Requires SQLite3 and PDO
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Cache_Sqlite extends Cache implements Kohana_Cache_Tagging, Kohana_Cache_GarbageCollect {
|
||||
|
||||
/**
|
||||
* Database resource
|
||||
*
|
||||
* @var PDO
|
||||
*/
|
||||
protected $_db;
|
||||
|
||||
/**
|
||||
* Sets up the PDO SQLite table and
|
||||
* initialises the PDO connection
|
||||
*
|
||||
* @param array configuration
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
$database = Arr::get($this->_config, 'database', NULL);
|
||||
|
||||
if ($database === NULL)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Database path not available in Kohana Cache configuration');
|
||||
}
|
||||
|
||||
// Load new Sqlite DB
|
||||
$this->_db = new PDO('sqlite:'.$database);
|
||||
|
||||
// Test for existing DB
|
||||
$result = $this->_db->query("SELECT * FROM sqlite_master WHERE name = 'caches' AND type = 'table'")->fetchAll();
|
||||
|
||||
// If there is no table, create a new one
|
||||
if (0 == count($result))
|
||||
{
|
||||
$database_schema = Arr::get($this->_config, 'schema', NULL);
|
||||
|
||||
if ($database_schema === NULL)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Database schema not found in Kohana Cache configuration');
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create the caches table
|
||||
$this->_db->query(Arr::get($this->_config, 'schema', NULL));
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('Failed to create new SQLite caches table with the following error : :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value based on an id
|
||||
*
|
||||
* @param string id
|
||||
* @param string default [Optional] Default value to return if id not found
|
||||
* @return mixed
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function get($id, $default = NULL)
|
||||
{
|
||||
// Prepare statement
|
||||
$statement = $this->_db->prepare('SELECT id, expiration, cache FROM caches WHERE id = :id LIMIT 0, 1');
|
||||
|
||||
// Try and load the cache based on id
|
||||
try
|
||||
{
|
||||
$statement->execute(array(':id' => $this->_sanitize_id($id)));
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
|
||||
if ( ! $result = $statement->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
// If the cache has expired
|
||||
if ($result->expiration != 0 and $result->expiration <= time())
|
||||
{
|
||||
// Delete it and return default value
|
||||
$this->delete($id);
|
||||
return $default;
|
||||
}
|
||||
// Otherwise return cached object
|
||||
else
|
||||
{
|
||||
// Disable notices for unserializing
|
||||
$ER = error_reporting(~E_NOTICE);
|
||||
|
||||
// Return the valid cache data
|
||||
$data = unserialize($result->cache);
|
||||
|
||||
// Turn notices back on
|
||||
error_reporting($ER);
|
||||
|
||||
// Return the resulting data
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value based on an id. Optionally add tags.
|
||||
*
|
||||
* @param string id
|
||||
* @param mixed data
|
||||
* @param integer lifetime [Optional]
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($id, $data, $lifetime = NULL)
|
||||
{
|
||||
return (bool) $this->set_with_tags($id, $data, $lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cache entry based on id
|
||||
*
|
||||
* @param string id
|
||||
* @param integer timeout [Optional]
|
||||
* @return boolean
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
// Prepare statement
|
||||
$statement = $this->_db->prepare('DELETE FROM caches WHERE id = :id');
|
||||
|
||||
// Remove the entry
|
||||
try
|
||||
{
|
||||
$statement->execute(array(':id' => $this->_sanitize_id($id)));
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
|
||||
return (bool) $statement->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cache entries
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
// Prepare statement
|
||||
$statement = $this->_db->prepare('DELETE FROM caches');
|
||||
|
||||
// Remove the entry
|
||||
try
|
||||
{
|
||||
$statement->execute();
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
|
||||
return (bool) $statement->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value based on an id. Optionally add tags.
|
||||
*
|
||||
* @param string id
|
||||
* @param mixed data
|
||||
* @param integer lifetime [Optional]
|
||||
* @param array tags [Optional]
|
||||
* @return boolean
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL)
|
||||
{
|
||||
// Serialize the data
|
||||
$data = serialize($data);
|
||||
|
||||
// Normalise tags
|
||||
$tags = (NULL === $tags) ? NULL : '<'.implode('>,<', $tags).'>';
|
||||
|
||||
// Setup lifetime
|
||||
if ($lifetime === NULL)
|
||||
{
|
||||
$lifetime = (0 === Arr::get('default_expire', NULL)) ? 0 : Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE) + time();
|
||||
}
|
||||
else
|
||||
{
|
||||
$lifetime = (0 === $lifetime) ? 0 : $lifetime + time();
|
||||
}
|
||||
|
||||
// Prepare statement
|
||||
// $this->exists() may throw Kohana_Cache_Exception, no need to catch/rethrow
|
||||
$statement = $this->exists($id) ? $this->_db->prepare('UPDATE caches SET expiration = :expiration, cache = :cache, tags = :tags WHERE id = :id') : $this->_db->prepare('INSERT INTO caches (id, cache, expiration, tags) VALUES (:id, :cache, :expiration, :tags)');
|
||||
|
||||
// Try to insert
|
||||
try
|
||||
{
|
||||
$statement->execute(array(':id' => $this->_sanitize_id($id), ':cache' => $data, ':expiration' => $lifetime, ':tags' => $tags));
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
|
||||
return (bool) $statement->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache entries based on a tag
|
||||
*
|
||||
* @param string tag
|
||||
* @param integer timeout [Optional]
|
||||
* @return boolean
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function delete_tag($tag)
|
||||
{
|
||||
// Prepare the statement
|
||||
$statement = $this->_db->prepare('DELETE FROM caches WHERE tags LIKE :tag');
|
||||
|
||||
// Try to delete
|
||||
try
|
||||
{
|
||||
$statement->execute(array(':tag' => "%<{$tag}>%"));
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
|
||||
return (bool) $statement->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find cache entries based on a tag
|
||||
*
|
||||
* @param string tag
|
||||
* @return array
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
public function find($tag)
|
||||
{
|
||||
// Prepare the statement
|
||||
$statement = $this->_db->prepare('SELECT id, cache FROM caches WHERE tags LIKE :tag');
|
||||
|
||||
// Try to find
|
||||
try
|
||||
{
|
||||
if ( ! $statement->execute(array(':tag' => "%<{$tag}>%")))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
while ($row = $statement->fetchObject())
|
||||
{
|
||||
// Disable notices for unserializing
|
||||
$ER = error_reporting(~E_NOTICE);
|
||||
|
||||
$result[$row->id] = unserialize($row->cache);
|
||||
|
||||
// Turn notices back on
|
||||
error_reporting($ER);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collection method that cleans any expired
|
||||
* cache entries from the cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function garbage_collect()
|
||||
{
|
||||
// Create the sequel statement
|
||||
$statement = $this->_db->prepare('DELETE FROM caches WHERE expiration < :expiration');
|
||||
|
||||
try
|
||||
{
|
||||
$statement->execute(array(':expiration' => time()));
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an id exists or not
|
||||
*
|
||||
* @param string id
|
||||
* @return boolean
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function exists($id)
|
||||
{
|
||||
$statement = $this->_db->prepare('SELECT id FROM caches WHERE id = :id');
|
||||
try
|
||||
{
|
||||
$statement->execute(array(':id' => $this->_sanitize_id($id)));
|
||||
}
|
||||
catch (PDOExeption $e)
|
||||
{
|
||||
throw new Kohana_Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
|
||||
}
|
||||
|
||||
return (bool) $statement->fetchAll();
|
||||
}
|
||||
}
|
42
includes/kohana/modules/cache/classes/kohana/cache/tagging.php
vendored
Normal file
42
includes/kohana/modules/cache/classes/kohana/cache/tagging.php
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Kohana Cache Tagging Interface
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
interface Kohana_Cache_Tagging {
|
||||
|
||||
/**
|
||||
* Set a value based on an id. Optionally add tags.
|
||||
*
|
||||
* Note : Some caching engines do not support
|
||||
* tagging
|
||||
*
|
||||
* @param string id
|
||||
* @param mixed data
|
||||
* @param integer lifetime [Optional]
|
||||
* @param array tags [Optional]
|
||||
* @return boolean
|
||||
*/
|
||||
public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL);
|
||||
|
||||
/**
|
||||
* Delete cache entries based on a tag
|
||||
*
|
||||
* @param string tag
|
||||
* @param integer timeout [Optional]
|
||||
*/
|
||||
public function delete_tag($tag);
|
||||
|
||||
/**
|
||||
* Find cache entries based on a tag
|
||||
*
|
||||
* @param string tag
|
||||
* @return array
|
||||
*/
|
||||
public function find($tag);
|
||||
}
|
84
includes/kohana/modules/cache/classes/kohana/cache/xcache.php
vendored
Normal file
84
includes/kohana/modules/cache/classes/kohana/cache/xcache.php
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Kohana Cache Xcache Driver
|
||||
*
|
||||
* Requires Xcache
|
||||
* http://xcache.lighttpd.net/
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Cache
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Cache_Xcache extends Cache {
|
||||
|
||||
/**
|
||||
* Check for existence of the APC extension
|
||||
*
|
||||
* @param array configuration
|
||||
* @throws Kohana_Cache_Exception
|
||||
*/
|
||||
protected function __construct(array $config)
|
||||
{
|
||||
if ( ! extension_loaded('xcache'))
|
||||
{
|
||||
throw new Kohana_Cache_Exception('PHP Xcache extension is not available.');
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value based on an id
|
||||
*
|
||||
* @param string id
|
||||
* @param string default [Optional] Default value to return if id not found
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($id, $default = NULL)
|
||||
{
|
||||
return (($data = xcache_get($this->_sanitize_id($id))) === NULL) ? $default : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value based on an id. Optionally add tags.
|
||||
*
|
||||
* @param string id
|
||||
* @param string data
|
||||
* @param integer lifetime [Optional]
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($id, $data, $lifetime = NULL)
|
||||
{
|
||||
if (NULL === $lifetime)
|
||||
{
|
||||
$lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
|
||||
}
|
||||
|
||||
return xcache_set($this->_sanitize_id($id), $data, $lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a cache entry based on id
|
||||
*
|
||||
* @param string id
|
||||
* @param integer timeout [Optional]
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
return xcache_unset($this->_sanitize_id($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cache entries
|
||||
* To use this method xcache.admin.enable_auth has to be Off in xcache.ini
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
xcache_clear_cache(XC_TYPE_PHP, 0);
|
||||
}
|
||||
}
|
71
includes/kohana/modules/cache/config/cache.php
vendored
Normal file
71
includes/kohana/modules/cache/config/cache.php
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
return array
|
||||
(
|
||||
'memcache' => array
|
||||
(
|
||||
'driver' => 'memcache',
|
||||
'default_expire' => 3600,
|
||||
'compression' => FALSE, // Use Zlib compression (can cause issues with integers)
|
||||
'servers' => array
|
||||
(
|
||||
array
|
||||
(
|
||||
'host' => 'localhost', // Memcache Server
|
||||
'port' => 11211, // Memcache port number
|
||||
'persistent' => FALSE, // Persistent connection
|
||||
'weight' => 1,
|
||||
'timeout' => 1,
|
||||
'retry_interval' => 15,
|
||||
'status' => TRUE,
|
||||
),
|
||||
),
|
||||
'instant_death' => TRUE, // Take server offline immediately on first fail (no retry)
|
||||
),
|
||||
'memcachetag' => array
|
||||
(
|
||||
'driver' => 'memcachetag',
|
||||
'default_expire' => 3600,
|
||||
'compression' => FALSE, // Use Zlib compression (can cause issues with integers)
|
||||
'servers' => array
|
||||
(
|
||||
array
|
||||
(
|
||||
'host' => 'localhost', // Memcache Server
|
||||
'port' => 11211, // Memcache port number
|
||||
'persistent' => FALSE, // Persistent connection
|
||||
'weight' => 1,
|
||||
'timeout' => 1,
|
||||
'retry_interval' => 15,
|
||||
'status' => TRUE,
|
||||
),
|
||||
),
|
||||
'instant_death' => TRUE,
|
||||
),
|
||||
'apc' => array
|
||||
(
|
||||
'driver' => 'apc',
|
||||
'default_expire' => 3600,
|
||||
),
|
||||
'sqlite' => array
|
||||
(
|
||||
'driver' => 'sqlite',
|
||||
'default_expire' => 3600,
|
||||
'database' => APPPATH.'cache/kohana-cache.sql3',
|
||||
'schema' => 'CREATE TABLE caches(id VARCHAR(127) PRIMARY KEY, tags VARCHAR(255), expiration INTEGER, cache TEXT)',
|
||||
),
|
||||
'eaccelerator' => array
|
||||
(
|
||||
'driver' => 'eaccelerator',
|
||||
),
|
||||
'xcache' => array
|
||||
(
|
||||
'driver' => 'xcache',
|
||||
'default_expire' => 3600,
|
||||
),
|
||||
'file' => array
|
||||
(
|
||||
'driver' => 'file',
|
||||
'cache_dir' => APPPATH.'cache',
|
||||
'default_expire' => 3600,
|
||||
)
|
||||
);
|
59
includes/kohana/modules/cache/guide/cache.about.md
vendored
Normal file
59
includes/kohana/modules/cache/guide/cache.about.md
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
# About Kohana Cache
|
||||
|
||||
[Kohana_Cache] provides a common interface to a variety of caching engines. [Kohana_Cache_Tagging] is
|
||||
supported where available natively to the cache system. Kohana Cache supports multiple
|
||||
instances of cache engines through a grouped singleton pattern.
|
||||
|
||||
## Supported cache engines
|
||||
|
||||
* APC ([Cache_Apc])
|
||||
* eAccelerator ([Cache_Eaccelerator])
|
||||
* File ([Cache_File])
|
||||
* Memcached ([Cache_Memcache])
|
||||
* Memcached-tags ([Cache_Memcachetag])
|
||||
* SQLite ([Cache_Sqlite])
|
||||
* Xcache ([Cache_Xcache])
|
||||
|
||||
## Introduction to caching
|
||||
|
||||
Caching should be implemented with consideration. Generally, caching the result of resources
|
||||
is faster than reprocessing them. Choosing what, how and when to cache is vital. [PHP APC](http://php.net/manual/en/book.apc.php) is one of the fastest caching systems available, closely followed by [Memcached](http://memcached.org/). [SQLite](http://www.sqlite.org/) and File caching are two of the slowest cache methods, however usually faster than reprocessing
|
||||
a complex set of instructions.
|
||||
|
||||
Caching engines that use memory are considerably faster than file based alternatives. But
|
||||
memory is limited whereas disk space is plentiful. If caching large datasets, such as large database result sets, it is best to use file caching.
|
||||
|
||||
[!!] Cache drivers require the relevant PHP extensions to be installed. APC, eAccelerator, Memecached and Xcache all require non-standard PHP extensions.
|
||||
|
||||
## What the Kohana Cache module does (and does not do)
|
||||
|
||||
This module provides a simple abstracted interface to a wide selection of popular PHP cache engines. The caching API provides the basic caching methods implemented across all solutions, memory, network or disk based. Basic key / value storing is supported by all drivers, with additional tagging and garbage collection support where implemented or required.
|
||||
|
||||
_Kohana Cache_ does not provide HTTP style caching for clients (web browsers) and/or proxies (_Varnish_, _Squid_). There are other Kohana modules that provide this functionality.
|
||||
|
||||
## Choosing a cache provider
|
||||
|
||||
Getting and setting values to cache is very simple when using the _Kohana Cache_ interface. The hardest choice is choosing which cache engine to use. When choosing a caching engine, the following criteria must be considered:
|
||||
|
||||
1. __Does the cache need to be distributed?__
|
||||
This is an important consideration as it will severely limit the options available to solutions such as Memcache when a distributed solution is required.
|
||||
2. __Does the cache need to be fast?__
|
||||
In almost all cases retrieving data from a cache is faster than execution. However generally memory based caching is considerably faster than disk based caching (see table below).
|
||||
3. __How much cache is required?__
|
||||
Cache is not endless, and memory based caches are subject to a considerably more limited storage resource.
|
||||
|
||||
Driver | Storage | Speed | Tags | Distributed | Automatic Garbage Collection | Notes
|
||||
---------------- | ------------ | --------- | -------- | ----------- | ---------------------------- | -----------------------
|
||||
APC | __Memory__ | Excellent | No | No | Yes | Widely available PHP opcode caching solution, improves php execution performance
|
||||
eAccelerator | __Memory__ | Excellent | No | No | Yes | Limited support and no longer developed. Included for legacy systems
|
||||
File | __Disk__ | Poor | No | No | No | Marginally faster than execution
|
||||
Memcache (tag) | __Memory__ | Good | No (yes) | Yes | Yes | Generally fast distributed solution, but has a speed hit due to variable network latency
|
||||
Sqlite | __Disk__ | Poor | Yes | No | No | Marginally faster than execution
|
||||
Xcache | __Memory__ | Excellent | Yes | No | Yes | Very fast memory solution and alternative to APC
|
||||
|
||||
It is possible to have hybrid cache solutions that use a combination of the engines above in different contexts. This is supported with _Kohana Cache_ as well.
|
||||
|
||||
## Minimum requirements
|
||||
|
||||
* Kohana 3.0.4
|
||||
* PHP 5.2.4 or greater
|
168
includes/kohana/modules/cache/guide/cache.config.md
vendored
Normal file
168
includes/kohana/modules/cache/guide/cache.config.md
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
# Kohana Cache configuration
|
||||
|
||||
Kohana Cache uses configuration groups to create cache instances. A configuration group can
|
||||
use any supported driver, with successive groups using multiple instances of the same driver type.
|
||||
|
||||
The default cache group is loaded based on the `Cache::$default` setting. It is set to the `file` driver as standard, however this can be changed within the `/application/boostrap.php` file.
|
||||
|
||||
// Change the default cache driver to memcache
|
||||
Cache::$default = 'memcache';
|
||||
|
||||
// Load the memcache cache driver using default setting
|
||||
$memcache = Cache::instance();
|
||||
|
||||
## Group settings
|
||||
|
||||
Below are the default cache configuration groups for each supported driver. Add to- or override these settings
|
||||
within the `application/config/cache.php` file.
|
||||
|
||||
Name | Required | Description
|
||||
-------------- | -------- | ---------------------------------------------------------------
|
||||
driver | __YES__ | (_string_) The driver type to use
|
||||
default_expire | __NO__ | (_string_) The driver type to use
|
||||
|
||||
|
||||
'file' => array
|
||||
(
|
||||
'driver' => 'file',
|
||||
'cache_dir' => APPPATH.'cache/.kohana_cache',
|
||||
'default_expire' => 3600,
|
||||
),
|
||||
|
||||
## Memcache & Memcached-tag settings
|
||||
|
||||
Name | Required | Description
|
||||
-------------- | -------- | ---------------------------------------------------------------
|
||||
driver | __YES__ | (_string_) The driver type to use
|
||||
servers | __YES__ | (_array_) Associative array of server details, must include a __host__ key. (see _Memcache server configuration_ below)
|
||||
compression | __NO__ | (_boolean_) Use data compression when caching
|
||||
|
||||
### Memcache server configuration
|
||||
|
||||
Name | Required | Description
|
||||
---------------- | -------- | ---------------------------------------------------------------
|
||||
host | __YES__ | (_string_) The host of the memcache server, i.e. __localhost__; or __127.0.0.1__; or __memcache.domain.tld__
|
||||
port | __NO__ | (_integer_) Point to the port where memcached is listening for connections. Set this parameter to 0 when using UNIX domain sockets. Default __11211__
|
||||
persistent | __NO__ | (_boolean_) Controls the use of a persistent connection. Default __TRUE__
|
||||
weight | __NO__ | (_integer_) Number of buckets to create for this server which in turn control its probability of it being selected. The probability is relative to the total weight of all servers. Default __1__
|
||||
timeout | __NO__ | (_integer_) Value in seconds which will be used for connecting to the daemon. Think twice before changing the default value of 1 second - you can lose all the advantages of caching if your connection is too slow. Default __1__
|
||||
retry_interval | __NO__ | (_integer_) Controls how often a failed server will be retried, the default value is 15 seconds. Setting this parameter to -1 disables automatic retry. Default __15__
|
||||
status | __NO__ | (_boolean_) Controls if the server should be flagged as online. Default __TRUE__
|
||||
failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback)_) Allows the user to specify a callback function to run upon encountering an error. The callback is run before failover is attempted. The function takes two parameters, the hostname and port of the failed server. Default __NULL__
|
||||
|
||||
'memcache' => array
|
||||
(
|
||||
'driver' => 'memcache',
|
||||
'default_expire' => 3600,
|
||||
'compression' => FALSE, // Use Zlib compression
|
||||
(can cause issues with integers)
|
||||
'servers' => array
|
||||
(
|
||||
array
|
||||
(
|
||||
'host' => 'localhost', // Memcache Server
|
||||
'port' => 11211, // Memcache port number
|
||||
'persistent' => FALSE, // Persistent connection
|
||||
),
|
||||
),
|
||||
),
|
||||
'memcachetag' => array
|
||||
(
|
||||
'driver' => 'memcachetag',
|
||||
'default_expire' => 3600,
|
||||
'compression' => FALSE, // Use Zlib compression
|
||||
(can cause issues with integers)
|
||||
'servers' => array
|
||||
(
|
||||
array
|
||||
(
|
||||
'host' => 'localhost', // Memcache Server
|
||||
'port' => 11211, // Memcache port number
|
||||
'persistent' => FALSE, // Persistent connection
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
## APC settings
|
||||
|
||||
'apc' => array
|
||||
(
|
||||
'driver' => 'apc',
|
||||
'default_expire' => 3600,
|
||||
),
|
||||
|
||||
## SQLite settings
|
||||
|
||||
'sqlite' => array
|
||||
(
|
||||
'driver' => 'sqlite',
|
||||
'default_expire' => 3600,
|
||||
'database' => APPPATH.'cache/kohana-cache.sql3',
|
||||
'schema' => 'CREATE TABLE caches(id VARCHAR(127) PRIMARY KEY,
|
||||
tags VARCHAR(255), expiration INTEGER, cache TEXT)',
|
||||
),
|
||||
|
||||
## Eaccelerator settings
|
||||
|
||||
'eaccelerator' array
|
||||
(
|
||||
'driver' => 'eaccelerator',
|
||||
),
|
||||
|
||||
## Xcache settings
|
||||
|
||||
'xcache' => array
|
||||
(
|
||||
'driver' => 'xcache',
|
||||
'default_expire' => 3600,
|
||||
),
|
||||
|
||||
## File settings
|
||||
|
||||
'file' => array
|
||||
(
|
||||
'driver' => 'file',
|
||||
'cache_dir' => 'cache/.kohana_cache',
|
||||
'default_expire' => 3600,
|
||||
)
|
||||
|
||||
## Override existing configuration group
|
||||
|
||||
The following example demonstrates how to override an existing configuration setting, using the config file in `/application/config/cache.php`.
|
||||
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
return array
|
||||
(
|
||||
// Override the default configuration
|
||||
'memcache' => array
|
||||
(
|
||||
'driver' => 'memcache', // Use Memcached as the default driver
|
||||
'default_expire' => 8000, // Overide default expiry
|
||||
'servers' => array
|
||||
(
|
||||
// Add a new server
|
||||
array
|
||||
(
|
||||
'host' => 'cache.domain.tld',
|
||||
'port' => 11211,
|
||||
'persistent' => FALSE
|
||||
)
|
||||
),
|
||||
'compression' => FALSE
|
||||
)
|
||||
);
|
||||
|
||||
## Add new configuration group
|
||||
|
||||
The following example demonstrates how to add a new configuration setting, using the config file in `/application/config/cache.php`.
|
||||
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
return array
|
||||
(
|
||||
// Override the default configuration
|
||||
'fastkv' => array
|
||||
(
|
||||
'driver' => 'apc', // Use Memcached as the default driver
|
||||
'default_expire' => 1000, // Overide default expiry
|
||||
)
|
||||
);
|
219
includes/kohana/modules/cache/guide/cache.usage.md
vendored
Normal file
219
includes/kohana/modules/cache/guide/cache.usage.md
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
# Kohana Cache usage
|
||||
|
||||
[Kohana_Cache] provides a simple interface allowing getting, setting and deleting of cached values. Two interfaces included in _Kohana Cache_ additionally provide _tagging_ and _garbage collection_ where they are supported by the respective drivers.
|
||||
|
||||
## Getting a new cache instance
|
||||
|
||||
Creating a new _Kohana Cache_ instance is simple, however it must be done using the [Cache::instance] method, rather than the traditional `new` constructor.
|
||||
|
||||
// Create a new instance of cache using the default group
|
||||
$cache = Cache::instance();
|
||||
|
||||
The default group will use whatever is set to [Cache::$default] and must have a corresponding [configuration](cache.config) definition for that group.
|
||||
|
||||
To create a cache instance using a group other than the _default_, simply provide the group name as an argument.
|
||||
|
||||
// Create a new instance of the memcache group
|
||||
$memcache = Cache::instance('memcache');
|
||||
|
||||
If there is a cache instance already instantiated then you can get it directly from the class member.
|
||||
|
||||
[!!] Beware that this can cause issues if you do not test for the instance before trying to access it.
|
||||
|
||||
// Check for the existance of the cache driver
|
||||
if (isset(Cache::$instances['memcache']))
|
||||
{
|
||||
// Get the existing cache instance directly (faster)
|
||||
$memcache = Cache::$instances['memcache'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the cache driver instance (slower)
|
||||
$memcache = Cache::instance('memcache');
|
||||
}
|
||||
|
||||
## Setting and getting variables to and from cache
|
||||
|
||||
The cache library supports scalar and object values, utilising object serialization where required (or not supported by the caching engine). This means that the majority or objects can be cached without any modification.
|
||||
|
||||
[!!] Serialisation does not work with resource handles, such as filesystem, curl or socket resources.
|
||||
|
||||
### Setting a value to cache
|
||||
|
||||
Setting a value to cache using the [Cache::set] method can be done in one of two ways; either using the Cache instance interface, which is good for atomic operations; or getting an instance and using that for multiple operations.
|
||||
|
||||
The first example demonstrates how to quickly load and set a value to the default cache instance.
|
||||
|
||||
// Create a cachable object
|
||||
$object = new stdClass;
|
||||
|
||||
// Set a property
|
||||
$object->foo = 'bar';
|
||||
|
||||
// Cache the object using default group (quick interface) with default time (3600 seconds)
|
||||
Cache::instance()->set('foo', $object);
|
||||
|
||||
If multiple cache operations are required, it is best to assign an instance of Cache to a variable and use that as below.
|
||||
|
||||
// Set the object using a defined group for a defined time period (30 seconds)
|
||||
$memcache = Cache::instance('memcache');
|
||||
$memcache->set('foo', $object, 30);
|
||||
|
||||
#### Setting a value with tags
|
||||
|
||||
Certain cache drivers support setting values with tags. To set a value to cache with tags using the following interface.
|
||||
|
||||
// Get a cache instance that supports tags
|
||||
$memcache = Cache::instance('memcachetag');
|
||||
|
||||
// Test for tagging interface
|
||||
if ($memcache instanceof Kohana_Cache_Tagging)
|
||||
{
|
||||
// Set a value with some tags for 30 seconds
|
||||
$memcache->set('foo', $object, 30, array('snafu', 'stfu', 'fubar'));
|
||||
}
|
||||
// Otherwise set without tags
|
||||
else
|
||||
{
|
||||
// Set a value for 30 seconds
|
||||
$memcache->set('foo', $object, 30);
|
||||
}
|
||||
|
||||
It is possible to implement custom tagging solutions onto existing or new cache drivers by implementing the [Kohana_Cache_Tagging] interface. Kohana_Cache only applies the interface to drivers that support tagging natively as standard.
|
||||
|
||||
### Getting a value from cache
|
||||
|
||||
Getting variables back from cache is achieved using the [Cache::get] method using a single key to identify the cache entry.
|
||||
|
||||
// Retrieve a value from cache (quickly)
|
||||
$object = Cache::instance()->get('foo');
|
||||
|
||||
In cases where the requested key is not available or the entry has expired, a default value will be returned (__NULL__ by default). It is possible to define the default value as the key is requested.
|
||||
|
||||
// If the cache key is available (with default value set to FALSE)
|
||||
if ($object = Cache::instance()->get('foo', FALSE))
|
||||
{
|
||||
// Do something
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do something else
|
||||
}
|
||||
|
||||
#### Getting values from cache using tags
|
||||
|
||||
It is possible to retrieve values from cache grouped by tag, using the [Cache::find] method with drivers that support tagging.
|
||||
|
||||
[!!] The __Memcachetag__ driver does not support the `Cache::find($tag)` interface and will throw an exception.
|
||||
|
||||
// Get an instance of cache
|
||||
$cache = Cache::instance('memcachetag');
|
||||
|
||||
// Wrap in a try/catch statement to gracefully handle memcachetag
|
||||
try
|
||||
{
|
||||
// Find values based on tag
|
||||
return $cache->find('snafu');
|
||||
}
|
||||
catch (Kohana_Cache_Exception $e)
|
||||
{
|
||||
// Handle gracefully
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
### Deleting values from cache
|
||||
|
||||
Deleting variables is very similar to the getting and setting methods already described. Deleting operations are split into three categories:
|
||||
|
||||
- __Delete value by key__. Deletes a cached value by the associated key.
|
||||
- __Delete all values__. Deletes all caches values stored in the cache instance.
|
||||
- __Delete values by tag__. Deletes all values that have the supplied tag. This is only supported by Memcached-Tag and Sqlite.
|
||||
|
||||
#### Delete value by key
|
||||
|
||||
To delete a specific value by its associated key:
|
||||
|
||||
// If the cache entry for 'foo' is deleted
|
||||
if (Cache::instance()->delete('foo'))
|
||||
{
|
||||
// Cache entry successfully deleted, do something
|
||||
}
|
||||
|
||||
By default a `TRUE` value will be returned. However a `FALSE` value will be returned in instances where the key did not exist in the cache.
|
||||
|
||||
#### Delete all values
|
||||
|
||||
To delete all values in a specific instance:
|
||||
|
||||
// If all cache items where deleted successfully
|
||||
if (Cache::instance()->delete_all())
|
||||
{
|
||||
// Do something
|
||||
}
|
||||
|
||||
It is also possible to delete all cache items in every instance:
|
||||
|
||||
// For each cache instance
|
||||
foreach (Cache::$instances as $group => $instance)
|
||||
{
|
||||
if ($instance->delete_all())
|
||||
{
|
||||
var_dump('instance : '.$group.' has been flushed!');
|
||||
}
|
||||
}
|
||||
|
||||
#### Delete values by tag
|
||||
|
||||
Some of the caching drivers support deleting by tag. This will remove all the cached values that are associated with a specific tag. Below is an example of how to robustly handle deletion by tag.
|
||||
|
||||
// Get cache instance
|
||||
$cache = Cache::instance();
|
||||
|
||||
// Check for tagging interface
|
||||
if ($cache instanceof Kohana_Cache_Tagging)
|
||||
{
|
||||
// Delete all entries by the tag 'snafu'
|
||||
$cache->delete_tag('snafu');
|
||||
}
|
||||
|
||||
#### Garbage Collection
|
||||
|
||||
Garbage Collection (GC) is the cleaning of expired cache entries. For the most part, caching engines will take care of garbage collection internally. However a few of the file based systems do not handle this task and in these circumstances it would be prudent to garbage collect at a predetermined frequency. If no garbage collection is executed, the resource storing the cache entries will eventually fill and become unusable.
|
||||
|
||||
When not automated, garbage collection is the responsibility of the developer. It is prudent to have a GC probability value that dictates how likely the garbage collection routing will be run. An example of such a system is demonstrated below.
|
||||
|
||||
// Get a cache instance
|
||||
$cache_file = Cache::instance('file');
|
||||
|
||||
// Set a GC probability of 10%
|
||||
$gc = 10;
|
||||
|
||||
// If the GC probability is a hit
|
||||
if (rand(0,99) <= $gc and $cache_file instanceof Kohana_Cache_GarbageCollect)
|
||||
{
|
||||
// Garbage Collect
|
||||
$cache_file->garbage_collect();
|
||||
}
|
||||
|
||||
# Interfaces
|
||||
|
||||
Kohana Cache comes with two interfaces that are implemented where the drivers support them:
|
||||
|
||||
- __[Kohana_Cache_Tagging] for tagging support on cache entries__
|
||||
- [Cache_MemcacheTag]
|
||||
- [Cache_Sqlite]
|
||||
- __[Kohana_Cache_GarbageCollect] for garbage collection with drivers without native support__
|
||||
- [Cache_File]
|
||||
- [Cache_Sqlite]
|
||||
|
||||
When using interface specific caching features, ensure that code checks for the required interface before using the methods supplied. The following example checks whether the garbage collection interface is available before calling the `garbage_collect` method.
|
||||
|
||||
// Create a cache instance
|
||||
$cache = Cache::instance();
|
||||
|
||||
// Test for Garbage Collection
|
||||
if ($cache instanceof Kohana_Cache_GarbageCollect)
|
||||
{
|
||||
// Collect garbage
|
||||
$cache->garbage_collect();
|
||||
}
|
4
includes/kohana/modules/cache/guide/menu.cache.md
vendored
Normal file
4
includes/kohana/modules/cache/guide/menu.cache.md
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
1. **Cache**
|
||||
- [About](cache.about)
|
||||
- [Configuration](cache.config)
|
||||
- [Usage](cache.usage)
|
91
includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php
vendored
Normal file
91
includes/kohana/modules/cache/tests/cache/KohanaCacheTest.php
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
class KohanaCacheTest extends PHPUnit_Framework_TestCase {
|
||||
|
||||
static protected $test_instance;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
self::$test_instance = Cache::instance('file');
|
||||
self::$test_instance->delete_all();
|
||||
|
||||
self::$test_instance->set('testGet1', 'foo', 3600);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
self::$test_instance->delete_all();
|
||||
self::$test_instance = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the cache static instance method
|
||||
*/
|
||||
public function testInstance()
|
||||
{
|
||||
$file_instance = Cache::instance('file');
|
||||
$file_instance2 = Cache::instance('file');
|
||||
|
||||
// Try and load a Cache instance
|
||||
$this->assertType('Kohana_Cache', Cache::instance());
|
||||
$this->assertType('Kohana_Cache_File', $file_instance);
|
||||
|
||||
// Test instances are only initialised once
|
||||
$this->assertTrue(spl_object_hash($file_instance) == spl_object_hash($file_instance2));
|
||||
|
||||
// Test the publically accessible Cache instance store
|
||||
$this->assertTrue(spl_object_hash(Cache::$instances['file']) == spl_object_hash($file_instance));
|
||||
|
||||
// Get the constructor method
|
||||
$constructorMethod = new ReflectionMethod($file_instance, '__construct');
|
||||
|
||||
// Test the constructor for hidden visibility
|
||||
$this->assertTrue($constructorMethod->isProtected(), '__construct is does not have protected visibility');
|
||||
}
|
||||
|
||||
public function testGet()
|
||||
{
|
||||
// Try and get a non property
|
||||
$this->assertNull(self::$test_instance->get('testGet0'));
|
||||
|
||||
// Try and get a non property with default return value
|
||||
$this->assertEquals('bar', self::$test_instance->get('testGet0', 'bar'));
|
||||
|
||||
// Try and get a real cached property
|
||||
$this->assertEquals('foo', self::$test_instance->get('testGet1'));
|
||||
}
|
||||
|
||||
public function testSet()
|
||||
{
|
||||
$value = 'foobar';
|
||||
$value2 = 'snafu';
|
||||
|
||||
// Set a new property
|
||||
$this->assertTrue(self::$test_instance->set('testSet1', $value));
|
||||
|
||||
// Test the property exists
|
||||
$this->assertEquals(self::$test_instance->get('testSet1'), $value);
|
||||
|
||||
// Test short set
|
||||
$this->assertTrue(self::$test_instance->set('testSet2', $value2, 3));
|
||||
|
||||
// Test the property exists
|
||||
$this->assertEquals(self::$test_instance->get('testSet2'), $value2);
|
||||
|
||||
// Allow test2 to expire
|
||||
sleep(4);
|
||||
|
||||
// Test the property has expired
|
||||
$this->assertNull(self::$test_instance->get('testSet2'));
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function testDeleteAll()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
16
includes/kohana/modules/cache/tests/phpunit.xml
vendored
Normal file
16
includes/kohana/modules/cache/tests/phpunit.xml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<!--
|
||||
This is an example phpunit.xml file to get you started
|
||||
Copy it to a directory, update the relative paths and rename to phpunit.xml
|
||||
Then to run tests cd into it's directory and just run
|
||||
phpunit
|
||||
(it'll automatically use any phpunit.xml file in the current directory)
|
||||
|
||||
Any options you specify when calling phpunit will override the ones in here
|
||||
-->
|
||||
<phpunit colors="true" bootstrap="../../../articles/public/index.php">
|
||||
<testsuites>
|
||||
<testsuite name="Kohana Cache Tests">
|
||||
<directory suffix=".php">cache/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
@ -0,0 +1,57 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ArrCallback extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Parsing <em>command[param,param]</em> strings in <code>Arr::callback()</code>:
|
||||
http://github.com/shadowhand/kohana/commit/c3aaae849164bf92a486e29e736a265b350cb4da#L0R127';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid callback strings
|
||||
'foo',
|
||||
'foo::bar',
|
||||
'foo[apple,orange]',
|
||||
'foo::bar[apple,orange]',
|
||||
'[apple,orange]', // no command, only params
|
||||
'foo[[apple],[orange]]', // params with brackets inside
|
||||
|
||||
// Invalid callback strings
|
||||
'foo[apple,orange', // no closing bracket
|
||||
);
|
||||
|
||||
public function bench_shadowhand($subject)
|
||||
{
|
||||
// The original regex we're trying to optimize
|
||||
if (preg_match('/([^\[]*+)\[(.*)\]/', $subject, $match))
|
||||
return $match;
|
||||
}
|
||||
|
||||
public function bench_geert_regex_1($subject)
|
||||
{
|
||||
// Added ^ and $ around the whole pattern
|
||||
if (preg_match('/^([^\[]*+)\[(.*)\]$/', $subject, $matches))
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_geert_regex_2($subject)
|
||||
{
|
||||
// A rather experimental approach using \K which requires PCRE 7.2 ~ PHP 5.2.4
|
||||
// Note: $matches[0] = params, $matches[1] = command
|
||||
if (preg_match('/^([^\[]*+)\[\K.*(?=\]$)/', $subject, $matches))
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_geert_str($subject)
|
||||
{
|
||||
// A native string function approach which beats all the regexes
|
||||
if (strpos($subject, '[') !== FALSE AND substr($subject, -1) === ']')
|
||||
return explode('[', substr($subject, 0, -1), 2);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_AutoLinkEmails extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Fixing <a href="http://dev.kohanaphp.com/issues/2772">#2772</a>, and comparing some possibilities.';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'<ul>
|
||||
<li>voorzitter@xxxx.com</li>
|
||||
<li>vicevoorzitter@xxxx.com</li>
|
||||
</ul>',
|
||||
);
|
||||
|
||||
// The original function, with str_replace replaced by preg_replace. Looks clean.
|
||||
public function bench_match_all_loop($subject)
|
||||
{
|
||||
if (preg_match_all('~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i', $subject, $matches))
|
||||
{
|
||||
foreach ($matches[0] as $match)
|
||||
{
|
||||
$subject = preg_replace('!\b'.preg_quote($match).'\b!', HTML::mailto($match), $subject);
|
||||
}
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
// The "e" stands for "eval", hmm... Ugly and slow because it needs to reinterpret the PHP code upon each match.
|
||||
public function bench_replace_e($subject)
|
||||
{
|
||||
return preg_replace(
|
||||
'~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~ie',
|
||||
'HTML::mailto("$0")', // Yuck!
|
||||
$subject
|
||||
);
|
||||
}
|
||||
|
||||
// This one should be quite okay, it just requires an otherwise useless single-purpose callback.
|
||||
public function bench_replace_callback_external($subject)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i',
|
||||
array($this, '_callback_external'),
|
||||
$subject
|
||||
);
|
||||
}
|
||||
protected function _callback_external($matches)
|
||||
{
|
||||
return HTML::mailto($matches[0]);
|
||||
}
|
||||
|
||||
// This one clearly is the ugliest, the slowest and consumes a lot of memory!
|
||||
public function bench_replace_callback_internal($subject)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i',
|
||||
create_function('$matches', 'return HTML::mailto($matches[0]);'), // Yuck!
|
||||
$subject
|
||||
);
|
||||
}
|
||||
|
||||
}
|
186
includes/kohana/modules/codebench/classes/bench/datespan.php
Normal file
186
includes/kohana/modules/codebench/classes/bench/datespan.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Woody Gilk <woody.gilk@kohanaphp.com>
|
||||
*/
|
||||
class Bench_DateSpan extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for <code>Date::span()</code>.';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->subjects = array(
|
||||
time(),
|
||||
time() - Date::MONTH,
|
||||
time() - Date::YEAR,
|
||||
time() - Date::YEAR * 10,
|
||||
);
|
||||
}
|
||||
|
||||
// Original method
|
||||
public static function bench_span_original($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
|
||||
{
|
||||
// Array with the output formats
|
||||
$output = preg_split('/[^a-z]+/', strtolower((string) $output));
|
||||
|
||||
// Invalid output
|
||||
if (empty($output))
|
||||
return FALSE;
|
||||
|
||||
// Make the output values into keys
|
||||
extract(array_flip($output), EXTR_SKIP);
|
||||
|
||||
if ($local === NULL)
|
||||
{
|
||||
// Calculate the span from the current time
|
||||
$local = time();
|
||||
}
|
||||
|
||||
// Calculate timespan (seconds)
|
||||
$timespan = abs($remote - $local);
|
||||
|
||||
if (isset($years))
|
||||
{
|
||||
$timespan -= Date::YEAR * ($years = (int) floor($timespan / Date::YEAR));
|
||||
}
|
||||
|
||||
if (isset($months))
|
||||
{
|
||||
$timespan -= Date::MONTH * ($months = (int) floor($timespan / Date::MONTH));
|
||||
}
|
||||
|
||||
if (isset($weeks))
|
||||
{
|
||||
$timespan -= Date::WEEK * ($weeks = (int) floor($timespan / Date::WEEK));
|
||||
}
|
||||
|
||||
if (isset($days))
|
||||
{
|
||||
$timespan -= Date::DAY * ($days = (int) floor($timespan / Date::DAY));
|
||||
}
|
||||
|
||||
if (isset($hours))
|
||||
{
|
||||
$timespan -= Date::HOUR * ($hours = (int) floor($timespan / Date::HOUR));
|
||||
}
|
||||
|
||||
if (isset($minutes))
|
||||
{
|
||||
$timespan -= Date::MINUTE * ($minutes = (int) floor($timespan / Date::MINUTE));
|
||||
}
|
||||
|
||||
// Seconds ago, 1
|
||||
if (isset($seconds))
|
||||
{
|
||||
$seconds = $timespan;
|
||||
}
|
||||
|
||||
// Remove the variables that cannot be accessed
|
||||
unset($timespan, $remote, $local);
|
||||
|
||||
// Deny access to these variables
|
||||
$deny = array_flip(array('deny', 'key', 'difference', 'output'));
|
||||
|
||||
// Return the difference
|
||||
$difference = array();
|
||||
foreach ($output as $key)
|
||||
{
|
||||
if (isset($$key) AND ! isset($deny[$key]))
|
||||
{
|
||||
// Add requested key to the output
|
||||
$difference[$key] = $$key;
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid output formats string
|
||||
if (empty($difference))
|
||||
return FALSE;
|
||||
|
||||
// If only one output format was asked, don't put it in an array
|
||||
if (count($difference) === 1)
|
||||
return current($difference);
|
||||
|
||||
// Return array
|
||||
return $difference;
|
||||
}
|
||||
|
||||
// Using an array for the output
|
||||
public static function bench_span_use_array($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
|
||||
{
|
||||
// Array with the output formats
|
||||
$output = preg_split('/[^a-z]+/', strtolower((string) $output));
|
||||
|
||||
// Invalid output
|
||||
if (empty($output))
|
||||
return FALSE;
|
||||
|
||||
// Convert the list of outputs to an associative array
|
||||
$output = array_combine($output, array_fill(0, count($output), 0));
|
||||
|
||||
// Make the output values into keys
|
||||
extract(array_flip($output), EXTR_SKIP);
|
||||
|
||||
if ($local === NULL)
|
||||
{
|
||||
// Calculate the span from the current time
|
||||
$local = time();
|
||||
}
|
||||
|
||||
// Calculate timespan (seconds)
|
||||
$timespan = abs($remote - $local);
|
||||
|
||||
if (isset($output['years']))
|
||||
{
|
||||
$timespan -= Date::YEAR * ($output['years'] = (int) floor($timespan / Date::YEAR));
|
||||
}
|
||||
|
||||
if (isset($output['months']))
|
||||
{
|
||||
$timespan -= Date::MONTH * ($output['months'] = (int) floor($timespan / Date::MONTH));
|
||||
}
|
||||
|
||||
if (isset($output['weeks']))
|
||||
{
|
||||
$timespan -= Date::WEEK * ($output['weeks'] = (int) floor($timespan / Date::WEEK));
|
||||
}
|
||||
|
||||
if (isset($output['days']))
|
||||
{
|
||||
$timespan -= Date::DAY * ($output['days'] = (int) floor($timespan / Date::DAY));
|
||||
}
|
||||
|
||||
if (isset($output['hours']))
|
||||
{
|
||||
$timespan -= Date::HOUR * ($output['hours'] = (int) floor($timespan / Date::HOUR));
|
||||
}
|
||||
|
||||
if (isset($output['minutes']))
|
||||
{
|
||||
$timespan -= Date::MINUTE * ($output['minutes'] = (int) floor($timespan / Date::MINUTE));
|
||||
}
|
||||
|
||||
// Seconds ago, 1
|
||||
if (isset($output['seconds']))
|
||||
{
|
||||
$output['seconds'] = $timespan;
|
||||
}
|
||||
|
||||
if (count($output) === 1)
|
||||
{
|
||||
// Only a single output was requested, return it
|
||||
return array_pop($output);
|
||||
}
|
||||
|
||||
// Return array
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ExplodeLimit extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Having a look at the effect of adding a limit to the <a href="http://php.net/explode">explode</a> function.<br />
|
||||
http://stackoverflow.com/questions/1308149/how-to-get-a-part-of-url-between-4th-and-5th-slashes';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'http://example.com/articles/123a/view',
|
||||
'http://example.com/articles/123a/view/x/x/x/x/x',
|
||||
'http://example.com/articles/123a/view/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x',
|
||||
);
|
||||
|
||||
public function bench_explode_without_limit($subject)
|
||||
{
|
||||
$parts = explode('/', $subject);
|
||||
return $parts[4];
|
||||
}
|
||||
|
||||
public function bench_explode_with_limit($subject)
|
||||
{
|
||||
$parts = explode('/', $subject, 6);
|
||||
return $parts[4];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_GruberURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for http://daringfireball.net/2009/11/liberal_regex_for_matching_urls';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'http://foo.com/blah_blah',
|
||||
'http://foo.com/blah_blah/',
|
||||
'(Something like http://foo.com/blah_blah)',
|
||||
'http://foo.com/blah_blah_(wikipedia)',
|
||||
'(Something like http://foo.com/blah_blah_(wikipedia))',
|
||||
'http://foo.com/blah_blah.',
|
||||
'http://foo.com/blah_blah/.',
|
||||
'<http://foo.com/blah_blah>',
|
||||
'<http://foo.com/blah_blah/>',
|
||||
'http://foo.com/blah_blah,',
|
||||
'http://www.example.com/wpstyle/?p=364.',
|
||||
'http://✪df.ws/e7l',
|
||||
'rdar://1234',
|
||||
'rdar:/1234',
|
||||
'x-yojimbo-item://6303E4C1-xxxx-45A6-AB9D-3A908F59AE0E',
|
||||
'message://%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e',
|
||||
'http://➡.ws/䨹',
|
||||
'www.➡.ws/䨹',
|
||||
'<tag>http://example.com</tag>',
|
||||
'Just a www.example.com link.',
|
||||
// To test the use of possessive quatifiers:
|
||||
'httpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp',
|
||||
);
|
||||
|
||||
public function bench_daringfireball($subject)
|
||||
{
|
||||
// Original regex by John Gruber
|
||||
preg_match('~\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))~', $subject, $matches);
|
||||
return (empty($matches)) ? FALSE : $matches[0];
|
||||
}
|
||||
|
||||
public function bench_daringfireball_v2($subject)
|
||||
{
|
||||
// Removed outer capturing parentheses, made another pair non-capturing
|
||||
preg_match('~\b(?:[\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|(?:[^[:punct:]\s]|/))~', $subject, $matches);
|
||||
return (empty($matches)) ? FALSE : $matches[0];
|
||||
}
|
||||
|
||||
public function bench_daringfireball_v3($subject)
|
||||
{
|
||||
// Made quantifiers possessive where possible
|
||||
preg_match('~\b(?:[\w-]++://?+|www[.])[^\s()<>]+(?:\([\w\d]++\)|(?:[^[:punct:]\s]|/))~', $subject, $matches);
|
||||
return (empty($matches)) ? FALSE : $matches[0];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_LtrimDigits extends Codebench {
|
||||
|
||||
public $description = 'Chopping off leading digits: regex vs ltrim.';
|
||||
|
||||
public $loops = 100000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'123digits',
|
||||
'no-digits',
|
||||
);
|
||||
|
||||
public function bench_regex($subject)
|
||||
{
|
||||
return preg_replace('/^\d+/', '', $subject);
|
||||
}
|
||||
|
||||
public function bench_ltrim($subject)
|
||||
{
|
||||
return ltrim($subject, '0..9');
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_MDDoBaseURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for the <code>doBaseURL()</code> method of <code>Kohana_Kodoc_Markdown</code>
|
||||
for the Kohana Userguide.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid matches
|
||||
'[filesystem](about.filesystem)',
|
||||
'[filesystem](about.filesystem "Optional title")',
|
||||
'[same page link](#id)',
|
||||
'[object oriented](http://wikipedia.org/wiki/Object-Oriented_Programming)',
|
||||
|
||||
// Invalid matches
|
||||
'![this is image syntax](about.filesystem)',
|
||||
'[filesystem](about.filesystem',
|
||||
);
|
||||
|
||||
public function bench_original($subject)
|
||||
{
|
||||
// The original regex contained a bug, which is fixed here for benchmarking purposes.
|
||||
// At the very start of the regex, (?!!) has been replace by (?<!!)
|
||||
return preg_replace_callback('~(?<!!)\[(.+?)\]\(([^#]\S*(?:\s*".+?")?)\)~', array($this, '_add_base_url_original'), $subject);
|
||||
}
|
||||
public function _add_base_url_original($matches)
|
||||
{
|
||||
if ($matches[2] AND strpos($matches[2], '://') === FALSE)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
}
|
||||
|
||||
// Recreate the link
|
||||
return "[{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_optimized_callback($subject)
|
||||
{
|
||||
return preg_replace_callback('~(?<!!)\[(.+?)\]\((?!\w++://)([^#]\S*(?:\s*+".+?")?)\)~', array($this, '_add_base_url_optimized'), $subject);
|
||||
}
|
||||
public function _add_base_url_optimized($matches)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
|
||||
// Recreate the link
|
||||
return "[{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_callback_gone($subject)
|
||||
{
|
||||
// All the optimized callback was doing now, is prepend some text to the URL.
|
||||
// We don't need a callback for that, and that should be clearly faster.
|
||||
return preg_replace('~(?<!!)(\[.+?\]\()(?!\w++://)([^#]\S*(?:\s*+".+?")?\))~', '$1http://BASE/$2', $subject);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_MDDoImageURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for the <code>doImageURL()</code> method of <code>Kohana_Kodoc_Markdown</code>
|
||||
for the Kohana Userguide.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid matches
|
||||
'![Alt text](http://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)',
|
||||
'![Alt text](https://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)',
|
||||
'![Alt text](otherprotocol://image.png "Optional title")',
|
||||
'![Alt text](img/install.png "Optional title")',
|
||||
'![Alt text containing [square] brackets](img/install.png)',
|
||||
'![Empty src]()',
|
||||
|
||||
// Invalid matches
|
||||
'![Alt text](img/install.png "No closing parenthesis"',
|
||||
);
|
||||
|
||||
public function bench_original($subject)
|
||||
{
|
||||
return preg_replace_callback('~!\[(.+?)\]\((\S*(?:\s*".+?")?)\)~', array($this, '_add_image_url_original'), $subject);
|
||||
}
|
||||
protected function _add_image_url_original($matches)
|
||||
{
|
||||
if ($matches[2] AND strpos($matches[2], '://') === FALSE)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
}
|
||||
|
||||
// Recreate the link
|
||||
return "![{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_optimized_callback($subject)
|
||||
{
|
||||
// Moved the check for "://" to the regex, simplifying the callback function
|
||||
return preg_replace_callback('~!\[(.+?)\]\((?!\w++://)(\S*(?:\s*+".+?")?)\)~', array($this, '_add_image_url_optimized'), $subject);
|
||||
}
|
||||
protected function _add_image_url_optimized($matches)
|
||||
{
|
||||
// Add the base url to the link URL
|
||||
$matches[2] = 'http://BASE/'.$matches[2];
|
||||
|
||||
// Recreate the link
|
||||
return "![{$matches[1]}]({$matches[2]})";
|
||||
}
|
||||
|
||||
public function bench_callback_gone($subject)
|
||||
{
|
||||
// All the optimized callback was doing now, is prepend some text to the URL.
|
||||
// We don't need a callback for that, and that should be clearly faster.
|
||||
return preg_replace('~(!\[.+?\]\()(?!\w++://)(\S*(?:\s*+".+?")?\))~', '$1http://BASE/$2', $subject);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_MDDoIncludeViews extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for the <code>doIncludeViews()</code> method of <code>Kohana_Kodoc_Markdown</code>
|
||||
for the Kohana Userguide.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid matches
|
||||
'{{one}} two {{three}}',
|
||||
'{{userguide/examples/hello_world_error}}',
|
||||
|
||||
// Invalid matches
|
||||
'{}',
|
||||
'{{}}',
|
||||
'{{userguide/examples/hello_world_error}',
|
||||
'{{userguide/examples/hello_world_error }}',
|
||||
'{{userguide/examples/{{hello_world_error }}',
|
||||
);
|
||||
|
||||
public function bench_original($subject)
|
||||
{
|
||||
preg_match_all('/{{(\S+?)}}/m', $subject, $matches, PREG_SET_ORDER);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_possessive($subject)
|
||||
{
|
||||
// Using a possessive character class
|
||||
// Removed useless /m modifier
|
||||
preg_match_all('/{{([^\s{}]++)}}/', $subject, $matches, PREG_SET_ORDER);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
public function bench_lookaround($subject)
|
||||
{
|
||||
// Using lookaround to move $mathes[1] into $matches[0]
|
||||
preg_match_all('/(?<={{)[^\s{}]++(?=}})/', $subject, $matches, PREG_SET_ORDER);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_StripNullBytes extends Codebench {
|
||||
|
||||
public $description =
|
||||
'String replacement comparisons related to <a href="http://dev.kohanaphp.com/issues/2676">#2676</a>.';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
"\0",
|
||||
"\0\0\0\0\0\0\0\0\0\0",
|
||||
"bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla\0bla",
|
||||
"blablablablablablablablablablablablablablablabla",
|
||||
);
|
||||
|
||||
public function bench_str_replace($subject)
|
||||
{
|
||||
return str_replace("\0", '', $subject);
|
||||
}
|
||||
|
||||
public function bench_strtr($subject)
|
||||
{
|
||||
return strtr($subject, array("\0" => ''));
|
||||
}
|
||||
|
||||
public function bench_preg_replace($subject)
|
||||
{
|
||||
return preg_replace('~\0+~', '', $subject);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_Transliterate extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Inspired by:
|
||||
http://forum.kohanaframework.org/comments.php?DiscussionID=6113';
|
||||
|
||||
public $loops = 10;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// ASCII
|
||||
'a', 'b', 'c', 'd', '1', '2', '3',
|
||||
|
||||
// Non-ASCII
|
||||
'à', 'ô', 'ď', 'ḟ', 'ë', 'š', 'ơ',
|
||||
'ß', 'ă', 'ř', 'ț', 'ň', 'ā', 'ķ',
|
||||
'ŝ', 'ỳ', 'ņ', 'ĺ', 'ħ', 'ṗ', 'ó',
|
||||
'ú', 'ě', 'é', 'ç', 'ẁ', 'ċ', 'õ',
|
||||
'ṡ', 'ø', 'ģ', 'ŧ', 'ș', 'ė', 'ĉ',
|
||||
'ś', 'î', 'ű', 'ć', 'ę', 'ŵ', 'ṫ',
|
||||
'ū', 'č', 'ö', 'è', 'ŷ', 'ą', 'ł',
|
||||
'ų', 'ů', 'ş', 'ğ', 'ļ', 'ƒ', 'ž',
|
||||
'ẃ', 'ḃ', 'å', 'ì', 'ï', 'ḋ', 'ť',
|
||||
'ŗ', 'ä', 'í', 'ŕ', 'ê', 'ü', 'ò',
|
||||
'ē', 'ñ', 'ń', 'ĥ', 'ĝ', 'đ', 'ĵ',
|
||||
'ÿ', 'ũ', 'ŭ', 'ư', 'ţ', 'ý', 'ő',
|
||||
'â', 'ľ', 'ẅ', 'ż', 'ī', 'ã', 'ġ',
|
||||
'ṁ', 'ō', 'ĩ', 'ù', 'į', 'ź', 'á',
|
||||
'û', 'þ', 'ð', 'æ', 'µ', 'ĕ', 'ı',
|
||||
'À', 'Ô', 'Ď', 'Ḟ', 'Ë', 'Š', 'Ơ',
|
||||
'Ă', 'Ř', 'Ț', 'Ň', 'Ā', 'Ķ', 'Ĕ',
|
||||
'Ŝ', 'Ỳ', 'Ņ', 'Ĺ', 'Ħ', 'Ṗ', 'Ó',
|
||||
'Ú', 'Ě', 'É', 'Ç', 'Ẁ', 'Ċ', 'Õ',
|
||||
'Ṡ', 'Ø', 'Ģ', 'Ŧ', 'Ș', 'Ė', 'Ĉ',
|
||||
'Ś', 'Î', 'Ű', 'Ć', 'Ę', 'Ŵ', 'Ṫ',
|
||||
'Ū', 'Č', 'Ö', 'È', 'Ŷ', 'Ą', 'Ł',
|
||||
'Ų', 'Ů', 'Ş', 'Ğ', 'Ļ', 'Ƒ', 'Ž',
|
||||
'Ẃ', 'Ḃ', 'Å', 'Ì', 'Ï', 'Ḋ', 'Ť',
|
||||
'Ŗ', 'Ä', 'Í', 'Ŕ', 'Ê', 'Ü', 'Ò',
|
||||
'Ē', 'Ñ', 'Ń', 'Ĥ', 'Ĝ', 'Đ', 'Ĵ',
|
||||
'Ÿ', 'Ũ', 'Ŭ', 'Ư', 'Ţ', 'Ý', 'Ő',
|
||||
'Â', 'Ľ', 'Ẅ', 'Ż', 'Ī', 'Ã', 'Ġ',
|
||||
'Ṁ', 'Ō', 'Ĩ', 'Ù', 'Į', 'Ź', 'Á',
|
||||
'Û', 'Þ', 'Ð', 'Æ', 'İ',
|
||||
);
|
||||
|
||||
public function bench_utf8($subject)
|
||||
{
|
||||
return UTF8::transliterate_to_ascii($subject);
|
||||
}
|
||||
|
||||
public function bench_iconv($subject)
|
||||
{
|
||||
// Note: need to suppress errors on iconv because some chars trigger the following notice:
|
||||
// "Detected an illegal character in input string"
|
||||
return preg_replace('~[^-a-z0-9]+~i', '', @iconv('UTF-8', 'ASCII//TRANSLIT', $subject));
|
||||
}
|
||||
|
||||
}
|
123
includes/kohana/modules/codebench/classes/bench/urlsite.php
Normal file
123
includes/kohana/modules/codebench/classes/bench/urlsite.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_URLSite extends Codebench {
|
||||
|
||||
public $description = 'http://dev.kohanaframework.org/issues/3110';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
'',
|
||||
'news',
|
||||
'news/',
|
||||
'/news/',
|
||||
'news/page/5',
|
||||
'news/page:5',
|
||||
'http://example.com/',
|
||||
'http://example.com/hello',
|
||||
'http://example.com:80/',
|
||||
'http://user:pass@example.com/',
|
||||
);
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
foreach ($this->subjects as $subject)
|
||||
{
|
||||
// Automatically create URIs with query string and/or fragment part appended
|
||||
$this->subjects[] = $subject.'?query=string';
|
||||
$this->subjects[] = $subject.'#fragment';
|
||||
$this->subjects[] = $subject.'?query=string#fragment';
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function bench_original($uri)
|
||||
{
|
||||
// Get the path from the URI
|
||||
$path = trim(parse_url($uri, PHP_URL_PATH), '/');
|
||||
|
||||
if ($query = parse_url($uri, PHP_URL_QUERY))
|
||||
{
|
||||
$query = '?'.$query;
|
||||
}
|
||||
|
||||
if ($fragment = parse_url($uri, PHP_URL_FRAGMENT))
|
||||
{
|
||||
$fragment = '#'.$fragment;
|
||||
}
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
public function bench_explode($uri)
|
||||
{
|
||||
// Chop off possible scheme, host, port, user and pass parts
|
||||
$path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
|
||||
|
||||
$fragment = '';
|
||||
$explode = explode('#', $path, 2);
|
||||
if (isset($explode[1]))
|
||||
{
|
||||
$path = $explode[0];
|
||||
$fragment = '#'.$explode[1];
|
||||
}
|
||||
|
||||
$query = '';
|
||||
$explode = explode('?', $path, 2);
|
||||
if (isset($explode[1]))
|
||||
{
|
||||
$path = $explode[0];
|
||||
$query = '?'.$explode[1];
|
||||
}
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
public function bench_regex($uri)
|
||||
{
|
||||
preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches);
|
||||
$path = Arr::get($matches, 1, '');
|
||||
$query = Arr::get($matches, 2, '');
|
||||
$fragment = Arr::get($matches, 3, '');
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
public function bench_regex_without_arrget($uri)
|
||||
{
|
||||
preg_match('~^(?:[-a-z0-9+.]++://[^/]++/?)?([^?#]++)?(\?[^#]*+)?(#.*)?~', trim($uri, '/'), $matches);
|
||||
$path = isset($matches[1]) ? $matches[1] : '';
|
||||
$query = isset($matches[2]) ? $matches[2] : '';
|
||||
$fragment = isset($matches[3]) ? $matches[3] : '';
|
||||
|
||||
return $path.$query.$fragment;
|
||||
}
|
||||
|
||||
// And then I thought, why do all the work of extracting the query and fragment parts and then reappending them?
|
||||
// Just leaving them alone should be fine, right? As a bonus we get a very nice speed boost.
|
||||
public function bench_less_is_more($uri)
|
||||
{
|
||||
// Chop off possible scheme, host, port, user and pass parts
|
||||
$path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function bench_less_is_more_with_strpos_optimization($uri)
|
||||
{
|
||||
if (strpos($uri, '://') !== FALSE)
|
||||
{
|
||||
// Chop off possible scheme, host, port, user and pass parts
|
||||
$uri = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Woody Gilk <woody.gilk@kohanaphp.com>
|
||||
*/
|
||||
class Bench_UserFuncArray extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Testing the speed difference of using <code>call_user_func_array</code>
|
||||
compared to counting args and doing manual calls.';
|
||||
|
||||
public $loops = 100000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Argument sets
|
||||
array(),
|
||||
array('one'),
|
||||
array('one', 'two'),
|
||||
array('one', 'two', 'three'),
|
||||
);
|
||||
|
||||
public function bench_count_args($args)
|
||||
{
|
||||
$name = 'callme';
|
||||
switch (count($args))
|
||||
{
|
||||
case 1:
|
||||
$this->$name($args[0]);
|
||||
break;
|
||||
case 2:
|
||||
$this->$name($args[0], $args[1]);
|
||||
break;
|
||||
case 3:
|
||||
$this->$name($args[0], $args[1], $args[2]);
|
||||
break;
|
||||
case 4:
|
||||
$this->$name($args[0], $args[1], $args[2], $args[3]);
|
||||
break;
|
||||
default:
|
||||
call_user_func_array(array($this, $name), $args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function bench_direct_call($args)
|
||||
{
|
||||
$name = 'callme';
|
||||
call_user_func_array(array($this, $name), $args);
|
||||
}
|
||||
|
||||
protected function callme()
|
||||
{
|
||||
return count(func_get_args());
|
||||
}
|
||||
|
||||
}
|
116
includes/kohana/modules/codebench/classes/bench/validcolor.php
Normal file
116
includes/kohana/modules/codebench/classes/bench/validcolor.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ValidColor extends Codebench {
|
||||
|
||||
public $description =
|
||||
'Optimization for <code>Validate::color()</code>.
|
||||
See: http://forum.kohanaphp.com/comments.php?DiscussionID=2192.
|
||||
|
||||
Note that the methods with an <em>_invalid</em> suffix contain flawed regexes and should be
|
||||
completely discarded. I left them in here for educational purposes, and to remind myself
|
||||
to think harder and test more thoroughly. It can\'t be that I only found out so late in
|
||||
the game. For the regex explanation have a look at the forum topic mentioned earlier.';
|
||||
|
||||
public $loops = 10000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid colors
|
||||
'aaA',
|
||||
'123',
|
||||
'000000',
|
||||
'#123456',
|
||||
'#abcdef',
|
||||
|
||||
// Invalid colors
|
||||
'ggg',
|
||||
'1234',
|
||||
'#1234567',
|
||||
"#000\n",
|
||||
'}§è!çà%$z',
|
||||
);
|
||||
|
||||
// Note that I added the D modifier to corey's regexes. We need to match exactly
|
||||
// the same if we want the benchmarks to be of any value.
|
||||
public function bench_corey_regex_1_invalid($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?([0-9a-f]{1,2}){3}$/iD', $subject);
|
||||
}
|
||||
|
||||
public function bench_corey_regex_2($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?([0-9a-f]){3}(([0-9a-f]){3})?$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized corey_regex_1
|
||||
// Using non-capturing parentheses and a possessive interval
|
||||
public function bench_geert_regex_1a_invalid($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?(?:[0-9a-f]{1,2}+){3}$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized corey_regex_2
|
||||
// Removed useless parentheses, made the remaining ones non-capturing
|
||||
public function bench_geert_regex_2a($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized geert_regex_1a
|
||||
// Possessive "#"
|
||||
public function bench_geert_regex_1b_invalid($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?+(?:[0-9a-f]{1,2}+){3}$/iD', $subject);
|
||||
}
|
||||
|
||||
// Optimized geert_regex_2a
|
||||
// Possessive "#"
|
||||
public function bench_geert_regex_2b($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $subject);
|
||||
}
|
||||
|
||||
// Using \z instead of $
|
||||
public function bench_salathe_regex_1($subject)
|
||||
{
|
||||
return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject);
|
||||
}
|
||||
|
||||
// Using \A instead of ^
|
||||
public function bench_salathe_regex_2($subject)
|
||||
{
|
||||
return (bool) preg_match('/\A#?+[0-9a-f]{3}(?:[0-9a-f]{3})?\z/i', $subject);
|
||||
}
|
||||
|
||||
// A solution without regex
|
||||
public function bench_geert_str($subject)
|
||||
{
|
||||
if ($subject[0] === '#')
|
||||
{
|
||||
$subject = substr($subject, 1);
|
||||
}
|
||||
|
||||
$strlen = strlen($subject);
|
||||
return (($strlen === 3 OR $strlen === 6) AND ctype_xdigit($subject));
|
||||
}
|
||||
|
||||
// An ugly, but fast, solution without regex
|
||||
public function bench_salathe_str($subject)
|
||||
{
|
||||
if ($subject[0] === '#')
|
||||
{
|
||||
$subject = substr($subject, 1);
|
||||
}
|
||||
|
||||
// TRUE if:
|
||||
// 1. $subject is 6 or 3 chars long
|
||||
// 2. $subject contains only hexadecimal digits
|
||||
return (((isset($subject[5]) AND ! isset($subject[6])) OR
|
||||
(isset($subject[2]) AND ! isset($subject[3])))
|
||||
AND ctype_xdigit($subject));
|
||||
}
|
||||
}
|
105
includes/kohana/modules/codebench/classes/bench/validurl.php
Normal file
105
includes/kohana/modules/codebench/classes/bench/validurl.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* @package Kohana/Codebench
|
||||
* @category Tests
|
||||
* @author Geert De Deckere <geert@idoe.be>
|
||||
*/
|
||||
class Bench_ValidURL extends Codebench {
|
||||
|
||||
public $description =
|
||||
'filter_var vs regex:
|
||||
http://dev.kohanaframework.org/issues/2847';
|
||||
|
||||
public $loops = 1000;
|
||||
|
||||
public $subjects = array
|
||||
(
|
||||
// Valid
|
||||
'http://google.com',
|
||||
'http://google.com/',
|
||||
'http://google.com/?q=abc',
|
||||
'http://google.com/#hash',
|
||||
'http://localhost',
|
||||
'http://hello-world.pl',
|
||||
'http://hello--world.pl',
|
||||
'http://h.e.l.l.0.pl',
|
||||
'http://server.tld/get/info',
|
||||
'http://127.0.0.1',
|
||||
'http://127.0.0.1:80',
|
||||
'http://user@127.0.0.1',
|
||||
'http://user:pass@127.0.0.1',
|
||||
'ftp://my.server.com',
|
||||
'rss+xml://rss.example.com',
|
||||
|
||||
// Invalid
|
||||
'http://google.2com',
|
||||
'http://google.com?q=abc',
|
||||
'http://google.com#hash',
|
||||
'http://hello-.pl',
|
||||
'http://hel.-lo.world.pl',
|
||||
'http://ww£.google.com',
|
||||
'http://127.0.0.1234',
|
||||
'http://127.0.0.1.1',
|
||||
'http://user:@127.0.0.1',
|
||||
"http://finalnewline.com\n",
|
||||
);
|
||||
|
||||
public function bench_filter_var($url)
|
||||
{
|
||||
return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
|
||||
}
|
||||
|
||||
public function bench_regex($url)
|
||||
{
|
||||
// Based on http://www.apps.ietf.org/rfc/rfc1738.html#sec-5
|
||||
if ( ! preg_match(
|
||||
'~^
|
||||
|
||||
# scheme
|
||||
[-a-z0-9+.]++://
|
||||
|
||||
# username:password (optional)
|
||||
(?:
|
||||
[-a-z0-9$_.+!*\'(),;?&=%]++ # username
|
||||
(?::[-a-z0-9$_.+!*\'(),;?&=%]++)? # password (optional)
|
||||
@
|
||||
)?
|
||||
|
||||
(?:
|
||||
# ip address
|
||||
\d{1,3}+(?:\.\d{1,3}+){3}+
|
||||
|
||||
| # or
|
||||
|
||||
# hostname (captured)
|
||||
(
|
||||
(?!-)[-a-z0-9]{1,63}+(?<!-)
|
||||
(?:\.(?!-)[-a-z0-9]{1,63}+(?<!-)){0,126}+
|
||||
)
|
||||
)
|
||||
|
||||
# port (optional)
|
||||
(?::\d{1,5}+)?
|
||||
|
||||
# path (optional)
|
||||
(?:/.*)?
|
||||
|
||||
$~iDx', $url, $matches))
|
||||
return FALSE;
|
||||
|
||||
// We matched an IP address
|
||||
if ( ! isset($matches[1]))
|
||||
return TRUE;
|
||||
|
||||
// Check maximum length of the whole hostname
|
||||
// http://en.wikipedia.org/wiki/Domain_name#cite_note-0
|
||||
if (strlen($matches[1]) > 253)
|
||||
return FALSE;
|
||||
|
||||
// An extra check for the top level domain
|
||||
// It must start with a letter
|
||||
$tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.');
|
||||
return ctype_alpha($tld[0]);
|
||||
}
|
||||
|
||||
}
|
3
includes/kohana/modules/codebench/classes/codebench.php
Normal file
3
includes/kohana/modules/codebench/classes/codebench.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Codebench extends Kohana_Codebench {}
|
@ -0,0 +1,32 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Codebench — A benchmarking module.
|
||||
*
|
||||
* @package Kohana/Codebench
|
||||
* @category Controllers
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
class Controller_Codebench extends Kohana_Controller_Template {
|
||||
|
||||
// The codebench view
|
||||
public $template = 'codebench';
|
||||
|
||||
public function action_index($class)
|
||||
{
|
||||
// Convert submitted class name to URI segment
|
||||
if (isset($_POST['class']))
|
||||
$this->request->redirect('codebench/'.trim($_POST['class']));
|
||||
|
||||
// Pass the class name on to the view
|
||||
$this->template->class = (string) $class;
|
||||
|
||||
// Try to load the class, then run it
|
||||
if (Kohana::auto_load($class) === TRUE)
|
||||
{
|
||||
$codebench = new $class;
|
||||
$this->template->codebench = $codebench->run();
|
||||
}
|
||||
}
|
||||
}
|
217
includes/kohana/modules/codebench/classes/kohana/codebench.php
Normal file
217
includes/kohana/modules/codebench/classes/kohana/codebench.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Codebench — A benchmarking module.
|
||||
*
|
||||
* @package Kohana/Codebench
|
||||
* @category Base
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
abstract class Kohana_Codebench {
|
||||
|
||||
/**
|
||||
* @var string Some optional explanatory comments about the benchmark file.
|
||||
* HTML allowed. URLs will be converted to links automatically.
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* @var integer How many times to execute each method per subject.
|
||||
*/
|
||||
public $loops = 1000;
|
||||
|
||||
/**
|
||||
* @var array The subjects to supply iteratively to your benchmark methods.
|
||||
*/
|
||||
public $subjects = array();
|
||||
|
||||
/**
|
||||
* @var array Grade letters with their maximum scores. Used to color the graphs.
|
||||
*/
|
||||
public $grades = array
|
||||
(
|
||||
125 => 'A',
|
||||
150 => 'B',
|
||||
200 => 'C',
|
||||
300 => 'D',
|
||||
500 => 'E',
|
||||
'default' => 'F',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Set the maximum execution time
|
||||
set_time_limit(Kohana::config('codebench')->max_execution_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs Codebench on the extending class.
|
||||
*
|
||||
* @return array benchmark output
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// Array of all methods to loop over
|
||||
$methods = array_filter(get_class_methods($this), array($this, '_method_filter'));
|
||||
|
||||
// Make sure the benchmark runs at least once,
|
||||
// also if no subject data has been provided.
|
||||
if (empty($this->subjects))
|
||||
{
|
||||
$this->subjects = array('NULL' => NULL);
|
||||
}
|
||||
|
||||
// Initialize benchmark output
|
||||
$codebench = array
|
||||
(
|
||||
'class' => get_class($this),
|
||||
'description' => $this->description,
|
||||
'loops' => array
|
||||
(
|
||||
'base' => (int) $this->loops,
|
||||
'total' => (int) $this->loops * count($this->subjects) * count($methods),
|
||||
),
|
||||
'subjects' => $this->subjects,
|
||||
'benchmarks' => array(),
|
||||
);
|
||||
|
||||
// Benchmark each method
|
||||
foreach ($methods as $method)
|
||||
{
|
||||
// Initialize benchmark output for this method
|
||||
$codebench['benchmarks'][$method] = array('time' => 0, 'memory' => 0);
|
||||
|
||||
// Using Reflection because simply calling $this->$method($subject) in the loop below
|
||||
// results in buggy benchmark times correlating to the length of the method name.
|
||||
$reflection = new ReflectionMethod(get_class($this), $method);
|
||||
|
||||
// Benchmark each subject on each method
|
||||
foreach ($this->subjects as $subject_key => $subject)
|
||||
{
|
||||
// Prerun each method/subject combo before the actual benchmark loop.
|
||||
// This way relatively expensive initial processes won't be benchmarked, e.g. autoloading.
|
||||
// At the same time we capture the return here so we don't have to do that in the loop anymore.
|
||||
$return = $reflection->invoke($this, $subject);
|
||||
|
||||
// Start the timer for one subject
|
||||
$token = Profiler::start('codebench', $method.$subject_key);
|
||||
|
||||
// The heavy work
|
||||
for ($i = 0; $i < $this->loops; ++$i)
|
||||
{
|
||||
$reflection->invoke($this, $subject);
|
||||
}
|
||||
|
||||
// Stop and read the timer
|
||||
$benchmark = Profiler::total($token);
|
||||
|
||||
// Benchmark output specific to the current method and subject
|
||||
$codebench['benchmarks'][$method]['subjects'][$subject_key] = array
|
||||
(
|
||||
'return' => $return,
|
||||
'time' => $benchmark[0],
|
||||
'memory' => $benchmark[1],
|
||||
);
|
||||
|
||||
// Update method totals
|
||||
$codebench['benchmarks'][$method]['time'] += $benchmark[0];
|
||||
$codebench['benchmarks'][$method]['memory'] += $benchmark[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the fastest and slowest benchmarks for both methods and subjects, time and memory,
|
||||
// these values will be overwritten using min() and max() later on.
|
||||
// The 999999999 values look like a hack, I know, but they work,
|
||||
// unless your method runs for more than 31 years or consumes over 1GB of memory.
|
||||
$fastest_method = $fastest_subject = array('time' => 999999999, 'memory' => 999999999);
|
||||
$slowest_method = $slowest_subject = array('time' => 0, 'memory' => 0);
|
||||
|
||||
// Find the fastest and slowest benchmarks, needed for the percentage calculations
|
||||
foreach ($methods as $method)
|
||||
{
|
||||
// Update the fastest and slowest method benchmarks
|
||||
$fastest_method['time'] = min($fastest_method['time'], $codebench['benchmarks'][$method]['time']);
|
||||
$fastest_method['memory'] = min($fastest_method['memory'], $codebench['benchmarks'][$method]['memory']);
|
||||
$slowest_method['time'] = max($slowest_method['time'], $codebench['benchmarks'][$method]['time']);
|
||||
$slowest_method['memory'] = max($slowest_method['memory'], $codebench['benchmarks'][$method]['memory']);
|
||||
|
||||
foreach ($this->subjects as $subject_key => $subject)
|
||||
{
|
||||
// Update the fastest and slowest subject benchmarks
|
||||
$fastest_subject['time'] = min($fastest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
|
||||
$fastest_subject['memory'] = min($fastest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
|
||||
$slowest_subject['time'] = max($slowest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
|
||||
$slowest_subject['memory'] = max($slowest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
|
||||
}
|
||||
}
|
||||
|
||||
// Percentage calculations for methods
|
||||
foreach ($codebench['benchmarks'] as & $method)
|
||||
{
|
||||
// Calculate percentage difference relative to fastest and slowest methods
|
||||
$method['percent']['fastest']['time'] = (empty($fastest_method['time'])) ? 0 : $method['time'] / $fastest_method['time'] * 100;
|
||||
$method['percent']['fastest']['memory'] = (empty($fastest_method['memory'])) ? 0 : $method['memory'] / $fastest_method['memory'] * 100;
|
||||
$method['percent']['slowest']['time'] = (empty($slowest_method['time'])) ? 0 : $method['time'] / $slowest_method['time'] * 100;
|
||||
$method['percent']['slowest']['memory'] = (empty($slowest_method['memory'])) ? 0 : $method['memory'] / $slowest_method['memory'] * 100;
|
||||
|
||||
// Assign a grade for time and memory to each method
|
||||
$method['grade']['time'] = $this->_grade($method['percent']['fastest']['time']);
|
||||
$method['grade']['memory'] = $this->_grade($method['percent']['fastest']['memory']);
|
||||
|
||||
// Percentage calculations for subjects
|
||||
foreach ($method['subjects'] as & $subject)
|
||||
{
|
||||
// Calculate percentage difference relative to fastest and slowest subjects for this method
|
||||
$subject['percent']['fastest']['time'] = (empty($fastest_subject['time'])) ? 0 : $subject['time'] / $fastest_subject['time'] * 100;
|
||||
$subject['percent']['fastest']['memory'] = (empty($fastest_subject['memory'])) ? 0 : $subject['memory'] / $fastest_subject['memory'] * 100;
|
||||
$subject['percent']['slowest']['time'] = (empty($slowest_subject['time'])) ? 0 : $subject['time'] / $slowest_subject['time'] * 100;
|
||||
$subject['percent']['slowest']['memory'] = (empty($slowest_subject['memory'])) ? 0 : $subject['memory'] / $slowest_subject['memory'] * 100;
|
||||
|
||||
// Assign a grade letter for time and memory to each subject
|
||||
$subject['grade']['time'] = $this->_grade($subject['percent']['fastest']['time']);
|
||||
$subject['grade']['memory'] = $this->_grade($subject['percent']['fastest']['memory']);
|
||||
}
|
||||
}
|
||||
|
||||
return $codebench;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for array_filter().
|
||||
* Filters out all methods not to benchmark.
|
||||
*
|
||||
* @param string method name
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _method_filter($method)
|
||||
{
|
||||
// Only benchmark methods with the "bench" prefix
|
||||
return (substr($method, 0, 5) === 'bench');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the applicable grade letter for a score.
|
||||
*
|
||||
* @param integer|double score
|
||||
* @return string grade letter
|
||||
*/
|
||||
protected function _grade($score)
|
||||
{
|
||||
foreach ($this->grades as $max => $grade)
|
||||
{
|
||||
if ($max === 'default')
|
||||
continue;
|
||||
|
||||
if ($score <= $max)
|
||||
return $grade;
|
||||
}
|
||||
|
||||
return $this->grades['default'];
|
||||
}
|
||||
}
|
16
includes/kohana/modules/codebench/config/codebench.php
Normal file
16
includes/kohana/modules/codebench/config/codebench.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
return array(
|
||||
|
||||
/**
|
||||
* The maximum execution time, in seconds. If set to zero, no time limit is imposed.
|
||||
* Note: http://php.net/manual/en/function.set-time-limit.php#84563
|
||||
*/
|
||||
'max_execution_time' => 0,
|
||||
|
||||
/**
|
||||
* Expand all benchmark details by default.
|
||||
*/
|
||||
'expand_all' => FALSE,
|
||||
|
||||
);
|
8
includes/kohana/modules/codebench/init.php
Normal file
8
includes/kohana/modules/codebench/init.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
// Catch-all route for Codebench classes to run
|
||||
Route::set('codebench', 'codebench(/<class>)')
|
||||
->defaults(array(
|
||||
'controller' => 'codebench',
|
||||
'action' => 'index',
|
||||
'class' => NULL));
|
258
includes/kohana/modules/codebench/views/codebench.php
Normal file
258
includes/kohana/modules/codebench/views/codebench.php
Normal file
@ -0,0 +1,258 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
/**
|
||||
* Codebench — A benchmarking module.
|
||||
*
|
||||
* @package Kohana
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license.html
|
||||
*/
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<title><?php if ($class !== '') echo $class, ' · ' ?>Codebench</title>
|
||||
|
||||
<style>
|
||||
/* General styles*/
|
||||
body { position:relative; margin:1em 2em; font:12px monaco,monospace; }
|
||||
h1 { font-size:24px; letter-spacing:-0.05em; }
|
||||
h2 { font-size:18px; letter-spacing:-0.1em; }
|
||||
input, code { font:inherit; }
|
||||
code { background:#e5e5e5; }
|
||||
caption { display:none; }
|
||||
|
||||
/* Form */
|
||||
#runner { margin-bottom:2em; }
|
||||
#runner input[type="text"] { letter-spacing:-0.05em; }
|
||||
|
||||
/* Expand/Collapse all */
|
||||
#toggle_all { position:absolute; top:0; right:0; margin:0; padding:0 4px; background:#000; font-size:18px; color:#fff; cursor:pointer; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
|
||||
/* Benchmark main graphs */
|
||||
#bench { margin:2em 0; padding:0; list-style:none; }
|
||||
#bench > li { margin:6px 0; }
|
||||
#bench h2 { position:relative; margin:0; padding:2px; background:#ccc; border:1px solid #999; cursor:pointer; -moz-border-radius:3px; -webkit-border-radius:3px; }
|
||||
#bench h2 > span { display:block; min-width:1px; height:33px; background:#fff; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
#bench h2 .method { position:absolute; top:6px; left:8px; text-shadow:0 -1px 0 rgba(255,255,255,0.6); }
|
||||
#bench h2 .method:before { content:'▸ '; }
|
||||
#bench h2 .percent { position:absolute; top:6px; right:6px; padding:0 4px; background:#000; color:#fff; font-weight:normal; letter-spacing:0; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
#bench h2:hover .method { left:10px; }
|
||||
#bench h2.expanded { margin:12px 0 0; -moz-border-radius-bottomleft:0; -moz-border-radius-bottomright:0; -webkit-border-bottom-left-radius:0; -webkit-border-bottom-right-radius:0; }
|
||||
#bench h2.expanded .method:before { content:'▾ '; }
|
||||
|
||||
/* Colorization of the bars */
|
||||
#bench .grade-A { background:#3f0; }
|
||||
#bench .grade-B { background:#fc0; }
|
||||
#bench .grade-C { background:#f90; }
|
||||
#bench .grade-D { background:#f60; }
|
||||
#bench .grade-E { background:#f30; }
|
||||
#bench .grade-F { background:#f00; }
|
||||
|
||||
/* Benchmark details */
|
||||
#bench > li > div { display:none; margin:0 0 12px; padding:0 0 2px; background:#eee; border:1px solid #999; border-top:0; -moz-border-radius-bottomleft:3px; -moz-border-radius-bottomright:3px; -webkit-border-bottom-left-radius:3px; -webkit-border-bottom-right-radius:3px; }
|
||||
#bench > li > div table { width:100%; background:#eee; border-collapse:collapse; }
|
||||
#bench > li > div th { padding:6px; background:#ddd url(data:image/gif;base64,R0lGODlhAQASALMAAMfHx8TExM7Oztvb29jY2NbW1tPT09DQ0MrKygAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAABABIAAAQNMAQAEBLiHGNKIcQwRAA7) repeat-x 0 1px; text-align:left; }
|
||||
#bench > li > div td { padding:6px; border-top:1px solid #ccc; vertical-align:top; }
|
||||
#bench .numeric { padding-left:18px; text-align:right; }
|
||||
#bench .numeric span { position:relative; display:block; height:16px; }
|
||||
#bench .numeric span span { position:absolute; top:0; right:0; min-width:1px; background:#ccc; -moz-border-radius:2px; -webkit-border-radius:2px; }
|
||||
#bench .numeric span span span { top:0; right:0; background:none; }
|
||||
#bench tbody tr:hover { background:#fff; }
|
||||
#bench tbody tr.highlight { background:#ffc; }
|
||||
|
||||
/* Footer */
|
||||
#footer { margin-top:2em; padding-top:1em; border-top:1px solid #ccc; color:#999; }
|
||||
#footer a { color:inherit; }
|
||||
|
||||
/* Misc text styles */
|
||||
.alert { padding:0 0.5em; background:#900; font-weight:normal; color:#fff; -moz-border-radius:3px; -webkit-border-radius:3px; }
|
||||
.quiet { color:#999; }
|
||||
.help { cursor:help; }
|
||||
</style>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Insert "Toggle All" button
|
||||
var expand_all_text = '▸ Expand all';
|
||||
var collapse_all_text = '▾ Collapse all';
|
||||
$('#bench').before('<p id="toggle_all">'+expand_all_text+'</p>');
|
||||
|
||||
// Cache these selection operations
|
||||
var $runner = $('#runner');
|
||||
var $toggle_all = $('#toggle_all');
|
||||
var $bench_titles = $('#bench > li > h2');
|
||||
var $bench_rows = $('#bench > li > div > table > tbody > tr');
|
||||
|
||||
// Runner form
|
||||
$(':input:first', $runner).focus();
|
||||
$runner.submit(function() {
|
||||
$(':submit', this).attr('value', 'Running…').attr('disabled', 'disabled');
|
||||
$('.alert', this).remove();
|
||||
});
|
||||
|
||||
// Toggle details for all benchmarks
|
||||
$('#toggle_all').click(function() {
|
||||
if ($(this).data('expanded')) {
|
||||
$(this).data('expanded', false);
|
||||
$(this).text(expand_all_text);
|
||||
$bench_titles.removeClass('expanded').siblings().hide();
|
||||
}
|
||||
else {
|
||||
$(this).data('expanded', true);
|
||||
$(this).text(collapse_all_text);
|
||||
$bench_titles.addClass('expanded').siblings().show();
|
||||
}
|
||||
});
|
||||
|
||||
<?php if (Kohana::config('codebench')->expand_all) { ?>
|
||||
// Expand all benchmark details by default
|
||||
$toggle_all.click();
|
||||
<?php } ?>
|
||||
|
||||
// Toggle details for a single benchmark
|
||||
$bench_titles.click(function() {
|
||||
$(this).toggleClass('expanded').siblings().toggle();
|
||||
|
||||
// Counts of bench titles
|
||||
var total_bench_titles = $bench_titles.length;
|
||||
var expanded_bench_titles = $bench_titles.filter('.expanded').length;
|
||||
|
||||
// If no benchmark details are expanded, change "Collapse all" to "Expand all"
|
||||
if (expanded_bench_titles == 0 && $toggle_all.data('expanded')) {
|
||||
$toggle_all.click();
|
||||
}
|
||||
// If all benchmark details are expanded, change "Expand all" to "Collapse all"
|
||||
else if (expanded_bench_titles == total_bench_titles && ! $toggle_all.data('expanded')) {
|
||||
$toggle_all.click();
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight clicked rows
|
||||
$bench_rows.click(function() {
|
||||
$(this).toggleClass('highlight');
|
||||
// Highlight doubleclicked rows globally
|
||||
}).dblclick(function() {
|
||||
var nth_row = $(this).parent().children().index(this) + 1;
|
||||
if ($(this).hasClass('highlight')) {
|
||||
$bench_rows.filter(':nth-child('+nth_row+')').removeClass('highlight');
|
||||
}
|
||||
else {
|
||||
$bench_rows.filter(':nth-child('+nth_row+')').addClass('highlight');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!--[if IE]><p class="alert">This page is not meant to be viewed in Internet Explorer. Get a better browser.</p><![endif]-->
|
||||
|
||||
<form id="runner" method="post" action="<?php echo URL::site('codebench') ?>">
|
||||
<h1>
|
||||
<input name="class" type="text" value="<?php echo ($class !== '') ? $class : 'Bench_' ?>" size="25" title="Name of the Codebench library to run" />
|
||||
<input type="submit" value="Run" />
|
||||
<?php if ( ! empty($class)) { ?>
|
||||
<?php if (empty($codebench)) { ?>
|
||||
<strong class="alert">Library not found</strong>
|
||||
<?php } elseif (empty($codebench['benchmarks'])) { ?>
|
||||
<strong class="alert">No methods found to benchmark</strong>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</h1>
|
||||
</form>
|
||||
|
||||
<?php if ( ! empty($codebench)) { ?>
|
||||
|
||||
<?php if (empty($codebench['benchmarks'])) { ?>
|
||||
|
||||
<p>
|
||||
<strong>
|
||||
Remember to prefix the methods you want to benchmark with “bench”.<br />
|
||||
You might also want to overwrite <code>Codebench->method_filter()</code>.
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<?php } else { ?>
|
||||
|
||||
<ul id="bench">
|
||||
<?php foreach ($codebench['benchmarks'] as $method => $benchmark) { ?>
|
||||
<li>
|
||||
|
||||
<h2 title="<?php printf('%01.6f', $benchmark['time']) ?>s">
|
||||
<span class="grade-<?php echo $benchmark['grade']['time'] ?>" style="width:<?php echo $benchmark['percent']['slowest']['time'] ?>%">
|
||||
<span class="method"><?php echo $method ?></span>
|
||||
<span class="percent">+<?php echo (int) $benchmark['percent']['fastest']['time'] ?>%</span>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<caption>Benchmarks per subject for <?php echo $method ?></caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50%">subject → return</th>
|
||||
<th class="numeric" style="width:25%" title="Total method memory"><?php echo Text::bytes($benchmark['memory'], 'MB', '%01.6f%s') ?></th>
|
||||
<th class="numeric" style="width:25%" title="Total method time"><?php printf('%01.6f', $benchmark['time']) ?>s</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<?php foreach ($benchmark['subjects'] as $subject_key => $subject) { ?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong class="help" title="(<?php echo gettype($codebench['subjects'][$subject_key]) ?>) <?php echo HTML::chars(var_export($codebench['subjects'][$subject_key], TRUE)) ?>">
|
||||
[<?php echo HTML::chars($subject_key) ?>] →
|
||||
</strong>
|
||||
<span class="quiet">(<?php echo gettype($subject['return']) ?>)</span>
|
||||
<?php echo HTML::chars(var_export($subject['return'], TRUE)) ?>
|
||||
</td>
|
||||
<td class="numeric">
|
||||
<span title="+<?php echo (int) $subject['percent']['fastest']['memory'] ?>% memory">
|
||||
<span style="width:<?php echo $subject['percent']['slowest']['memory'] ?>%">
|
||||
<span><?php echo Text::bytes($subject['memory'], 'MB', '%01.6f%s') ?></span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="numeric">
|
||||
<span title="+<?php echo (int) $subject['percent']['fastest']['time'] ?>% time">
|
||||
<span style="width:<?php echo $subject['percent']['slowest']['time'] ?>%">
|
||||
<span><?php printf('%01.6f', $subject['time']) ?>s</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<?php if ( ! empty($codebench['description'])) { ?>
|
||||
<?php echo Text::auto_p(Text::auto_link($codebench['description']), FALSE) ?>
|
||||
<?php } ?>
|
||||
|
||||
<?php // echo '<h2>Raw output:</h2>', Kohana::debug($codebench) ?>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
<p id="footer">
|
||||
Page executed in <strong><?php echo round(microtime(TRUE) - KOHANA_START_TIME, 2) ?> s</strong>
|
||||
using <strong><?php echo Text::widont(Text::bytes(memory_get_usage(), 'MB')) ?></strong> of memory.<br />
|
||||
<a href="http://github.com/kohana/codebench">Codebench</a>, a <a href="http://kohanaframework.org/">Kohana</a> module
|
||||
by <a href="http://www.geertdedeckere.be/article/introducing-codebench">Geert De Deckere</a>.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
3
includes/kohana/modules/database/classes/database.php
Normal file
3
includes/kohana/modules/database/classes/database.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
abstract class Database extends Kohana_Database {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Exception extends Kohana_Database_Exception {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Expression extends Kohana_Database_Expression {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_MySQL extends Kohana_Database_MySQL {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_MySQL_Result extends Kohana_Database_MySQL_Result {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_PDO extends Kohana_Database_PDO {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Query extends Kohana_Database_Query {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
abstract class Database_Query_Builder extends Kohana_Database_Query_Builder {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Query_Builder_Delete extends Kohana_Database_Query_Builder_Delete {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Query_Builder_Insert extends Kohana_Database_Query_Builder_Insert {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Query_Builder_Join extends Kohana_Database_Query_Builder_Join {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Query_Builder_Select extends Kohana_Database_Query_Builder_Select {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Query_Builder_Update extends Kohana_Database_Query_Builder_Update {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
abstract class Database_Query_Builder_Where extends Kohana_Database_Query_Builder_Where {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
abstract class Database_Result extends Kohana_Database_Result {}
|
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Database_Result_Cached extends Kohana_Database_Result_Cached {}
|
3
includes/kohana/modules/database/classes/db.php
Normal file
3
includes/kohana/modules/database/classes/db.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class DB extends Kohana_DB {}
|
@ -0,0 +1,97 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database-based configuration loader.
|
||||
*
|
||||
* Schema for configuration table:
|
||||
*
|
||||
* group_name varchar(128)
|
||||
* config_key varchar(128)
|
||||
* config_value text
|
||||
* primary key (group_name, config_key)
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Configuration
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Config_Database extends Kohana_Config_Reader {
|
||||
|
||||
protected $_database_instance = 'default';
|
||||
|
||||
protected $_database_table = 'config';
|
||||
|
||||
public function __construct(array $config = NULL)
|
||||
{
|
||||
if (isset($config['instance']))
|
||||
{
|
||||
$this->_database_instance = $config['instance'];
|
||||
}
|
||||
|
||||
if (isset($config['table']))
|
||||
{
|
||||
$this->_database_table = $config['table'];
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the configuration table for all values for this group and
|
||||
* unserialize each of the values.
|
||||
*
|
||||
* @param string group name
|
||||
* @param array configuration array
|
||||
* @return $this clone of the current object
|
||||
*/
|
||||
public function load($group, array $config = NULL)
|
||||
{
|
||||
if ($config === NULL AND $group !== 'database')
|
||||
{
|
||||
// Load all of the configuration values for this group
|
||||
$query = DB::select('config_key', 'config_value')
|
||||
->from($this->_database_table)
|
||||
->where('group_name', '=', $group)
|
||||
->execute($this->_database_instance);
|
||||
|
||||
if (count($query) > 0)
|
||||
{
|
||||
// Unserialize the configuration values
|
||||
$config = array_map('unserialize', $query->as_array('config_key', 'config_value'));
|
||||
}
|
||||
}
|
||||
|
||||
return parent::load($group, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload setting offsets to insert or update the database values as
|
||||
* changes occur.
|
||||
*
|
||||
* @param string array key
|
||||
* @param mixed new value
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetSet($key, $value)
|
||||
{
|
||||
if ( ! $this->offsetExists($key))
|
||||
{
|
||||
// Insert a new value
|
||||
DB::insert($this->_database_table, array('group_name', 'config_key', 'config_value'))
|
||||
->values(array($this->_configuration_group, $key, serialize($value)))
|
||||
->execute($this->_database_instance);
|
||||
}
|
||||
elseif ($this->offsetGet($key) !== $value)
|
||||
{
|
||||
// Update the value
|
||||
DB::update($this->_database_table)
|
||||
->value('config_value', serialize($value))
|
||||
->where('group_name', '=', $this->_configuration_group)
|
||||
->where('config_key', '=', $key)
|
||||
->execute($this->_database_instance);
|
||||
}
|
||||
|
||||
return parent::offsetSet($key, $value);
|
||||
}
|
||||
|
||||
} // End Kohana_Config_Database
|
588
includes/kohana/modules/database/classes/kohana/database.php
Normal file
588
includes/kohana/modules/database/classes/kohana/database.php
Normal file
@ -0,0 +1,588 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database connection wrapper. All database object instances are referenced
|
||||
* by a name. Queries are typically handled by [Database_Query], rather than
|
||||
* using the database object directly.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Base
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2010 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
abstract class Kohana_Database {
|
||||
|
||||
// Query types
|
||||
const SELECT = 1;
|
||||
const INSERT = 2;
|
||||
const UPDATE = 3;
|
||||
const DELETE = 4;
|
||||
|
||||
/**
|
||||
* @var string default instance name
|
||||
*/
|
||||
public static $default = 'default';
|
||||
|
||||
/**
|
||||
* @var array Database instances
|
||||
*/
|
||||
public static $instances = array();
|
||||
|
||||
/**
|
||||
* Get a singleton Database instance. If configuration is not specified,
|
||||
* it will be loaded from the database configuration file using the same
|
||||
* group as the name.
|
||||
*
|
||||
* // Load the default database
|
||||
* $db = Database::instance();
|
||||
*
|
||||
* // Create a custom configured instance
|
||||
* $db = Database::instance('custom', $config);
|
||||
*
|
||||
* @param string instance name
|
||||
* @param array configuration parameters
|
||||
* @return Database
|
||||
*/
|
||||
public static function instance($name = NULL, array $config = NULL)
|
||||
{
|
||||
if ($name === NULL)
|
||||
{
|
||||
// Use the default instance name
|
||||
$name = Database::$default;
|
||||
}
|
||||
|
||||
if ( ! isset(Database::$instances[$name]))
|
||||
{
|
||||
if ($config === NULL)
|
||||
{
|
||||
// Load the configuration for this database
|
||||
$config = Kohana::config('database')->$name;
|
||||
}
|
||||
|
||||
if ( ! isset($config['type']))
|
||||
{
|
||||
throw new Kohana_Exception('Database type not defined in :name configuration',
|
||||
array(':name' => $name));
|
||||
}
|
||||
|
||||
// Set the driver class name
|
||||
$driver = 'Database_'.ucfirst($config['type']);
|
||||
|
||||
// Create the database connection instance
|
||||
new $driver($name, $config);
|
||||
}
|
||||
|
||||
return Database::$instances[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string the last query executed
|
||||
*/
|
||||
public $last_query;
|
||||
|
||||
// Character that is used to quote identifiers
|
||||
protected $_identifier = '"';
|
||||
|
||||
// Instance name
|
||||
protected $_instance;
|
||||
|
||||
// Raw server connection
|
||||
protected $_connection;
|
||||
|
||||
// Configuration array
|
||||
protected $_config;
|
||||
|
||||
/**
|
||||
* Stores the database configuration locally and name the instance.
|
||||
*
|
||||
* [!!] This method cannot be accessed directly, you must use [Database::instance].
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function __construct($name, array $config)
|
||||
{
|
||||
// Set the instance name
|
||||
$this->_instance = $name;
|
||||
|
||||
// Store the config locally
|
||||
$this->_config = $config;
|
||||
|
||||
// Store the database instance
|
||||
Database::$instances[$name] = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the database when the object is destroyed.
|
||||
*
|
||||
* // Destroy the database instance
|
||||
* unset(Database::instances[(string) $db], $db);
|
||||
*
|
||||
* [!!] Calling `unset($db)` is not enough to destroy the database, as it
|
||||
* will still be stored in `Database::$instances`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
final public function __destruct()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database instance name.
|
||||
*
|
||||
* echo (string) $db;
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function __toString()
|
||||
{
|
||||
return $this->_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the database. This is called automatically when the first
|
||||
* query is executed.
|
||||
*
|
||||
* $db->connect();
|
||||
*
|
||||
* @throws Database_Exception
|
||||
* @return void
|
||||
*/
|
||||
abstract public function connect();
|
||||
|
||||
/**
|
||||
* Disconnect from the database. This is called automatically by [Database::__destruct].
|
||||
*
|
||||
* $db->disconnect();
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
abstract public function disconnect();
|
||||
|
||||
/**
|
||||
* Set the connection character set. This is called automatically by [Database::connect].
|
||||
*
|
||||
* $db->set_charset('utf8');
|
||||
*
|
||||
* @throws Database_Exception
|
||||
* @param string character set name
|
||||
* @return void
|
||||
*/
|
||||
abstract public function set_charset($charset);
|
||||
|
||||
/**
|
||||
* Perform an SQL query of the given type.
|
||||
*
|
||||
* // Make a SELECT query and use objects for results
|
||||
* $db->query(Database::SELECT, 'SELECT * FROM groups', TRUE);
|
||||
*
|
||||
* // Make a SELECT query and use "Model_User" for the results
|
||||
* $db->query(Database::SELECT, 'SELECT * FROM users LIMIT 1', 'Model_User');
|
||||
*
|
||||
* @param integer Database::SELECT, Database::INSERT, etc
|
||||
* @param string SQL query
|
||||
* @param mixed result object class, TRUE for stdClass, FALSE for assoc array
|
||||
* @return object Database_Result for SELECT queries
|
||||
* @return array list (insert id, row count) for INSERT queries
|
||||
* @return integer number of affected rows for all other queries
|
||||
*/
|
||||
abstract public function query($type, $sql, $as_object);
|
||||
|
||||
/**
|
||||
* Count the number of records in the last query, without LIMIT or OFFSET applied.
|
||||
*
|
||||
* // Get the total number of records that match the last query
|
||||
* $count = $db->count_last_query();
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function count_last_query()
|
||||
{
|
||||
if ($sql = $this->last_query)
|
||||
{
|
||||
$sql = trim($sql);
|
||||
if (stripos($sql, 'SELECT') !== 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (stripos($sql, 'LIMIT') !== FALSE)
|
||||
{
|
||||
// Remove LIMIT from the SQL
|
||||
$sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
|
||||
}
|
||||
|
||||
if (stripos($sql, 'OFFSET') !== FALSE)
|
||||
{
|
||||
// Remove OFFSET from the SQL
|
||||
$sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql);
|
||||
}
|
||||
|
||||
// Get the total rows from the last query executed
|
||||
$result = $this->query
|
||||
(
|
||||
Database::SELECT,
|
||||
'SELECT COUNT(*) AS '.$this->quote_identifier('total_rows').' '.
|
||||
'FROM ('.$sql.') AS '.$this->quote_table('counted_results'),
|
||||
TRUE
|
||||
);
|
||||
|
||||
// Return the total number of rows from the query
|
||||
return (int) $result->current()->total_rows;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of records in a table.
|
||||
*
|
||||
* // Get the total number of records in the "users" table
|
||||
* $count = $db->count_records('users');
|
||||
*
|
||||
* @param mixed table name string or array(query, alias)
|
||||
* @return integer
|
||||
*/
|
||||
public function count_records($table)
|
||||
{
|
||||
// Quote the table name
|
||||
$table = $this->quote_identifier($table);
|
||||
|
||||
return $this->query(Database::SELECT, 'SELECT COUNT(*) AS total_row_count FROM '.$table, FALSE)
|
||||
->get('total_row_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized array describing the SQL data type
|
||||
*
|
||||
* $db->datatype('char');
|
||||
*
|
||||
* @param string SQL data type
|
||||
* @return array
|
||||
*/
|
||||
public function datatype($type)
|
||||
{
|
||||
static $types = array
|
||||
(
|
||||
// SQL-92
|
||||
'bit' => array('type' => 'string', 'exact' => TRUE),
|
||||
'bit varying' => array('type' => 'string'),
|
||||
'char' => array('type' => 'string', 'exact' => TRUE),
|
||||
'char varying' => array('type' => 'string'),
|
||||
'character' => array('type' => 'string', 'exact' => TRUE),
|
||||
'character varying' => array('type' => 'string'),
|
||||
'date' => array('type' => 'string'),
|
||||
'dec' => array('type' => 'float', 'exact' => TRUE),
|
||||
'decimal' => array('type' => 'float', 'exact' => TRUE),
|
||||
'double precision' => array('type' => 'float'),
|
||||
'float' => array('type' => 'float'),
|
||||
'int' => array('type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'),
|
||||
'integer' => array('type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'),
|
||||
'interval' => array('type' => 'string'),
|
||||
'national char' => array('type' => 'string', 'exact' => TRUE),
|
||||
'national char varying' => array('type' => 'string'),
|
||||
'national character' => array('type' => 'string', 'exact' => TRUE),
|
||||
'national character varying' => array('type' => 'string'),
|
||||
'nchar' => array('type' => 'string', 'exact' => TRUE),
|
||||
'nchar varying' => array('type' => 'string'),
|
||||
'numeric' => array('type' => 'float', 'exact' => TRUE),
|
||||
'real' => array('type' => 'float'),
|
||||
'smallint' => array('type' => 'int', 'min' => '-32768', 'max' => '32767'),
|
||||
'time' => array('type' => 'string'),
|
||||
'time with time zone' => array('type' => 'string'),
|
||||
'timestamp' => array('type' => 'string'),
|
||||
'timestamp with time zone' => array('type' => 'string'),
|
||||
'varchar' => array('type' => 'string'),
|
||||
|
||||
// SQL:1999
|
||||
'binary large object' => array('type' => 'string', 'binary' => TRUE),
|
||||
'blob' => array('type' => 'string', 'binary' => TRUE),
|
||||
'boolean' => array('type' => 'bool'),
|
||||
'char large object' => array('type' => 'string'),
|
||||
'character large object' => array('type' => 'string'),
|
||||
'clob' => array('type' => 'string'),
|
||||
'national character large object' => array('type' => 'string'),
|
||||
'nchar large object' => array('type' => 'string'),
|
||||
'nclob' => array('type' => 'string'),
|
||||
'time without time zone' => array('type' => 'string'),
|
||||
'timestamp without time zone' => array('type' => 'string'),
|
||||
|
||||
// SQL:2003
|
||||
'bigint' => array('type' => 'int', 'min' => '-9223372036854775808', 'max' => '9223372036854775807'),
|
||||
|
||||
// SQL:2008
|
||||
'binary' => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE),
|
||||
'binary varying' => array('type' => 'string', 'binary' => TRUE),
|
||||
'varbinary' => array('type' => 'string', 'binary' => TRUE),
|
||||
);
|
||||
|
||||
if (isset($types[$type]))
|
||||
return $types[$type];
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all of the tables in the database. Optionally, a LIKE string can
|
||||
* be used to search for specific tables.
|
||||
*
|
||||
* // Get all tables in the current database
|
||||
* $tables = $db->list_tables();
|
||||
*
|
||||
* // Get all user-related tables
|
||||
* $tables = $db->list_tables('user%');
|
||||
*
|
||||
* @param string table to search for
|
||||
* @return array
|
||||
*/
|
||||
abstract public function list_tables($like = NULL);
|
||||
|
||||
/**
|
||||
* Lists all of the columns in a table. Optionally, a LIKE string can be
|
||||
* used to search for specific fields.
|
||||
*
|
||||
* // Get all columns from the "users" table
|
||||
* $columns = $db->list_columns('users');
|
||||
*
|
||||
* // Get all name-related columns
|
||||
* $columns = $db->list_columns('users', '%name%');
|
||||
*
|
||||
* @param string table to get columns from
|
||||
* @param string column to search for
|
||||
* @return array
|
||||
*/
|
||||
abstract public function list_columns($table, $like = NULL);
|
||||
|
||||
/**
|
||||
* Extracts the text between parentheses, if any.
|
||||
*
|
||||
* // Returns: array('CHAR', '6')
|
||||
* list($type, $length) = $db->_parse_type('CHAR(6)');
|
||||
*
|
||||
* @param string
|
||||
* @return array list containing the type and length, if any
|
||||
*/
|
||||
protected function _parse_type($type)
|
||||
{
|
||||
if (($open = strpos($type, '(')) === FALSE)
|
||||
{
|
||||
// No length specified
|
||||
return array($type, NULL);
|
||||
}
|
||||
|
||||
// Closing parenthesis
|
||||
$close = strpos($type, ')', $open);
|
||||
|
||||
// Length without parentheses
|
||||
$length = substr($type, $open + 1, $close - 1 - $open);
|
||||
|
||||
// Type without the length
|
||||
$type = substr($type, 0, $open).substr($type, $close + 1);
|
||||
|
||||
return array($type, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table prefix defined in the current configuration.
|
||||
*
|
||||
* $prefix = $db->table_prefix();
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function table_prefix()
|
||||
{
|
||||
return $this->_config['table_prefix'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote a value for an SQL query.
|
||||
*
|
||||
* $db->quote(NULL); // 'NULL'
|
||||
* $db->quote(10); // 10
|
||||
* $db->quote('fred'); // 'fred'
|
||||
*
|
||||
* Objects passed to this function will be converted to strings.
|
||||
* [Database_Expression] objects will use the value of the expression.
|
||||
* [Database_Query] objects will be compiled and converted to a sub-query.
|
||||
* All other objects will be converted using the `__toString` method.
|
||||
*
|
||||
* @param mixed any value to quote
|
||||
* @return string
|
||||
* @uses Database::escape
|
||||
*/
|
||||
public function quote($value)
|
||||
{
|
||||
if ($value === NULL)
|
||||
{
|
||||
return 'NULL';
|
||||
}
|
||||
elseif ($value === TRUE)
|
||||
{
|
||||
return "'1'";
|
||||
}
|
||||
elseif ($value === FALSE)
|
||||
{
|
||||
return "'0'";
|
||||
}
|
||||
elseif (is_object($value))
|
||||
{
|
||||
if ($value instanceof Database_Query)
|
||||
{
|
||||
// Create a sub-query
|
||||
return '('.$value->compile($this).')';
|
||||
}
|
||||
elseif ($value instanceof Database_Expression)
|
||||
{
|
||||
// Use a raw expression
|
||||
return $value->value();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert the object to a string
|
||||
return $this->quote((string) $value);
|
||||
}
|
||||
}
|
||||
elseif (is_array($value))
|
||||
{
|
||||
return '('.implode(', ', array_map(array($this, __FUNCTION__), $value)).')';
|
||||
}
|
||||
elseif (is_int($value))
|
||||
{
|
||||
return (int) $value;
|
||||
}
|
||||
elseif (is_float($value))
|
||||
{
|
||||
// Convert to non-locale aware float to prevent possible commas
|
||||
return sprintf('%F', $value);
|
||||
}
|
||||
|
||||
return $this->escape($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote a database table name and adds the table prefix if needed.
|
||||
*
|
||||
* $table = $db->quote_table($table);
|
||||
*
|
||||
* @param mixed table name or array(table, alias)
|
||||
* @return string
|
||||
* @uses Database::quote_identifier
|
||||
* @uses Database::table_prefix
|
||||
*/
|
||||
public function quote_table($value)
|
||||
{
|
||||
// Assign the table by reference from the value
|
||||
if (is_array($value))
|
||||
{
|
||||
$table =& $value[0];
|
||||
|
||||
// Attach table prefix to alias
|
||||
$value[1] = $this->table_prefix().$value[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$table =& $value;
|
||||
}
|
||||
|
||||
if (is_string($table) AND strpos($table, '.') === FALSE)
|
||||
{
|
||||
// Add the table prefix for tables
|
||||
$table = $this->table_prefix().$table;
|
||||
}
|
||||
|
||||
return $this->quote_identifier($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote a database identifier, such as a column name. Adds the
|
||||
* table prefix to the identifier if a table name is present.
|
||||
*
|
||||
* $column = $db->quote_identifier($column);
|
||||
*
|
||||
* You can also use SQL methods within identifiers.
|
||||
*
|
||||
* // The value of "column" will be quoted
|
||||
* $column = $db->quote_identifier('COUNT("column")');
|
||||
*
|
||||
* Objects passed to this function will be converted to strings.
|
||||
* [Database_Expression] objects will use the value of the expression.
|
||||
* [Database_Query] objects will be compiled and converted to a sub-query.
|
||||
* All other objects will be converted using the `__toString` method.
|
||||
*
|
||||
* @param mixed any identifier
|
||||
* @return string
|
||||
* @uses Database::table_prefix
|
||||
*/
|
||||
public function quote_identifier($value)
|
||||
{
|
||||
if ($value === '*')
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
elseif (is_object($value))
|
||||
{
|
||||
if ($value instanceof Database_Query)
|
||||
{
|
||||
// Create a sub-query
|
||||
return '('.$value->compile($this).')';
|
||||
}
|
||||
elseif ($value instanceof Database_Expression)
|
||||
{
|
||||
// Use a raw expression
|
||||
return $value->value();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert the object to a string
|
||||
return $this->quote_identifier((string) $value);
|
||||
}
|
||||
}
|
||||
elseif (is_array($value))
|
||||
{
|
||||
// Separate the column and alias
|
||||
list ($value, $alias) = $value;
|
||||
|
||||
return $this->quote_identifier($value).' AS '.$this->quote_identifier($alias);
|
||||
}
|
||||
|
||||
if (strpos($value, '"') !== FALSE)
|
||||
{
|
||||
// Quote the column in FUNC("ident") identifiers
|
||||
return preg_replace('/"(.+?)"/e', '$this->quote_identifier("$1")', $value);
|
||||
}
|
||||
elseif (strpos($value, '.') !== FALSE)
|
||||
{
|
||||
// Split the identifier into the individual parts
|
||||
$parts = explode('.', $value);
|
||||
|
||||
if ($prefix = $this->table_prefix())
|
||||
{
|
||||
// Get the offset of the table name, 2nd-to-last part
|
||||
// This works for databases that can have 3 identifiers (Postgre)
|
||||
$offset = count($parts) - 2;
|
||||
|
||||
// Add the table prefix to the table name
|
||||
$parts[$offset] = $prefix.$parts[$offset];
|
||||
}
|
||||
|
||||
// Quote each of the parts
|
||||
return implode('.', array_map(array($this, __FUNCTION__), $parts));
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->_identifier.$value.$this->_identifier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a string by escaping characters that could cause an SQL
|
||||
* injection attack.
|
||||
*
|
||||
* $value = $db->escape('any string');
|
||||
*
|
||||
* @param string value to quote
|
||||
* @return string
|
||||
*/
|
||||
abstract public function escape($value);
|
||||
|
||||
} // End Database_Connection
|
@ -0,0 +1,11 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database exceptions.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Exceptions
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Exception extends Kohana_Exception {}
|
@ -0,0 +1,60 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database expressions can be used to add unescaped SQL fragments to a
|
||||
* [Database_Query_Builder] object.
|
||||
*
|
||||
* For example, you can use an expression to generate a column alias:
|
||||
*
|
||||
* // SELECT CONCAT(first_name, last_name) AS full_name
|
||||
* $query = DB::select(array(DB::expr('CONCAT(first_name, last_name)'), 'full_name')));
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Base
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Expression {
|
||||
|
||||
// Raw expression string
|
||||
protected $_value;
|
||||
|
||||
/**
|
||||
* Sets the expression string.
|
||||
*
|
||||
* $expression = new Database_Expression('COUNT(users.id)');
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
// Set the expression string
|
||||
$this->_value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expression value as a string.
|
||||
*
|
||||
* $sql = $expression->value();
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function value()
|
||||
{
|
||||
return (string) $this->_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the expression as a string.
|
||||
*
|
||||
* echo $expression;
|
||||
*
|
||||
* @return string
|
||||
* @uses Database_Expression::value
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->value();
|
||||
}
|
||||
|
||||
} // End Database_Expression
|
@ -0,0 +1,381 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* MySQL database connection.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Drivers
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_MySQL extends Database {
|
||||
|
||||
// Database in use by each connection
|
||||
protected static $_current_databases = array();
|
||||
|
||||
// Use SET NAMES to set the character set
|
||||
protected static $_set_names;
|
||||
|
||||
// Identifier for this connection within the PHP driver
|
||||
protected $_connection_id;
|
||||
|
||||
// MySQL uses a backtick for identifiers
|
||||
protected $_identifier = '`';
|
||||
|
||||
public function connect()
|
||||
{
|
||||
if ($this->_connection)
|
||||
return;
|
||||
|
||||
if (Database_MySQL::$_set_names === NULL)
|
||||
{
|
||||
// Determine if we can use mysql_set_charset(), which is only
|
||||
// available on PHP 5.2.3+ when compiled against MySQL 5.0+
|
||||
Database_MySQL::$_set_names = ! function_exists('mysql_set_charset');
|
||||
}
|
||||
|
||||
// Extract the connection parameters, adding required variabels
|
||||
extract($this->_config['connection'] + array(
|
||||
'database' => '',
|
||||
'hostname' => '',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'persistent' => FALSE,
|
||||
));
|
||||
|
||||
// Prevent this information from showing up in traces
|
||||
unset($this->_config['connection']['username'], $this->_config['connection']['password']);
|
||||
|
||||
try
|
||||
{
|
||||
if ($persistent)
|
||||
{
|
||||
// Create a persistent connection
|
||||
$this->_connection = mysql_pconnect($hostname, $username, $password);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a connection and force it to be a new link
|
||||
$this->_connection = mysql_connect($hostname, $username, $password, TRUE);
|
||||
}
|
||||
}
|
||||
catch (ErrorException $e)
|
||||
{
|
||||
// No connection exists
|
||||
$this->_connection = NULL;
|
||||
|
||||
throw new Database_Exception(':error', array(
|
||||
':error' => mysql_error(),
|
||||
),
|
||||
mysql_errno());
|
||||
}
|
||||
|
||||
// \xFF is a better delimiter, but the PHP driver uses underscore
|
||||
$this->_connection_id = sha1($hostname.'_'.$username.'_'.$password);
|
||||
|
||||
$this->_select_db($database);
|
||||
|
||||
if ( ! empty($this->_config['charset']))
|
||||
{
|
||||
// Set the character set
|
||||
$this->set_charset($this->_config['charset']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the database
|
||||
*
|
||||
* @param string Database
|
||||
* @return void
|
||||
*/
|
||||
protected function _select_db($database)
|
||||
{
|
||||
if ( ! mysql_select_db($database, $this->_connection))
|
||||
{
|
||||
// Unable to select database
|
||||
throw new Database_Exception(':error',
|
||||
array(':error' => mysql_error($this->_connection)),
|
||||
mysql_errno($this->_connection));
|
||||
}
|
||||
|
||||
Database_MySQL::$_current_databases[$this->_connection_id] = $database;
|
||||
}
|
||||
|
||||
public function disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Database is assumed disconnected
|
||||
$status = TRUE;
|
||||
|
||||
if (is_resource($this->_connection))
|
||||
{
|
||||
if ($status = mysql_close($this->_connection))
|
||||
{
|
||||
// Clear the connection
|
||||
$this->_connection = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Database is probably not disconnected
|
||||
$status = ! is_resource($this->_connection);
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function set_charset($charset)
|
||||
{
|
||||
// Make sure the database is connected
|
||||
$this->_connection or $this->connect();
|
||||
|
||||
if (Database_MySQL::$_set_names === TRUE)
|
||||
{
|
||||
// PHP is compiled against MySQL 4.x
|
||||
$status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->_connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
// PHP is compiled against MySQL 5.x
|
||||
$status = mysql_set_charset($charset, $this->_connection);
|
||||
}
|
||||
|
||||
if ($status === FALSE)
|
||||
{
|
||||
throw new Database_Exception(':error',
|
||||
array(':error' => mysql_error($this->_connection)),
|
||||
mysql_errno($this->_connection));
|
||||
}
|
||||
}
|
||||
|
||||
public function query($type, $sql, $as_object)
|
||||
{
|
||||
// Make sure the database is connected
|
||||
$this->_connection or $this->connect();
|
||||
|
||||
if ( ! empty($this->_config['profiling']))
|
||||
{
|
||||
// Benchmark this query for the current instance
|
||||
$benchmark = Profiler::start("Database ({$this->_instance})", $sql);
|
||||
}
|
||||
|
||||
if ( ! empty($this->_config['connection']['persistent']) AND $this->_config['connection']['database'] !== Database_MySQL::$_current_databases[$this->_connection_id])
|
||||
{
|
||||
// Select database on persistent connections
|
||||
$this->_select_db($this->_config['connection']['database']);
|
||||
}
|
||||
|
||||
// Execute the query
|
||||
if (($result = mysql_query($sql, $this->_connection)) === FALSE)
|
||||
{
|
||||
if (isset($benchmark))
|
||||
{
|
||||
// This benchmark is worthless
|
||||
Profiler::delete($benchmark);
|
||||
}
|
||||
|
||||
throw new Database_Exception(':error [ :query ]',
|
||||
array(':error' => mysql_error($this->_connection), ':query' => $sql),
|
||||
mysql_errno($this->_connection));
|
||||
}
|
||||
|
||||
if (isset($benchmark))
|
||||
{
|
||||
Profiler::stop($benchmark);
|
||||
}
|
||||
|
||||
// Set the last query
|
||||
$this->last_query = $sql;
|
||||
|
||||
if ($type === Database::SELECT)
|
||||
{
|
||||
// Return an iterator of results
|
||||
return new Database_MySQL_Result($result, $sql, $as_object);
|
||||
}
|
||||
elseif ($type === Database::INSERT)
|
||||
{
|
||||
// Return a list of insert id and rows created
|
||||
return array(
|
||||
mysql_insert_id($this->_connection),
|
||||
mysql_affected_rows($this->_connection),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return the number of rows affected
|
||||
return mysql_affected_rows($this->_connection);
|
||||
}
|
||||
}
|
||||
|
||||
public function datatype($type)
|
||||
{
|
||||
static $types = array
|
||||
(
|
||||
'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'),
|
||||
'bool' => array('type' => 'bool'),
|
||||
'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'),
|
||||
'datetime' => array('type' => 'string'),
|
||||
'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
|
||||
'double' => array('type' => 'float'),
|
||||
'double precision unsigned' => array('type' => 'float', 'min' => '0'),
|
||||
'double unsigned' => array('type' => 'float', 'min' => '0'),
|
||||
'enum' => array('type' => 'string'),
|
||||
'fixed' => array('type' => 'float', 'exact' => TRUE),
|
||||
'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
|
||||
'float unsigned' => array('type' => 'float', 'min' => '0'),
|
||||
'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
|
||||
'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'),
|
||||
'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'),
|
||||
'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'),
|
||||
'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'),
|
||||
'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'),
|
||||
'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'),
|
||||
'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'),
|
||||
'national varchar' => array('type' => 'string'),
|
||||
'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'),
|
||||
'nvarchar' => array('type' => 'string'),
|
||||
'point' => array('type' => 'string', 'binary' => TRUE),
|
||||
'real unsigned' => array('type' => 'float', 'min' => '0'),
|
||||
'set' => array('type' => 'string'),
|
||||
'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'),
|
||||
'text' => array('type' => 'string', 'character_maximum_length' => '65535'),
|
||||
'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'),
|
||||
'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'),
|
||||
'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'),
|
||||
'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'),
|
||||
'year' => array('type' => 'string'),
|
||||
);
|
||||
|
||||
$type = str_replace(' zerofill', '', $type);
|
||||
|
||||
if (isset($types[$type]))
|
||||
return $types[$type];
|
||||
|
||||
return parent::datatype($type);
|
||||
}
|
||||
|
||||
public function list_tables($like = NULL)
|
||||
{
|
||||
if (is_string($like))
|
||||
{
|
||||
// Search for table names
|
||||
$result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find all table names
|
||||
$result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE);
|
||||
}
|
||||
|
||||
$tables = array();
|
||||
foreach ($result as $row)
|
||||
{
|
||||
$tables[] = reset($row);
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
public function list_columns($table, $like = NULL)
|
||||
{
|
||||
// Quote the table name
|
||||
$table = $this->quote_table($table);
|
||||
|
||||
if (is_string($like))
|
||||
{
|
||||
// Search for column names
|
||||
$result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find all column names
|
||||
$result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE);
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$columns = array();
|
||||
foreach ($result as $row)
|
||||
{
|
||||
list($type, $length) = $this->_parse_type($row['Type']);
|
||||
|
||||
$column = $this->datatype($type);
|
||||
|
||||
$column['column_name'] = $row['Field'];
|
||||
$column['column_default'] = $row['Default'];
|
||||
$column['data_type'] = $type;
|
||||
$column['is_nullable'] = ($row['Null'] == 'YES');
|
||||
$column['ordinal_position'] = ++$count;
|
||||
|
||||
switch ($column['type'])
|
||||
{
|
||||
case 'float':
|
||||
if (isset($length))
|
||||
{
|
||||
list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
if (isset($length))
|
||||
{
|
||||
// MySQL attribute
|
||||
$column['display'] = $length;
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
switch ($column['data_type'])
|
||||
{
|
||||
case 'binary':
|
||||
case 'varbinary':
|
||||
$column['character_maximum_length'] = $length;
|
||||
break;
|
||||
|
||||
case 'char':
|
||||
case 'varchar':
|
||||
$column['character_maximum_length'] = $length;
|
||||
case 'text':
|
||||
case 'tinytext':
|
||||
case 'mediumtext':
|
||||
case 'longtext':
|
||||
$column['collation_name'] = $row['Collation'];
|
||||
break;
|
||||
|
||||
case 'enum':
|
||||
case 'set':
|
||||
$column['collation_name'] = $row['Collation'];
|
||||
$column['options'] = explode('\',\'', substr($length, 1, -1));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// MySQL attributes
|
||||
$column['comment'] = $row['Comment'];
|
||||
$column['extra'] = $row['Extra'];
|
||||
$column['key'] = $row['Key'];
|
||||
$column['privileges'] = $row['Privileges'];
|
||||
|
||||
$columns[$row['Field']] = $column;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public function escape($value)
|
||||
{
|
||||
// Make sure the database is connected
|
||||
$this->_connection or $this->connect();
|
||||
|
||||
if (($value = mysql_real_escape_string((string) $value, $this->_connection)) === FALSE)
|
||||
{
|
||||
throw new Database_Exception(':error',
|
||||
array(':error' => mysql_errno($this->_connection)),
|
||||
mysql_error($this->_connection));
|
||||
}
|
||||
|
||||
// SQL standard is to use single-quotes for all values
|
||||
return "'$value'";
|
||||
}
|
||||
|
||||
} // End Database_MySQL
|
@ -0,0 +1,71 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* MySQL database result.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query/Result
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_MySQL_Result extends Database_Result {
|
||||
|
||||
protected $_internal_row = 0;
|
||||
|
||||
public function __construct($result, $sql, $as_object)
|
||||
{
|
||||
parent::__construct($result, $sql, $as_object);
|
||||
|
||||
// Find the number of rows in the result
|
||||
$this->_total_rows = mysql_num_rows($result);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (is_resource($this->_result))
|
||||
{
|
||||
mysql_free_result($this->_result);
|
||||
}
|
||||
}
|
||||
|
||||
public function seek($offset)
|
||||
{
|
||||
if ($this->offsetExists($offset) AND mysql_data_seek($this->_result, $offset))
|
||||
{
|
||||
// Set the current row to the offset
|
||||
$this->_current_row = $this->_internal_row = $offset;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
public function current()
|
||||
{
|
||||
if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row))
|
||||
return FALSE;
|
||||
|
||||
// Increment internal row for optimization assuming rows are fetched in order
|
||||
$this->_internal_row++;
|
||||
|
||||
if ($this->_as_object === TRUE)
|
||||
{
|
||||
// Return an stdClass
|
||||
return mysql_fetch_object($this->_result);
|
||||
}
|
||||
elseif (is_string($this->_as_object))
|
||||
{
|
||||
// Return an object of given class name
|
||||
return mysql_fetch_object($this->_result, $this->_as_object);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return an array of the row
|
||||
return mysql_fetch_assoc($this->_result);
|
||||
}
|
||||
}
|
||||
|
||||
} // End Database_MySQL_Result_Select
|
186
includes/kohana/modules/database/classes/kohana/database/pdo.php
Normal file
186
includes/kohana/modules/database/classes/kohana/database/pdo.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* PDO database connection.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Drivers
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_PDO extends Database {
|
||||
|
||||
// PDO uses no quoting for identifiers
|
||||
protected $_identifier = '';
|
||||
|
||||
protected function __construct($name, array $config)
|
||||
{
|
||||
parent::__construct($name, $config);
|
||||
|
||||
if (isset($this->_config['identifier']))
|
||||
{
|
||||
// Allow the identifier to be overloaded per-connection
|
||||
$this->_identifier = (string) $this->_config['identifier'];
|
||||
}
|
||||
}
|
||||
|
||||
public function connect()
|
||||
{
|
||||
if ($this->_connection)
|
||||
return;
|
||||
|
||||
// Extract the connection parameters, adding required variabels
|
||||
extract($this->_config['connection'] + array(
|
||||
'dsn' => '',
|
||||
'username' => NULL,
|
||||
'password' => NULL,
|
||||
'persistent' => FALSE,
|
||||
));
|
||||
|
||||
// Clear the connection parameters for security
|
||||
unset($this->_config['connection']);
|
||||
|
||||
// Force PDO to use exceptions for all errors
|
||||
$attrs = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
if ( ! empty($persistent))
|
||||
{
|
||||
// Make the connection persistent
|
||||
$attrs[PDO::ATTR_PERSISTENT] = TRUE;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create a new PDO connection
|
||||
$this->_connection = new PDO($dsn, $username, $password, $attrs);
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
throw new Database_Exception(':error', array(
|
||||
':error' => $e->getMessage(),
|
||||
),
|
||||
$e->getCode(),
|
||||
$e);
|
||||
}
|
||||
|
||||
if ( ! empty($this->_config['charset']))
|
||||
{
|
||||
// Set the character set
|
||||
$this->set_charset($this->_config['charset']);
|
||||
}
|
||||
}
|
||||
|
||||
public function disconnect()
|
||||
{
|
||||
// Destroy the PDO object
|
||||
$this->_connection = NULL;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function set_charset($charset)
|
||||
{
|
||||
// Make sure the database is connected
|
||||
$this->_connection or $this->connect();
|
||||
|
||||
// Execute a raw SET NAMES query
|
||||
$this->_connection->exec('SET NAMES '.$this->quote($charset));
|
||||
}
|
||||
|
||||
public function query($type, $sql, $as_object)
|
||||
{
|
||||
// Make sure the database is connected
|
||||
$this->_connection or $this->connect();
|
||||
|
||||
if ( ! empty($this->_config['profiling']))
|
||||
{
|
||||
// Benchmark this query for the current instance
|
||||
$benchmark = Profiler::start("Database ({$this->_instance})", $sql);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$result = $this->_connection->query($sql);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if (isset($benchmark))
|
||||
{
|
||||
// This benchmark is worthless
|
||||
Profiler::delete($benchmark);
|
||||
}
|
||||
|
||||
// Convert the exception in a database exception
|
||||
throw new Database_Exception(':error [ :query ]', array(
|
||||
':error' => $e->getMessage(),
|
||||
':query' => $sql
|
||||
),
|
||||
$e->getCode(),
|
||||
$e);
|
||||
}
|
||||
|
||||
if (isset($benchmark))
|
||||
{
|
||||
Profiler::stop($benchmark);
|
||||
}
|
||||
|
||||
// Set the last query
|
||||
$this->last_query = $sql;
|
||||
|
||||
if ($type === Database::SELECT)
|
||||
{
|
||||
// Convert the result into an array, as PDOStatement::rowCount is not reliable
|
||||
if ($as_object === FALSE)
|
||||
{
|
||||
$result->setFetchMode(PDO::FETCH_ASSOC);
|
||||
}
|
||||
elseif (is_string($as_object))
|
||||
{
|
||||
$result->setFetchMode(PDO::FETCH_CLASS, $as_object);
|
||||
}
|
||||
else
|
||||
{
|
||||
$result->setFetchMode(PDO::FETCH_CLASS, 'stdClass');
|
||||
}
|
||||
|
||||
$result = $result->fetchAll();
|
||||
|
||||
// Return an iterator of results
|
||||
return new Database_Result_Cached($result, $sql, $as_object);
|
||||
}
|
||||
elseif ($type === Database::INSERT)
|
||||
{
|
||||
// Return a list of insert id and rows created
|
||||
return array(
|
||||
$this->_connection->lastInsertId(),
|
||||
$result->rowCount(),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return the number of rows affected
|
||||
return $result->rowCount();
|
||||
}
|
||||
}
|
||||
|
||||
public function list_tables($like = NULL)
|
||||
{
|
||||
throw new Kohana_Exception('Database method :method is not supported by :class',
|
||||
array(':method' => __FUNCTION__, ':class' => __CLASS__));
|
||||
}
|
||||
|
||||
public function list_columns($table, $like = NULL)
|
||||
{
|
||||
throw new Kohana_Exception('Database method :method is not supported by :class',
|
||||
array(':method' => __FUNCTION__, ':class' => __CLASS__));
|
||||
}
|
||||
|
||||
public function escape($value)
|
||||
{
|
||||
// Make sure the database is connected
|
||||
$this->_connection or $this->connect();
|
||||
|
||||
return $this->_connection->quote($value);
|
||||
}
|
||||
|
||||
} // End Database_PDO
|
@ -0,0 +1,218 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database query wrapper.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Query {
|
||||
|
||||
// Query type
|
||||
protected $_type;
|
||||
|
||||
// Cache lifetime
|
||||
protected $_lifetime;
|
||||
|
||||
// SQL statement
|
||||
protected $_sql;
|
||||
|
||||
// Quoted query parameters
|
||||
protected $_parameters = array();
|
||||
|
||||
// Return results as associative arrays or objects
|
||||
protected $_as_object = FALSE;
|
||||
|
||||
/**
|
||||
* Creates a new SQL query of the specified type.
|
||||
*
|
||||
* @param integer query type: Database::SELECT, Database::INSERT, etc
|
||||
* @param string query string
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($type, $sql)
|
||||
{
|
||||
$this->_type = $type;
|
||||
$this->_sql = $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL query string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final public function __toString()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Return the SQL string
|
||||
return $this->compile(Database::instance());
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return Kohana::exception_text($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the query.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return $this->_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the query to be cached for a specified amount of time.
|
||||
*
|
||||
* @param integer number of seconds to cache or null for default
|
||||
* @return $this
|
||||
*/
|
||||
public function cached($lifetime = NULL)
|
||||
{
|
||||
$this->_lifetime = $lifetime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns results as associative arrays
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function as_assoc()
|
||||
{
|
||||
$this->_as_object = FALSE;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns results as objects
|
||||
*
|
||||
* @param string classname or TRUE for stdClass
|
||||
* @return $this
|
||||
*/
|
||||
public function as_object($class = TRUE)
|
||||
{
|
||||
$this->_as_object = $class;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a parameter in the query.
|
||||
*
|
||||
* @param string parameter key to replace
|
||||
* @param mixed value to use
|
||||
* @return $this
|
||||
*/
|
||||
public function param($param, $value)
|
||||
{
|
||||
// Add or overload a new parameter
|
||||
$this->_parameters[$param] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a variable to a parameter in the query.
|
||||
*
|
||||
* @param string parameter key to replace
|
||||
* @param mixed variable to use
|
||||
* @return $this
|
||||
*/
|
||||
public function bind($param, & $var)
|
||||
{
|
||||
// Bind a value to a variable
|
||||
$this->_parameters[$param] =& $var;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple parameters to the query.
|
||||
*
|
||||
* @param array list of parameters
|
||||
* @return $this
|
||||
*/
|
||||
public function parameters(array $params)
|
||||
{
|
||||
// Merge the new parameters in
|
||||
$this->_parameters = $params + $this->_parameters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL query and return it. Replaces any parameters with their
|
||||
* given values.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @return string
|
||||
*/
|
||||
public function compile(Database $db)
|
||||
{
|
||||
// Import the SQL locally
|
||||
$sql = $this->_sql;
|
||||
|
||||
if ( ! empty($this->_parameters))
|
||||
{
|
||||
// Quote all of the values
|
||||
$values = array_map(array($db, 'quote'), $this->_parameters);
|
||||
|
||||
// Replace the values in the SQL
|
||||
$sql = strtr($sql, $values);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the current query on the given database.
|
||||
*
|
||||
* @param mixed Database instance or name of instance
|
||||
* @return object Database_Result for SELECT queries
|
||||
* @return mixed the insert id for INSERT queries
|
||||
* @return integer number of affected rows for all other queries
|
||||
*/
|
||||
public function execute($db = NULL)
|
||||
{
|
||||
if ( ! is_object($db))
|
||||
{
|
||||
// Get the database instance
|
||||
$db = Database::instance($db);
|
||||
}
|
||||
|
||||
// Compile the SQL query
|
||||
$sql = $this->compile($db);
|
||||
|
||||
if ( ! empty($this->_lifetime) AND $this->_type === Database::SELECT)
|
||||
{
|
||||
// Set the cache key based on the database instance name and SQL
|
||||
$cache_key = 'Database::query("'.$db.'", "'.$sql.'")';
|
||||
|
||||
if ($result = Kohana::cache($cache_key, NULL, $this->_lifetime))
|
||||
{
|
||||
// Return a cached result
|
||||
return new Database_Result_Cached($result, $sql, $this->_as_object);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the query
|
||||
$result = $db->query($this->_type, $sql, $this->_as_object);
|
||||
|
||||
if (isset($cache_key))
|
||||
{
|
||||
// Cache the result array
|
||||
Kohana::cache($cache_key, $result->as_array(), $this->_lifetime);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
} // End Database_Query
|
@ -0,0 +1,199 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database query builder.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
abstract class Kohana_Database_Query_Builder extends Database_Query {
|
||||
|
||||
/**
|
||||
* Compiles an array of JOIN statements into an SQL partial.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @param array join statements
|
||||
* @return string
|
||||
*/
|
||||
protected function _compile_join(Database $db, array $joins)
|
||||
{
|
||||
$statements = array();
|
||||
|
||||
foreach ($joins as $join)
|
||||
{
|
||||
// Compile each of the join statements
|
||||
$statements[] = $join->compile($db);
|
||||
}
|
||||
|
||||
return implode(' ', $statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles an array of conditions into an SQL partial. Used for WHERE
|
||||
* and HAVING.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @param array condition statements
|
||||
* @return string
|
||||
*/
|
||||
protected function _compile_conditions(Database $db, array $conditions)
|
||||
{
|
||||
$last_condition = NULL;
|
||||
|
||||
$sql = '';
|
||||
foreach ($conditions as $group)
|
||||
{
|
||||
// Process groups of conditions
|
||||
foreach ($group as $logic => $condition)
|
||||
{
|
||||
if ($condition === '(')
|
||||
{
|
||||
if ( ! empty($sql) AND $last_condition !== '(')
|
||||
{
|
||||
// Include logic operator
|
||||
$sql .= ' '.$logic.' ';
|
||||
}
|
||||
|
||||
$sql .= '(';
|
||||
}
|
||||
elseif ($condition === ')')
|
||||
{
|
||||
$sql .= ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( ! empty($sql) AND $last_condition !== '(')
|
||||
{
|
||||
// Add the logic operator
|
||||
$sql .= ' '.$logic.' ';
|
||||
}
|
||||
|
||||
// Split the condition
|
||||
list($column, $op, $value) = $condition;
|
||||
|
||||
if ($value === NULL)
|
||||
{
|
||||
if ($op === '=')
|
||||
{
|
||||
// Convert "val = NULL" to "val IS NULL"
|
||||
$op = 'IS';
|
||||
}
|
||||
elseif ($op === '!=')
|
||||
{
|
||||
// Convert "val != NULL" to "valu IS NOT NULL"
|
||||
$op = 'IS NOT';
|
||||
}
|
||||
}
|
||||
|
||||
// Database operators are always uppercase
|
||||
$op = strtoupper($op);
|
||||
|
||||
if ($op === 'BETWEEN' AND is_array($value))
|
||||
{
|
||||
// BETWEEN always has exactly two arguments
|
||||
list($min, $max) = $value;
|
||||
|
||||
if (is_string($min) AND array_key_exists($min, $this->_parameters))
|
||||
{
|
||||
// Set the parameter as the minimum
|
||||
$min = $this->_parameters[$min];
|
||||
}
|
||||
|
||||
if (is_string($max) AND array_key_exists($max, $this->_parameters))
|
||||
{
|
||||
// Set the parameter as the maximum
|
||||
$max = $this->_parameters[$max];
|
||||
}
|
||||
|
||||
// Quote the min and max value
|
||||
$value = $db->quote($min).' AND '.$db->quote($max);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_string($value) AND array_key_exists($value, $this->_parameters))
|
||||
{
|
||||
// Set the parameter as the value
|
||||
$value = $this->_parameters[$value];
|
||||
}
|
||||
|
||||
// Quote the entire value normally
|
||||
$value = $db->quote($value);
|
||||
}
|
||||
|
||||
// Append the statement to the query
|
||||
$sql .= $db->quote_identifier($column).' '.$op.' '.$value;
|
||||
}
|
||||
|
||||
$last_condition = $condition;
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles an array of set values into an SQL partial. Used for UPDATE.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @param array updated values
|
||||
* @return string
|
||||
*/
|
||||
protected function _compile_set(Database $db, array $values)
|
||||
{
|
||||
$set = array();
|
||||
foreach ($values as $group)
|
||||
{
|
||||
// Split the set
|
||||
list ($column, $value) = $group;
|
||||
|
||||
// Quote the column name
|
||||
$column = $db->quote_identifier($column);
|
||||
|
||||
if (is_string($value) AND array_key_exists($value, $this->_parameters))
|
||||
{
|
||||
// Use the parameter value
|
||||
$value = $this->_parameters[$value];
|
||||
}
|
||||
|
||||
$set[$column] = $column.' = '.$db->quote($value);
|
||||
}
|
||||
|
||||
return implode(', ', $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles an array of ORDER BY statements into an SQL partial.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @param array sorting columns
|
||||
* @return string
|
||||
*/
|
||||
protected function _compile_order_by(Database $db, array $columns)
|
||||
{
|
||||
$sort = array();
|
||||
foreach ($columns as $group)
|
||||
{
|
||||
list ($column, $direction) = $group;
|
||||
|
||||
if ( ! empty($direction))
|
||||
{
|
||||
// Make the direction uppercase
|
||||
$direction = ' '.strtoupper($direction);
|
||||
}
|
||||
|
||||
$sort[] = $db->quote_identifier($column).$direction;
|
||||
}
|
||||
|
||||
return 'ORDER BY '.implode(', ', $sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current builder status.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
abstract public function reset();
|
||||
|
||||
} // End Database_Query_Builder
|
@ -0,0 +1,89 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database query builder for DELETE statements.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Query_Builder_Delete extends Database_Query_Builder_Where {
|
||||
|
||||
// DELETE FROM ...
|
||||
protected $_table;
|
||||
|
||||
/**
|
||||
* Set the table for a delete.
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($table = NULL)
|
||||
{
|
||||
if ($table)
|
||||
{
|
||||
// Set the inital table name
|
||||
$this->_table = $table;
|
||||
}
|
||||
|
||||
// Start the query with no SQL
|
||||
return parent::__construct(Database::DELETE, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the table to delete from.
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @return $this
|
||||
*/
|
||||
public function table($table)
|
||||
{
|
||||
$this->_table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL query and return it.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @return string
|
||||
*/
|
||||
public function compile(Database $db)
|
||||
{
|
||||
// Start a deletion query
|
||||
$query = 'DELETE FROM '.$db->quote_table($this->_table);
|
||||
|
||||
if ( ! empty($this->_where))
|
||||
{
|
||||
// Add deletion conditions
|
||||
$query .= ' WHERE '.$this->_compile_conditions($db, $this->_where);
|
||||
}
|
||||
|
||||
if ( ! empty($this->_order_by))
|
||||
{
|
||||
// Add sorting
|
||||
$query .= ' '.$this->_compile_order_by($db, $this->_order_by);
|
||||
}
|
||||
|
||||
if ($this->_limit !== NULL)
|
||||
{
|
||||
// Add limiting
|
||||
$query .= ' LIMIT '.$this->_limit;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->_table = NULL;
|
||||
$this->_where = array();
|
||||
|
||||
$this->_parameters = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
} // End Database_Query_Builder_Delete
|
@ -0,0 +1,171 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database query builder for INSERT statements.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Query_Builder_Insert extends Database_Query_Builder {
|
||||
|
||||
// INSERT INTO ...
|
||||
protected $_table;
|
||||
|
||||
// (...)
|
||||
protected $_columns = array();
|
||||
|
||||
// VALUES (...)
|
||||
protected $_values = array();
|
||||
|
||||
/**
|
||||
* Set the table and columns for an insert.
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @param array column names
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($table = NULL, array $columns = NULL)
|
||||
{
|
||||
if ($table)
|
||||
{
|
||||
// Set the inital table name
|
||||
$this->_table = $table;
|
||||
}
|
||||
|
||||
if ($columns)
|
||||
{
|
||||
// Set the column names
|
||||
$this->_columns = $columns;
|
||||
}
|
||||
|
||||
// Start the query with no SQL
|
||||
return parent::__construct(Database::INSERT, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the table to insert into.
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @return $this
|
||||
*/
|
||||
public function table($table)
|
||||
{
|
||||
$this->_table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the columns that will be inserted.
|
||||
*
|
||||
* @param array column names
|
||||
* @return $this
|
||||
*/
|
||||
public function columns(array $columns)
|
||||
{
|
||||
$this->_columns = $columns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or overwrites values. Multiple value sets can be added.
|
||||
*
|
||||
* @param array values list
|
||||
* @param ...
|
||||
* @return $this
|
||||
*/
|
||||
public function values(array $values)
|
||||
{
|
||||
if ( ! is_array($this->_values))
|
||||
{
|
||||
throw new Kohana_Exception('INSERT INTO ... SELECT statements cannot be combined with INSERT INTO ... VALUES');
|
||||
}
|
||||
|
||||
// Get all of the passed values
|
||||
$values = func_get_args();
|
||||
|
||||
$this->_values = array_merge($this->_values, $values);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a sub-query to for the inserted values.
|
||||
*
|
||||
* @param object Database_Query of SELECT type
|
||||
* @return $this
|
||||
*/
|
||||
public function select(Database_Query $query)
|
||||
{
|
||||
if ($query->type() !== Database::SELECT)
|
||||
{
|
||||
throw new Kohana_Exception('Only SELECT queries can be combined with INSERT queries');
|
||||
}
|
||||
|
||||
$this->_values = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL query and return it.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @return string
|
||||
*/
|
||||
public function compile(Database $db)
|
||||
{
|
||||
// Start an insertion query
|
||||
$query = 'INSERT INTO '.$db->quote_table($this->_table);
|
||||
|
||||
// Add the column names
|
||||
$query .= ' ('.implode(', ', array_map(array($db, 'quote_identifier'), $this->_columns)).') ';
|
||||
|
||||
if (is_array($this->_values))
|
||||
{
|
||||
// Callback for quoting values
|
||||
$quote = array($db, 'quote');
|
||||
|
||||
$groups = array();
|
||||
foreach ($this->_values as $group)
|
||||
{
|
||||
foreach ($group as $i => $value)
|
||||
{
|
||||
if (is_string($value) AND isset($this->_parameters[$value]))
|
||||
{
|
||||
// Use the parameter value
|
||||
$group[$i] = $this->_parameters[$value];
|
||||
}
|
||||
}
|
||||
|
||||
$groups[] = '('.implode(', ', array_map($quote, $group)).')';
|
||||
}
|
||||
|
||||
// Add the values
|
||||
$query .= 'VALUES '.implode(', ', $groups);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the sub-query
|
||||
$query .= (string) $this->_values;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->_table = NULL;
|
||||
|
||||
$this->_columns =
|
||||
$this->_values = array();
|
||||
|
||||
$this->_parameters = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
} // End Database_Query_Builder_Insert
|
@ -0,0 +1,107 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database query builder for JOIN statements.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Query_Builder_Join extends Database_Query_Builder {
|
||||
|
||||
// Type of JOIN
|
||||
protected $_type;
|
||||
|
||||
// JOIN ...
|
||||
protected $_table;
|
||||
|
||||
// ON ...
|
||||
protected $_on = array();
|
||||
|
||||
/**
|
||||
* Creates a new JOIN statement for a table. Optionally, the type of JOIN
|
||||
* can be specified as the second parameter.
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param string type of JOIN: INNER, RIGHT, LEFT, etc
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($table, $type = NULL)
|
||||
{
|
||||
// Set the table to JOIN on
|
||||
$this->_table = $table;
|
||||
|
||||
if ($type !== NULL)
|
||||
{
|
||||
// Set the JOIN type
|
||||
$this->_type = (string) $type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new condition for joining.
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param string logic operator
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @return $this
|
||||
*/
|
||||
public function on($c1, $op, $c2)
|
||||
{
|
||||
$this->_on[] = array($c1, $op, $c2);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL partial for a JOIN statement and return it.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @return string
|
||||
*/
|
||||
public function compile(Database $db)
|
||||
{
|
||||
if ($this->_type)
|
||||
{
|
||||
$sql = strtoupper($this->_type).' JOIN';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sql = 'JOIN';
|
||||
}
|
||||
|
||||
// Quote the table name that is being joined
|
||||
$sql .= ' '.$db->quote_table($this->_table).' ON ';
|
||||
|
||||
$conditions = array();
|
||||
foreach ($this->_on as $condition)
|
||||
{
|
||||
// Split the condition
|
||||
list($c1, $op, $c2) = $condition;
|
||||
|
||||
if ($op)
|
||||
{
|
||||
// Make the operator uppercase and spaced
|
||||
$op = ' '.strtoupper($op);
|
||||
}
|
||||
|
||||
// Quote each of the identifiers used for the condition
|
||||
$conditions[] = $db->quote_identifier($c1).$op.' '.$db->quote_identifier($c2);
|
||||
}
|
||||
|
||||
// Concat the conditions "... AND ..."
|
||||
$sql .= '('.implode(' AND ', $conditions).')';
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->_type =
|
||||
$this->_table = NULL;
|
||||
|
||||
$this->_on = array();
|
||||
}
|
||||
|
||||
} // End Database_Query_Builder_Join
|
@ -0,0 +1,388 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database query builder for SELECT statements.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Query_Builder_Select extends Database_Query_Builder_Where {
|
||||
|
||||
// SELECT ...
|
||||
protected $_select = array();
|
||||
|
||||
// DISTINCT
|
||||
protected $_distinct = FALSE;
|
||||
|
||||
// FROM ...
|
||||
protected $_from = array();
|
||||
|
||||
// JOIN ...
|
||||
protected $_join = array();
|
||||
|
||||
// GROUP BY ...
|
||||
protected $_group_by = array();
|
||||
|
||||
// HAVING ...
|
||||
protected $_having = array();
|
||||
|
||||
// OFFSET ...
|
||||
protected $_offset = NULL;
|
||||
|
||||
// The last JOIN statement created
|
||||
protected $_last_join;
|
||||
|
||||
/**
|
||||
* Sets the initial columns to select from.
|
||||
*
|
||||
* @param array column list
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $columns = NULL)
|
||||
{
|
||||
if ( ! empty($columns))
|
||||
{
|
||||
// Set the initial columns
|
||||
$this->_select = $columns;
|
||||
}
|
||||
|
||||
// Start the query with no actual SQL statement
|
||||
parent::__construct(Database::SELECT, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables selecting only unique columns using "SELECT DISTINCT"
|
||||
*
|
||||
* @param boolean enable or disable distinct columns
|
||||
* @return $this
|
||||
*/
|
||||
public function distinct($value)
|
||||
{
|
||||
$this->_distinct = (bool) $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the columns to select from.
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param ...
|
||||
* @return $this
|
||||
*/
|
||||
public function select($columns = NULL)
|
||||
{
|
||||
$columns = func_get_args();
|
||||
|
||||
$this->_select = array_merge($this->_select, $columns);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the columns to select from, using an array.
|
||||
*
|
||||
* @param array list of column names or aliases
|
||||
* @return $this
|
||||
*/
|
||||
public function select_array(array $columns)
|
||||
{
|
||||
$this->_select = array_merge($this->_select, $columns);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the tables to select "FROM ..."
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @param ...
|
||||
* @return $this
|
||||
*/
|
||||
public function from($tables)
|
||||
{
|
||||
$tables = func_get_args();
|
||||
|
||||
$this->_from = array_merge($this->_from, $tables);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds addition tables to "JOIN ...".
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param string join type (LEFT, RIGHT, INNER, etc)
|
||||
* @return $this
|
||||
*/
|
||||
public function join($table, $type = NULL)
|
||||
{
|
||||
$this->_join[] = $this->_last_join = new Database_Query_Builder_Join($table, $type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds "ON ..." conditions for the last created JOIN statement.
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param string logic operator
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @return $this
|
||||
*/
|
||||
public function on($c1, $op, $c2)
|
||||
{
|
||||
$this->_last_join->on($c1, $op, $c2);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "GROUP BY ..." filter.
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param ...
|
||||
* @return $this
|
||||
*/
|
||||
public function group_by($columns)
|
||||
{
|
||||
$columns = func_get_args();
|
||||
|
||||
$this->_group_by = array_merge($this->_group_by, $columns);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of and_having()
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param string logic operator
|
||||
* @param mixed column value
|
||||
* @return $this
|
||||
*/
|
||||
public function having($column, $op, $value = NULL)
|
||||
{
|
||||
return $this->and_having($column, $op, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new "AND HAVING" condition for the query.
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param string logic operator
|
||||
* @param mixed column value
|
||||
* @return $this
|
||||
*/
|
||||
public function and_having($column, $op, $value = NULL)
|
||||
{
|
||||
$this->_having[] = array('AND' => array($column, $op, $value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new "OR HAVING" condition for the query.
|
||||
*
|
||||
* @param mixed column name or array($column, $alias) or object
|
||||
* @param string logic operator
|
||||
* @param mixed column value
|
||||
* @return $this
|
||||
*/
|
||||
public function or_having($column, $op, $value = NULL)
|
||||
{
|
||||
$this->_having[] = array('OR' => array($column, $op, $value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of and_having_open()
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function having_open()
|
||||
{
|
||||
return $this->and_having_open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new "AND HAVING (...)" grouping.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function and_having_open()
|
||||
{
|
||||
$this->_having[] = array('AND' => '(');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new "OR HAVING (...)" grouping.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function or_having_open()
|
||||
{
|
||||
$this->_having[] = array('OR' => '(');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an open "AND HAVING (...)" grouping.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function having_close()
|
||||
{
|
||||
return $this->and_having_close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an open "AND HAVING (...)" grouping.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function and_having_close()
|
||||
{
|
||||
$this->_having[] = array('AND' => ')');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an open "OR HAVING (...)" grouping.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function or_having_close()
|
||||
{
|
||||
$this->_having[] = array('OR' => ')');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start returning results after "OFFSET ..."
|
||||
*
|
||||
* @param integer starting result number
|
||||
* @return $this
|
||||
*/
|
||||
public function offset($number)
|
||||
{
|
||||
$this->_offset = (int) $number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL query and return it.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @return string
|
||||
*/
|
||||
public function compile(Database $db)
|
||||
{
|
||||
// Callback to quote identifiers
|
||||
$quote_ident = array($db, 'quote_identifier');
|
||||
|
||||
// Callback to quote tables
|
||||
$quote_table = array($db, 'quote_table');
|
||||
|
||||
// Start a selection query
|
||||
$query = 'SELECT ';
|
||||
|
||||
if ($this->_distinct === TRUE)
|
||||
{
|
||||
// Select only unique results
|
||||
$query .= 'DISTINCT ';
|
||||
}
|
||||
|
||||
if (empty($this->_select))
|
||||
{
|
||||
// Select all columns
|
||||
$query .= '*';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select all columns
|
||||
$query .= implode(', ', array_unique(array_map($quote_ident, $this->_select)));
|
||||
}
|
||||
|
||||
if ( ! empty($this->_from))
|
||||
{
|
||||
// Set tables to select from
|
||||
$query .= ' FROM '.implode(', ', array_unique(array_map($quote_table, $this->_from)));
|
||||
}
|
||||
|
||||
if ( ! empty($this->_join))
|
||||
{
|
||||
// Add tables to join
|
||||
$query .= ' '.$this->_compile_join($db, $this->_join);
|
||||
}
|
||||
|
||||
if ( ! empty($this->_where))
|
||||
{
|
||||
// Add selection conditions
|
||||
$query .= ' WHERE '.$this->_compile_conditions($db, $this->_where);
|
||||
}
|
||||
|
||||
if ( ! empty($this->_group_by))
|
||||
{
|
||||
// Add sorting
|
||||
$query .= ' GROUP BY '.implode(', ', array_map($quote_ident, $this->_group_by));
|
||||
}
|
||||
|
||||
if ( ! empty($this->_having))
|
||||
{
|
||||
// Add filtering conditions
|
||||
$query .= ' HAVING '.$this->_compile_conditions($db, $this->_having);
|
||||
}
|
||||
|
||||
if ( ! empty($this->_order_by))
|
||||
{
|
||||
// Add sorting
|
||||
$query .= ' '.$this->_compile_order_by($db, $this->_order_by);
|
||||
}
|
||||
|
||||
if ($this->_limit !== NULL)
|
||||
{
|
||||
// Add limiting
|
||||
$query .= ' LIMIT '.$this->_limit;
|
||||
}
|
||||
|
||||
if ($this->_offset !== NULL)
|
||||
{
|
||||
// Add offsets
|
||||
$query .= ' OFFSET '.$this->_offset;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->_select =
|
||||
$this->_from =
|
||||
$this->_join =
|
||||
$this->_where =
|
||||
$this->_group_by =
|
||||
$this->_having =
|
||||
$this->_order_by = array();
|
||||
|
||||
$this->_distinct = FALSE;
|
||||
|
||||
$this->_limit =
|
||||
$this->_offset =
|
||||
$this->_last_join = NULL;
|
||||
|
||||
$this->_parameters = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
} // End Database_Query_Select
|
@ -0,0 +1,124 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Database query builder for UPDATE statements.
|
||||
*
|
||||
* @package Kohana/Database
|
||||
* @category Query
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2008-2009 Kohana Team
|
||||
* @license http://kohanaphp.com/license
|
||||
*/
|
||||
class Kohana_Database_Query_Builder_Update extends Database_Query_Builder_Where {
|
||||
|
||||
// UPDATE ...
|
||||
protected $_table;
|
||||
|
||||
// SET ...
|
||||
protected $_set = array();
|
||||
|
||||
/**
|
||||
* Set the table for a update.
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($table = NULL)
|
||||
{
|
||||
if ($table)
|
||||
{
|
||||
// Set the inital table name
|
||||
$this->_table = $table;
|
||||
}
|
||||
|
||||
// Start the query with no SQL
|
||||
return parent::__construct(Database::UPDATE, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the table to update.
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @return $this
|
||||
*/
|
||||
public function table($table)
|
||||
{
|
||||
$this->_table = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the values to update with an associative array.
|
||||
*
|
||||
* @param array associative (column => value) list
|
||||
* @return $this
|
||||
*/
|
||||
public function set(array $pairs)
|
||||
{
|
||||
foreach ($pairs as $column => $value)
|
||||
{
|
||||
$this->_set[] = array($column, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a single column.
|
||||
*
|
||||
* @param mixed table name or array($table, $alias) or object
|
||||
* @param mixed column value
|
||||
* @return $this
|
||||
*/
|
||||
public function value($column, $value)
|
||||
{
|
||||
$this->_set[] = array($column, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SQL query and return it.
|
||||
*
|
||||
* @param object Database instance
|
||||
* @return string
|
||||
*/
|
||||
public function compile(Database $db)
|
||||
{
|
||||
// Start an update query
|
||||
$query = 'UPDATE '.$db->quote_table($this->_table);
|
||||
|
||||
// Add the columns to update
|
||||
$query .= ' SET '.$this->_compile_set($db, $this->_set);
|
||||
|
||||
if ( ! empty($this->_where))
|
||||
{
|
||||
// Add selection conditions
|
||||
$query .= ' WHERE '.$this->_compile_conditions($db, $this->_where);
|
||||
}
|
||||
|
||||
if ($this->_limit !== NULL)
|
||||
{
|
||||
// Add limiting
|
||||
$query .= ' LIMIT '.$this->_limit;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->_table = NULL;
|
||||
|
||||
$this->_set =
|
||||
$this->_where = array();
|
||||
|
||||
$this->_limit = NULL;
|
||||
|
||||
$this->_parameters = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
} // End Database_Query_Builder_Update
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user