Commit 7bf84066 authored by Taylor Otwell's avatar Taylor Otwell

refactoring. added redis drivers.

parent 8595253a
......@@ -131,7 +131,6 @@ return array(
*/
'aliases' => array(
'Arr' => 'Laravel\\Arr',
'Asset' => 'Laravel\\Asset',
'Auth' => 'Laravel\\Security\\Auth',
'Benchmark' => 'Laravel\\Benchmark',
......@@ -150,12 +149,9 @@ return array(
'Input' => 'Laravel\\Input',
'IoC' => 'Laravel\\IoC',
'Lang' => 'Laravel\\Lang',
'Loader' => 'Laravel\\Loader',
'Messages' => 'Laravel\\Validation\\Messages',
'Package' => 'Laravel\\Facades\\Package',
'URI' => 'Laravel\\URI',
'URL' => 'Laravel\\URL',
'Redirect' => 'Laravel\\Redirect',
'Redis' => 'Laravel\\Redis',
'Request' => 'Laravel\\Request',
'Response' => 'Laravel\\Response',
'Session' => 'Laravel\\Session\\Manager',
......
......@@ -12,7 +12,7 @@ return array(
| Caching can be used to increase the performance of your application
| by storing commonly accessed data in memory or in a file.
|
| Supported Drivers: 'file', 'memcached', 'apc'.
| Supported Drivers: 'file', 'memcached', 'apc', 'redis'.
|
*/
......
......@@ -70,4 +70,25 @@ return array(
),
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store. However, it
| provides a richer set of commands than a typical key-value store such as
| APC or memcached.
|
| Here you may specify the hosts and ports for your Redis databases.
|
| For more information regarding Redis, check out: http://redis.io
|
*/
'redis' => array(
'default' => array('host' => '127.0.0.1', 'port' => 6379),
),
);
\ No newline at end of file
......@@ -12,7 +12,7 @@ return array(
| Since HTTP is stateless, sessions are used to maintain "state" across
| multiple requests from the same user of your application.
|
| Supported Drivers: 'cookie', 'file', 'database', 'memcached', 'apc'.
| Supported Drivers: 'cookie', 'file', 'database', 'memcached', 'apc', 'redis'.
|
*/
......
......@@ -5,7 +5,7 @@ define('CRLF', chr(13).chr(10));
define('EXT', '.php');
/**
* Define a function that registers an array of constants if they
* Define a function that registers an array of constants if they haven't
* haven't already been registered. This allows the constants to
* be changed from their default values when unit testing.
*/
......
......@@ -101,7 +101,6 @@ register_shutdown_function(function() use ($handler)
{
if ( ! is_null($error = error_get_last()))
{
die('here');
extract($error, EXTR_SKIP);
$handler(new \ErrorException($message, $type, 0, $file, $line));
......
......@@ -68,7 +68,9 @@ class File extends Driver {
*/
public function put($key, $value, $minutes)
{
file_put_contents($this->path.$key, (time() + ($minutes * 60)).serialize($value), LOCK_EX);
$value = (time() + ($minutes * 60)).serialize($value);
file_put_contents($this->path.$key, $value, LOCK_EX);
}
/**
......
<?php namespace Laravel\Cache\Drivers;
class Redis extends Driver {
/**
* The Redis database instance.
*
* @var Redis
*/
protected $redis;
/**
* Create a new Redis cache driver instance.
*
* @param Redis $redis
* @return void
*/
public function __construct(\Laravel\Redis $redis)
{
$this->redis = $redis;
}
/**
* Determine if an item exists in the cache.
*
* @param string $key
* @return bool
*/
public function has($key)
{
return ( ! is_null($this->redis->get($key)));
}
/**
* Retrieve an item from the cache driver.
*
* @param string $key
* @return mixed
*/
protected function retrieve($key)
{
if ( ! is_null($cache = $this->redis->get($key)))
{
return unserialize($cache);
}
}
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::put('name', 'Taylor', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$this->redis->set($key, serialize($value));
$this->redis->expire($key, $minutes * 60);
}
/**
* Delete an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
$this->redis->del($key);
}
}
\ No newline at end of file
......@@ -71,11 +71,9 @@ return array(
| Laravel Caching Components
|--------------------------------------------------------------------------
|
| The following components are used by the wonderfully, simple Laravel
| caching system. Each driver is resolved through the container.
|
| New cache drivers may be added to the framework by simply registering
| them into the container.
| The following components are used by the wonderfully simple Laravel cache
| system. Each driver is resolved through the container, so new drivers may
| be added by simply registering them in the container.
|
*/
......@@ -91,6 +89,12 @@ return array(
}),
'laravel.cache.redis' => array('resolver' => function()
{
return new Cache\Drivers\Redis(Redis::db());
}),
'laravel.cache.memcached' => array('resolver' => function($c)
{
return new Cache\Drivers\Memcached($c->core('cache.memcache.connection'), Config::get('cache.key'));
......@@ -130,10 +134,6 @@ return array(
| from the session driver, as well as examining the payload validitiy
| and things like the CSRF token.
|
| Like the caching components, each session driver is resolved via the
| container and new drivers may be added by registering them into the
| container. Several session drivers are "driven" by the cache drivers.
|
*/
'laravel.session.transporter' => array('resolver' => function($c)
......@@ -166,6 +166,12 @@ return array(
}),
'laravel.session.redis' => array('resolver' => function($c)
{
return new Session\Drivers\Redis($c->core('cache.redis'));
}),
'laravel.session.memcached' => array('resolver' => function($c)
{
return new Session\Drivers\Memcached($c->core('cache.memcached'));
......
......@@ -17,8 +17,6 @@ class Manager {
*
* If no database name is specified, the default connection will be returned.
*
* Note: Database connections are managed as singletons.
*
* <code>
* // Get the default database connection for the application
* $connection = DB::connection();
......
......@@ -611,7 +611,9 @@ class Query {
*/
protected function adjust($column, $amount, $operator)
{
return $this->update(array($column => Manager::raw($this->grammar->wrap($column).$operator.$amount)));
$value = Manager::raw($this->grammar->wrap($column).$operator.$amount);
return $this->update(array($column => $value));
}
/**
......
......@@ -91,9 +91,9 @@ Input::$input = $input;
*/
Routing\Filter::register(require APP_PATH.'filters'.EXT);
list($uri, $method) = array(Request::uri()->get(), Request::method());
list($uri, $method, $format) = array(Request::uri()->get(), Request::method(), Request::format());
$route = IoC::container()->core('routing.router')->route($method, $uri);
$route = IoC::container()->core('routing.router')->route($method, $uri, $format);
if ( ! is_null($route))
{
......
......@@ -3,114 +3,162 @@
class Redis {
/**
* The name of the Redis connection.
* The address for the Redis host.
*
* @var string
*/
public $name;
protected $host;
/**
* The configuration array for the Redis connection.
* The port on which Redis can be accessed on the host.
*
* @var array
* @var int
*/
public $config = array();
protected $port;
/**
* The connection to the Redis database.
*
* @var resource
*/
protected static $connection;
protected $connection;
/**
* Create a new Redis connection instance.
* The active Redis database instances.
*
* @param string $name
* @param array $config
* @return void
* @var array
*/
public function __construct($name, $config)
{
$this->name = $name;
$this->config = $config;
}
protected static $databases = array();
/**
* Create a new Redis connection instance.
*
* @param string $connection
* @param array $config
* @return Redis
* @param string $host
* @param string $port
* @return void
*/
public static function make($name, $config)
public function __construct($host, $port)
{
return new static($name, $config);
$this->host = $host;
$this->port = $port;
}
/**
* Create a new Redis connection instance.
* Get a Redis database connection instance.
*
* The Redis connection is managed as a singleton, so if the connection has
* already been established, that same connection instance will be returned
* on subsequent requests for the connection.
* The given name should correspond to a Redis database in the configuration file.
*
* @param string $connection
* <code>
* // Get the default Redis database instance
* $redis = Redis::db();
*
* // Get a specified Redis database instance
* $reids = Redis::db('redis_2');
* </code>
*
* @param string $name
* @return Redis
*/
public static function connection()
public static function db($name = 'default')
{
if (is_null(static::$connection))
if (is_null(static::$databases[$name]))
{
static::$connection = static::make($name, Config::get('database.redis'))->connect();
if (is_null($config = Config::get("database.redis.{$name}")))
{
throw new \Exception("Redis database [$name] is not defined.");
}
static::$databases[$name] = new static($config['host'], $config['port']);
}
return static::$connection;
return static::$databases[$name];
}
/**
* Connect to the Redis database.
* Execute a command against the Redis database.
*
* The Redis instance itself will be returned by the method.
* <code>
* // Execute the GET command for the "name" key
* $name = Redis::db()->run('get', array('name'));
*
* @return Redis
* // Execute the LRANGE command for the "list" key
* $list = Redis::db()->run('lrange', array(0, 5));
* </code>
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function connect()
public function run($method, $parameters)
{
static::$connection = @fsockopen($this->config['host'], $this->config['port'], $error, $message);
fwrite($this->connect(), $this->command($method, (array) $parameters));
$ersponse = trim(fgets($this->connection, 512));
if (static::$connection === false)
switch (substr($ersponse, 0, 1))
{
throw new \Exception("Error establishing Redis connection [{$this->name}]: {$error} - {$message}");
case '-':
throw new \Exception('Redis error: '.substr(trim($ersponse), 4));
case '+':
case ':':
return $this->inline($ersponse);
case '$':
return $this->bulk($ersponse);
case '*':
return $this->multibulk($ersponse);
default:
throw new \Exception("Unknown response from Redis server: ".substr($ersponse, 0, 1));
}
return $this;
}
/**
* Execute a command agaisnt the Redis database.
* Establish the connection to the Redis database.
*
* @param string $method
* @param array $parameters
* @return mixed
* @return resource
*/
public function run($method, $parameters)
protected function connect()
{
fwrite(static::$connection, $this->command($method, $parameters));
if ( ! is_null($this->connection)) return $this->connection;
$this->connection = @fsockopen($this->host, $this->port, $error, $message);
if ($this->connection === false)
{
throw new \Exception("Error making Redis connection: {$error} - {$message}");
}
$reply = trim(fgets(static::$connection, 512));
return $this->connection;
}
/**
* Build the Redis command based from a given method and parameters.
*
* Redis protocol states that a command should conform to the following format:
*
* *<number of arguments> CR LF
* $<number of bytes of argument 1> CR LF
* <argument data> CR LF
* ...
* $<number of bytes of argument N> CR LF
* <argument data> CR LF
*
* More information regarding the Redis protocol: http://redis.io/topics/protocol
*
* @param string $method
* @param array $parameters
* @return string
*/
protected function command($method, $parameters)
{
$command = '*'.(count($parameters) + 1).CRLF.'$'.strlen($method).CRLF.strtoupper($method).CRLF;
$command = '*'.(count($parameters) + 1).CRLF;
$command .= '$'.strlen($method).CRLF;
$command .= strtoupper($method).CRLF;
foreach ($parameters as $parameter)
{
......@@ -120,6 +168,73 @@ class Redis {
return $command;
}
/**
* Parse and handle an inline response from the Redis database.
*
* @param string $response
* @return string
*/
protected function inline($response)
{
return substr(trim($response), 1);
}
/**
* Parse and handle a bulk response from the Redis database.
*
* @param string $head
* @return string
*/
protected function bulk($head)
{
if ($head == '$-1') return;
list($read, $response, $size) = array(0, '', substr($head, 1));
do
{
// Calculate and read the appropriate bytes off of the Redis response.
// We'll read off the response in 1024 byte chunks until the entire
// response has been read from the database.
$block = (($remaining = $size - $read) < 1024) ? $remaining : 1024;
$response .= fread($this->connection, $block);
$read += $block;
} while ($read < $size);
// The response ends with a trailing CRLF. So, we need to read that off
// of the end of the file stream to get it out of the way of the next
// command that is issued to the database.
fread($this->connection, 2);
return $response;
}
/**
* Parse and handle a multi-bulk reply from the Redis database.
*
* @param string $head
* @return array
*/
protected function multibulk($head)
{
if (($count = substr($head, 1)) == '-1') return;
$response = array();
// Iterate through each bulk response in the multi-bulk and parse it out
// using the "bulk" method since a multi-bulk response is just a list of
// plain old bulk responses.
for ($i = 0; $i < $count; $i++)
{
$response[] = $this->bulk(trim(fgets($this->connection, 512)));
}
return $response;
}
/**
* Dynamically make calls to the Redis database.
*/
......@@ -128,6 +243,14 @@ class Redis {
return $this->run($method, $parameters);
}
/**
* Dynamically pass static method calls to the Redis instance.
*/
public static function __callStatic($method, $parameters)
{
return static::db()->run($method, $parameters);
}
/**
* Close the connection to the Redis database.
*
......@@ -135,7 +258,7 @@ class Redis {
*/
public function __destruct()
{
fclose(static::$connection);
fclose($this->connection);
}
}
\ No newline at end of file
......@@ -150,15 +150,15 @@ class Route {
{
return call_user_func_array($this->callback, $this->parameters);
}
// If the route is an array we will return the first value with a
// key of "delegate", or the first instance of a Closure. If the
// value is a string, the route is delegating the responsibility
// If the route is an array, we will return the first value with a
// key of "uses", or the first instance of a Closure. If the value
// is a string, the route is delegating the responsibility for
// for handling the request to a controller.
elseif (is_array($this->callback))
{
$callback = Arr::first($this->callback, function($key, $value)
{
return $key == 'delegate' or $value instanceof Closure;
return $key == 'uses' or $value instanceof Closure;
});
if ($callback instanceof Closure)
......
......@@ -51,8 +51,8 @@ class Router {
* @var array
*/
protected $patterns = array(
'(:num)' => '[0-9]+',
'(:any)' => '[a-zA-Z0-9\.\-_]+',
'(:num)' => '([0-9]+)',
'(:any)' => '([a-zA-Z0-9\.\-_]+)',
);
/**
......@@ -104,9 +104,10 @@ class Router {
*
* @param string $method
* @param string $uri
* @param string $format
* @return Route
*/
public function route($method, $uri)
public function route($method, $uri, $format)
{
$routes = $this->loader->load($uri);
......@@ -122,19 +123,18 @@ class Router {
foreach ($routes as $keys => $callback)
{
// Formats are appended to the route key as a regular expression.
// It will look something like: "(\.(json|xml|html))?"
$formats = $this->provides($callback);
// We need to make sure that the requested format is provided by the
// route. If it isn't, there is no need to continue evaluating it.
if ( ! in_array($format, $this->provides($callback))) continue;
// Only check routes that have multiple URIs or wildcards since other
// Only check routes having multiple URIs or wildcards since other
// routes would have been caught by the check for literal matches.
// We also need to check routes with "provides" clauses.
if ($this->fuzzy($keys) or ! is_null($formats))
if (strpos($keys, '(') !== false or strpos($keys, ',') !== false)
{
if ( ! is_null($route = $this->match($destination, $keys, $callback, $formats)))
if ( ! is_null($route = $this->match($destination, $keys, $callback, $format)))
{
return Request::$route = $route;
}
}
}
}
......@@ -142,18 +142,19 @@ class Router {
}
/**
* Determine if the route contains elements that forbid literal matches.
*
* Any route key containing a regular expression, wildcard, or multiple
* URIs cannot be matched using a literal string check, but must be
* checked using regular expressions.
* Get the request formats for which the route provides responses.
*
* @param string $keys
* @return bool
* @param mixed $callback
* @return array
*/
protected function fuzzy($keys)
protected function provides($callback)
{
return strpos($keys, '(') !== false or strpos($keys, ',') !== false;
if (is_array($callback) and isset($callback['provides']))
{
return (is_string($provides = $callback['provides'])) ? explode('|', $provides) : $provides;
}
return array();
}
/**
......@@ -166,18 +167,19 @@ class Router {
* @param string $destination
* @param array $keys
* @param mixed $callback
* @param array $formats
* @param string $format
* @return mixed
*/
protected function match($destination, $keys, $callback, $formats)
protected function match($destination, $keys, $callback, $format)
{
// Append the provided formats to the route as an optional regular expression.
// This should make the route look something like: "user(\.(json|xml|html))?"
$formats = ( ! is_null($formats)) ? '(\.('.implode('|', $formats).'))?' : '';
// We need to remove the format from the route since formats are
// not specified in the route URI directly, but rather through
// the "provides" keyword on the route array.
$destination = str_replace('.'.$format, '', $destination);
foreach (explode(', ', $keys) as $key)
{
if (preg_match('#^'.$this->wildcards($key).$formats.'$#', $destination))
if (preg_match('#^'.$this->wildcards($key).'$#', $destination))
{
return new Route($keys, $callback, $this->parameters($destination, $key));
}
......@@ -241,20 +243,6 @@ class Router {
}
}
/**
* Get the request formats for which the route provides responses.
*
* @param mixed $callback
* @return array
*/
protected function provides($callback)
{
if (is_array($callback) and isset($callback['provides']))
{
return (is_string($provides = $callback['provides'])) ? explode('|', $provides) : $provides;
}
}
/**
* Translate route URI wildcards into actual regular expressions.
*
......@@ -272,8 +260,6 @@ class Router {
$key .= ($replacements > 0) ? str_repeat(')?', $replacements) : '';
// After replacing all of the optional wildcards, we can replace all
// of the "regular" wildcards and return the fully regexed string.
return str_replace(array_keys($this->patterns), array_values($this->patterns), $key);
}
......@@ -288,6 +274,11 @@ class Router {
*/
protected function parameters($uri, $route)
{
// When gathering the parameters, we need to get the request format out
// of the destination, otherwise it could be passed in as a parameter
// to the route closure or controller, which we don't want.
$uri = str_replace('.'.Request::format(), '', $uri);
list($uri, $route) = array(explode('/', $uri), explode('/', $route));
$count = count($route);
......
<?php namespace Laravel\Session\Drivers;
class Redis implements Driver {
/**
* The Redis cache driver instance.
*
* @var Cache\Drivers\Redis
*/
protected $redis;
/**
* Create a new Redis session driver.
*
* @param Cache\Drivers\Redis $redis
* @return void
*/
public function __construct(\Laravel\Cache\Drivers\Redis $redis)
{
$this->redis = $redis;
}
/**
* Load a session from storage by a given ID.
*
* If no session is found for the ID, null will be returned.
*
* @param string $id
* @return array
*/
public function load($id)
{
return $this->redis->get($id);
}
/**
* Save a given session to storage.
*
* @param array $session
* @param array $config
* @param bool $exists
* @return void
*/
public function save($session, $config, $exists)
{
$this->redis->put($session['id'], $session, $config['lifetime']);
}
/**
* Delete a session from storage by a given ID.
*
* @param string $id
* @return void
*/
public function delete($id)
{
$this->redis->forget($id);
}
}
\ No newline at end of file
......@@ -41,9 +41,7 @@ class URI {
{
if ( ! is_null($this->uri)) return $this->uri;
$uri = parse_url($this->server['REQUEST_URI'], PHP_URL_PATH);
return $this->uri = $this->format($this->clean($uri));
return $this->uri = $this->format($this->clean($this->parse($this->server['REQUEST_URI'])));
}
/**
......@@ -54,14 +52,24 @@ class URI {
*/
protected function clean($uri)
{
$uri = $this->remove($uri, parse_url(Config::$items['application']['url'], PHP_URL_PATH));
$uri = $this->remove($uri, $this->parse(Config::$items['application']['url']));
if (($index = '/'.Config::$items['application']['index']) !== '/')
{
$uri = $this->remove($uri, $index);
}
return rtrim($uri, '.'.Request::format($uri));
return $uri;
}
/**
* Parse a given string URI using PHP_URL_PATH to remove the domain.
*
* @return string
*/
protected function parse($uri)
{
return parse_url($uri, PHP_URL_PATH);
}
/**
......
......@@ -37,10 +37,19 @@ class Messages {
*/
public function add($key, $message)
{
if ( ! isset($this->messages[$key]) or array_search($message, $this->messages[$key]) === false)
{
$this->messages[$key][] = $message;
}
if ($this->unique($key, $message)) $this->messages[$key][] = $message;
}
/**
* Determine if a key and message combination already exists.
*
* @param string $key
* @param string $message
* @return bool
*/
protected function unique($key, $message)
{
return ! isset($this->messages[$key]) or array_search($message, $this->messages[$key]) === false;
}
/**
......
......@@ -188,7 +188,7 @@ class View {
// use the regular path to the view.
$view = (strpos($this->path, BLADE_EXT) !== false) ? $this->compile() : $this->path;
try { include $view; } catch (Exception $e) { ob_get_clean(); throw $e; }
try { include $view; } catch (\Exception $e) { ob_get_clean(); throw $e; }
return ob_get_clean();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment