From a6eaa069810e0d100a9e660ae646974992e245b8 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sat, 15 Oct 2011 14:04:11 -0500 Subject: [PATCH] refactoring routing and comments. --- laravel/arr.php | 2 + laravel/cache/drivers/driver.php | 4 +- laravel/cache/drivers/memcached.php | 2 +- laravel/cache/manager.php | 8 +- laravel/database/eloquent/model.php | 8 +- laravel/database/grammars/grammar.php | 26 ++-- laravel/database/query.php | 61 +++++---- laravel/laravel.php | 2 +- laravel/routing/controller.php | 30 +++-- laravel/routing/loader.php | 25 ++-- laravel/routing/route.php | 21 +-- laravel/routing/router.php | 61 +++++---- laravel/security/auth.php | 48 +++---- laravel/security/crypter.php | 15 ++- laravel/security/hasher.php | 17 +-- laravel/session/manager.php | 42 ++++-- laravel/str.php | 40 +++--- laravel/validation/messages.php | 7 +- laravel/validation/validator.php | 186 ++++++++++++++++---------- laravel/view.php | 28 ++-- 20 files changed, 368 insertions(+), 265 deletions(-) diff --git a/laravel/arr.php b/laravel/arr.php index 32aca743..3e84d56a 100644 --- a/laravel/arr.php +++ b/laravel/arr.php @@ -117,6 +117,8 @@ public static function first($array, $callback, $default = null) */ public static function without($array, $without = array()) { + $without = (array) $without; + foreach ((array) $array as $key => $value) { if (in_array($value, $without)) unset($array[$key]); diff --git a/laravel/cache/drivers/driver.php b/laravel/cache/drivers/driver.php index cc82655d..6476ded4 100644 --- a/laravel/cache/drivers/driver.php +++ b/laravel/cache/drivers/driver.php @@ -57,8 +57,8 @@ abstract protected function retrieve($key); abstract public function put($key, $value, $minutes); /** - * Get an item from the cache. If the item doesn't exist in the cache, store - * the default value in the cache and return it. + * Get an item from the cache. If the item doesn't exist in the + * cache, store the default value in the cache and return it. * * * // Get an item from the cache, or cache a value for 15 minutes if it doesn't exist diff --git a/laravel/cache/drivers/memcached.php b/laravel/cache/drivers/memcached.php index 81172154..a581d973 100644 --- a/laravel/cache/drivers/memcached.php +++ b/laravel/cache/drivers/memcached.php @@ -1,4 +1,4 @@ - * // Get the default cache driver instance @@ -46,8 +46,8 @@ public static function driver($driver = null) /** * Pass all other methods to the default cache driver. * - * Passing method calls to the driver instance provides a convenient API for the developer - * when always using the default cache driver. + * Passing method calls to the driver instance provides a convenient API + * for the developer when always using the default cache driver. * * * // Call the "get" method on the default driver diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php index f2c6661a..d204cfc9 100644 --- a/laravel/database/eloquent/model.php +++ b/laravel/database/eloquent/model.php @@ -339,12 +339,12 @@ public function has_and_belongs_to_many($model, $table = null, $foreign_key = nu $this->relating_table = (is_null($table)) ? $this->intermediate_table($model) : $table; - // Allowing the overriding of the foreign and associated keys provides the flexibility for - // self-referential many-to-many relationships, such as a "buddy list". + // Allowing the overriding of the foreign and associated keys provides + // the flexibility for self-referential many-to-many relationships. $this->relating_key = (is_null($foreign_key)) ? strtolower(static::model_name($this)).'_id' : $foreign_key; - // The associated key is the foreign key name of the related model. So, if the related model - // is "Role", the associated key on the intermediate table would be "role_id". + // The associated key is the foreign key name of the related model. + // If the related model is "Role", the key would be "role_id". $associated_key = (is_null($associated_key)) ? strtolower(static::model_name($model)).'_id' : $associated_key; return static::query($model) diff --git a/laravel/database/grammars/grammar.php b/laravel/database/grammars/grammar.php index 513e0a00..3468e1f0 100644 --- a/laravel/database/grammars/grammar.php +++ b/laravel/database/grammars/grammar.php @@ -262,20 +262,18 @@ protected function offset(Query $query) */ public function insert(Query $query, $values) { - // Force every insert to be treated like a batch insert. - // This simply makes creating the SQL syntax a little - // easier on us since we can always treat the values - // as if is an array containing multiple inserts. + // Force every insert to be treated like a batch insert. This simple makes + // creating the SQL syntax a little easier on us since we can always treat + // the values as if it is an array containing multiple inserts. if ( ! is_array(reset($values))) $values = array($values); - // Since we only care about the column names, we can pass - // any of the insert arrays into the "columnize" method. - // The names should be the same for every insert. + // Since we only care about the column names, we can pass any of the insert + // arrays into the "columnize" method. The names should be the same for + // every insert to the table. $columns = $this->columnize(array_keys(reset($values))); - // We need to create a string of comma-delimited insert - // segments. Each segment contains PDO place-holders for - // each value being inserted into the table. + // We need to create a string of comma-delimited insert segments. Each segment + // contains PDO place-holders for each value being inserted into the table. $parameters = implode(', ', array_fill(0, count($values), '('.$this->parameterize(reset($values)).')')); return 'INSERT INTO '.$this->wrap($query->from).' ('.$columns.') VALUES '.$parameters; @@ -314,9 +312,9 @@ public function delete(Query $query) } /** - * The following functions primarily serve as utility functions - * for the grammar. They perform tasks such as wrapping values - * in keyword identifiers or creating variable lists of bindings. + * The following functions primarily serve as utility functions for + * the grammar. They perform tasks such as wrapping values in keyword + * identifiers or creating variable lists of bindings. */ /** @@ -385,7 +383,7 @@ public function parameterize($values) * Get the appropriate query parameter string for a value. * * If the value is an expression, the raw expression string should - * will be returned, otherwise, the parameter place-holder will be + * be returned, otherwise, the parameter place-holder will be * returned by the method. * * @param mixed $value diff --git a/laravel/database/query.php b/laravel/database/query.php index df84de47..caf6ac56 100644 --- a/laravel/database/query.php +++ b/laravel/database/query.php @@ -24,8 +24,8 @@ class Query { public $selects; /** - * If the query is performing an aggregate function, this will contain the column - * and and function to use when aggregating. + * If the query is performing an aggregate function, this will contain + * the column and and function to use when aggregating. * * @var array */ @@ -250,9 +250,6 @@ public function or_where_id($value) */ public function where_in($column, $values, $connector = 'AND', $not = false) { - // The type set in this method will be used by the query grammar to call the - // appropriate compiler function for the where clause. For cleanliness, the - // compiler for "not in" and "in" statements is broken into two functions. $type = ($not) ? 'where_not_in' : 'where_in'; $this->wheres[] = compact('type', 'column', 'values', 'connector'); @@ -309,9 +306,6 @@ public function or_where_not_in($column, $values) */ public function where_null($column, $connector = 'AND', $not = false) { - // The type set in this method will be used by the query grammar to call the - // appropriate compiler function for the where clause. For cleanliness, the - // compiler for "not null" and "null" statements is broken into two functions. $type = ($not) ? 'where_not_null' : 'where_null'; $this->wheres[] = compact('type', 'column', 'connector'); @@ -371,11 +365,13 @@ private function dynamic_where($method, $parameters) // Split the column names from the connectors. $segments = preg_split('/(_and_|_or_)/i', $finder, -1, PREG_SPLIT_DELIM_CAPTURE); - // The connector variable will determine which connector will be used for the condition. - // We'll change it as we come across new connectors in the dynamic method string. + // The connector variable will determine which connector will be + // used for the condition. We'll change it as we come across new + // connectors in the dynamic method string. // - // The index variable helps us get the correct parameter value for the where condition. - // We increment it each time we add a condition. + // The index variable helps us get the correct parameter value + // for the where condition. We increment it each time we add + // a condition to the query. $connector = 'AND'; $index = 0; @@ -501,8 +497,9 @@ public function get($columns = array('*')) $results = $this->connection->query($this->grammar->select($this), $this->bindings); - // Reset the SELECT clause so more queries can be performed using the same instance. - // This is helpful for getting aggregates and then getting actual results. + // Reset the SELECT clause so more queries can be performed using + // the same instance. This is helpful for getting aggregates and + // then getting actual results. $this->selects = null; return $results; @@ -521,8 +518,9 @@ private function aggregate($aggregator, $column) $result = $this->connection->only($this->grammar->select($this), $this->bindings); - // Reset the aggregate so more queries can be performed using the same instance. - // This is helpful for getting aggregates and then getting actual results. + // Reset the aggregate so more queries can be performed using + // the same instance. This is helpful for getting aggregates + // and then getting actual results. $this->aggregate = null; return $result; @@ -537,8 +535,9 @@ private function aggregate($aggregator, $column) */ public function paginate($per_page = 20, $columns = array('*')) { - // Calculate the current page for the request. The page number will be validated - // and adjusted by the Paginator class, so we can assume it is valid. + // Calculate the current page for the request. The page number + // will be validated and adjusted by the Paginator class, + // so we can assume it is valid. $page = Paginator::page($total = $this->count(), $per_page); return Paginator::make($this->for_page($page, $per_page)->get($columns), $total, $per_page); @@ -552,9 +551,9 @@ public function paginate($per_page = 20, $columns = array('*')) */ public function insert($values) { - // Force every insert to be treated like a batch insert. This simply makes creating - // the binding array easier. We will simply loop through each inserted row and merge - // the values together to get one big binding array. + // Force every insert to be treated like a batch insert to make creating + // the binding array simpler since we can just spin through the inserted + // rows as if there/ was more than one every time. if ( ! is_array(reset($values))) $values = array($values); $bindings = array(); @@ -568,7 +567,8 @@ public function insert($values) } /** - * Insert an array of values into the database table and return the value of the ID column. + * Insert an array of values into the database table and + * return the value of the ID column. * * @param array $values * @param string $sequence @@ -626,7 +626,9 @@ protected function adjust($column, $amount, $operator) */ public function update($values) { - return $this->connection->query($this->grammar->update($this, $values), array_merge(array_values($values), $this->bindings)); + $bindings = array_merge(array_values($values), $this->bindings); + + return $this->connection->query($this->grammar->update($this, $values), $bindings); } /** @@ -647,8 +649,8 @@ public function delete($id = null) /** * Magic Method for handling dynamic functions. * - * This method handles all calls to aggregate functions as well as the construction - * of dynamic where clauses via the "dynamic_where" method. + * This method handles all calls to aggregate functions as well + * as the construction of dynamic where clauses. */ public function __call($method, $parameters) { @@ -659,7 +661,14 @@ public function __call($method, $parameters) if (in_array($method, array('abs', 'count', 'min', 'max', 'avg', 'sum'))) { - return ($method == 'count') ? $this->aggregate(strtoupper($method), '*') : $this->aggregate(strtoupper($method), $parameters[0]); + if ($method == 'count') + { + return $this->aggregate(strtoupper($method), '*'); + } + else + { + return $this->aggregate(strtoupper($method), $parameters[0]); + } } throw new \Exception("Method [$method] is not defined on the Query class."); diff --git a/laravel/laravel.php b/laravel/laravel.php index 15cd8758..c7a99112 100644 --- a/laravel/laravel.php +++ b/laravel/laravel.php @@ -129,7 +129,7 @@ { $flash = array(Input::old_input => Input::get()); - Session\Manager::close($driver, $transporter, $flash); + Session\Manager::close($flash); } /** diff --git a/laravel/routing/controller.php b/laravel/routing/controller.php index 41db0a13..ac24aa7c 100644 --- a/laravel/routing/controller.php +++ b/laravel/routing/controller.php @@ -59,25 +59,18 @@ public static function call($destination, $parameters) $response = call_user_func_array(array($controller, $method), $parameters); } - $filters = array_merge($controller->filters('after'), array('after')); + // The after filter and the framework expects all responses to + // be instances of the Response class. If the route did not + // return an instsance of Response, we will make on now. + if ( ! $response instanceof Response) $response = new Response($response); + + $filters = array_merge($this->filters('after'), array('after')); Filter::run($filters, array($response)); return $response; } - - /** - * Determine if a given controller method is callable. - * - * @param string $method - * @return bool - */ - protected static function hidden($method) - { - return $method == 'before' or $method == 'after' or strncmp($method, '_', 1) == 0; - } - /** * Resolve a controller name to a controller instance. * @@ -123,6 +116,17 @@ protected static function load($controller) return false; } + /** + * Determine if a given controller method is callable. + * + * @param string $method + * @return bool + */ + protected static function hidden($method) + { + return $method == 'before' or $method == 'after' or strncmp($method, '_', 1) == 0; + } + /** * Get an array of filter names defined for the destination. * diff --git a/laravel/routing/loader.php b/laravel/routing/loader.php index 3d787d2b..47c656a6 100644 --- a/laravel/routing/loader.php +++ b/laravel/routing/loader.php @@ -50,25 +50,28 @@ public function load($uri) { $routes = (file_exists($path = $this->base.'routes'.EXT)) ? require $path : array(); - return array_merge($this->nested(Arr::without(explode('/', $uri), array(''))), $routes); + $segments = Arr::without(explode('/', $uri), ''); + + return array_merge($this->nested($segments), $routes); } /** * Get the appropriate routes from the routes directory for a given URI. * + * This method works backwards through the URI segments until we find the + * deepest possible matching route directory. Once the deepest directory + * is found, all of the applicable routes will be returend. + * * @param array $segments * @return array */ protected function nested($segments) { - // Work backwards through the URI segments until we find the deepest possible - // matching route directory. Once we find it, we will return those routes. foreach (array_reverse($segments, true) as $key => $value) { - if (file_exists($path = $this->nest.implode('/', array_slice($segments, 0, $key + 1)).EXT)) - { - return require $path; - } + $path = $this->nest.implode('/', array_slice($segments, 0, $key + 1)).EXT; + + if (file_exists($path)) return require $path; } return array(); @@ -77,6 +80,10 @@ protected function nested($segments) /** * Get every route defined for the application. * + * The entire routes directory will be searched recursively to gather + * every route for the application. Of course, the routes in the root + * routes file will be returned as well. + * * @return array */ public function everything() @@ -90,8 +97,8 @@ public function everything() $routes = array_merge($routes, require $path); } - // Since route files can be nested deep within the route directory, - // we need to recursively spin through each directory + if ( ! is_dir($this->nest)) return $routes; + $iterator = new Iterator(new DirectoryIterator($this->nest), Iterator::SELF_FIRST); foreach ($iterator as $file) diff --git a/laravel/routing/route.php b/laravel/routing/route.php index 91add9f3..ff073aeb 100644 --- a/laravel/routing/route.php +++ b/laravel/routing/route.php @@ -104,19 +104,24 @@ public function call() { if ($response instanceof Delegate) { - return $response; + return Controller::call($response, $this->parameters); } + else + { + // The after filter and the framework expects all responses to + // be instances of the Response class. If the route did not + // return an instsance of Response, we will make on now. + if ( ! $response instanceof Response) $response = new Response($response); - $filters = array_merge($this->filters('after'), array('after')); + $filters = array_merge($this->filters('after'), array('after')); - Filter::run($filters, array($response)); + Filter::run($filters, array($response)); - return $response; - } - else - { - return Response::error('404'); + return $response; + } } + + return Response::error('404'); } /** diff --git a/laravel/routing/router.php b/laravel/routing/router.php index 405a34d8..32cf367e 100644 --- a/laravel/routing/router.php +++ b/laravel/routing/router.php @@ -88,13 +88,8 @@ public function __construct(Loader $loader, $controllers) */ public function find($name) { - // First we will check the cache of route names. If we have already found the given route, - // we will simply return that route from the cache to improve performance. if (array_key_exists($name, $this->names)) return $this->names[$name]; - // Spin through every route defined for the application searching for a route that has - // a name matching the name passed to the method. If the route is found, it will be - // cached in the array of named routes and returned. foreach ($this->loader->everything() as $key => $value) { if (is_array($value) and isset($value['name']) and $value['name'] === $name) @@ -116,11 +111,10 @@ public function route($method, $uri) $routes = $this->loader->load($uri); // Put the request method and URI in route form. Routes begin with - // the request method and a forward slash. + // the request method and a forward slash followed by the URI. $destination = $method.' /'.trim($uri, '/'); - // Check for a literal route match first. If we find one, there is - // no need to spin through all of the routes. + // Check for a literal route match first... if (isset($routes[$destination])) { return Request::$route = new Route($destination, $routes[$destination], array()); @@ -130,22 +124,42 @@ public function route($method, $uri) { // Only check routes that have multiple URIs or wildcards since other // routes would have been caught by the check for literal matches. - if (strpos($keys, '(') !== false or strpos($keys, ',') !== false ) + if (strpos($keys, '(') !== false or strpos($keys, ',') !== false) { - foreach (explode(', ', $keys) as $key) + if ( ! is_null($route = $this->match($destination, $keys, $callback))) { - // Append the provided formats to the route as an optional regular expression. - if ( ! is_null($formats = $this->provides($callback))) $key .= '(\.('.implode('|', $formats).'))?'; - - if (preg_match('#^'.$this->translate_wildcards($key).'$#', $destination)) - { - return Request::$route = new Route($keys, $callback, $this->parameters($destination, $key)); - } - } + return Request::$route = $route; + } } } - return Request::$route = $this->route_to_controller($method, $uri, $destination); + return Request::$route = $this->controller($method, $uri, $destination); + } + + /** + * Attempt to match a given route destination to a given route. + * + * The destination's methods and URIs will be compared against the route's. + * If there is a match, the Route instance will be returned, otherwise null + * will be returned by the method. + * + * @param string $destination + * @param array $keys + * @param mixed $callback + * @return mixed + */ + protected function match($destination, $keys, $callback) + { + foreach (explode(', ', $keys) as $key) + { + // Append the provided formats to the route as an optional regular expression. + if ( ! is_null($formats = $this->provides($callback))) $key .= '(\.('.implode('|', $formats).'))?'; + + if (preg_match('#^'.$this->wildcards($key).'$#', $destination)) + { + return new Route($keys, $callback, $this->parameters($destination, $key)); + } + } } /** @@ -156,10 +170,11 @@ public function route($method, $uri) * @param string $destination * @return Route */ - protected function route_to_controller($method, $uri, $destination) + protected function controller($method, $uri, $destination) { - // If the request is to the root of the application, an ad-hoc route will be generated - // to the home controller's "index" method, making it the default controller method. + // If the request is to the root of the application, an ad-hoc route + // will be generated to the home controller's "index" method, making + // it the default controller method. if ($uri === '/') return new Route($method.' /', 'home@index'); $segments = explode('/', trim($uri, '/')); @@ -228,7 +243,7 @@ protected function provides($callback) * @param string $key * @return string */ - protected function translate_wildcards($key) + protected function wildcards($key) { $replacements = 0; diff --git a/laravel/security/auth.php b/laravel/security/auth.php index 3a2e65d8..08d0faca 100644 --- a/laravel/security/auth.php +++ b/laravel/security/auth.php @@ -43,7 +43,7 @@ public static function check() * Get the current user of the application. * * This method will call the "user" closure in the authentication configuration file. - * If the user is not authenticated, null will be returned. + * If the user is not authenticated, null will be returned by the methd. * * If no user exists in the session, the method will check for a "remember me" * cookie and attempt to login the user based on the value of that cookie. @@ -75,6 +75,10 @@ public static function user() /** * Attempt to login a user based on a long-lived "remember me" cookie. * + * We should be able to trust the cookie is valid, since all cookies + * set by Laravel include a fingerprint hash. So, the cookie should + * be safe to use within this method. + * * @param string $cookie * @return mixed */ @@ -82,14 +86,7 @@ protected static function recall($cookie) { $cookie = explode('|', Crypter::decrypt($cookie)); - // If there are not at least two elements in the array, the decrypted value - // is not valid and we wil just bail out of the method since the cookie may - // have been tampered with and should not be considered trustworthy. - if (count($cookie) < 2) return; - - list($id, $username, $config) = array($cookie[0], $cookie[1], Config::get('auth')); - - if ( ! is_null($user = call_user_func($config['user'], $id)) and $user->{$config['username']} === $username) + if ( ! is_null($user = call_user_func(Config::get('auth.user'), $cookie[0]))) { static::login($user); @@ -100,14 +97,12 @@ protected static function recall($cookie) /** * Attempt to log a user into the application. * - * If the given credentials are valid, the user will be logged into - * the application and their user ID will be stored in the session - * via the "login" method. + * If the credentials are valid, the user will be logged into the application + * and their user ID will be stored in the session via the "login" method. * - * The user may also be "remembered". When this option is set, the user - * will be automatically logged into the application for one year via - * an encrypted cookie containing their ID. Of course, if the user logs - * out of the application, they will no longer be remembered. + * The user may also be "remembered", which will keep the user logged into the + * application for one year or until they logout. The user is rememberd via + * an encrypted cookie. * * @param string $username * @param string $password @@ -139,7 +134,7 @@ public static function login($user, $remember = false) { static::$user = $user; - if ($remember) static::remember($user->id, $user->{Config::get('auth.username')}); + if ($remember) static::remember($user->id); Session::put(Auth::user_key, $user->id); } @@ -148,17 +143,16 @@ public static function login($user, $remember = false) * Set a cookie so that users are "remembered" and don't need to login. * * @param string $id - * @param string $username * @return void */ - protected static function remember($id, $username) + protected static function remember($id) { - $cookie = Crypter::encrypt($id.'|'.$username.'|'.Str::random(40)); + $cookie = Crypter::encrypt($id.'|'.Str::random(40)); - // This method assumes the "remember me" cookie should have the - // same configuration as the session cookie. Since this cookie, - // like the session cookie, should be kept very secure, it's - // probably safe to assume the settings are the same. + // This method assumes the "remember me" cookie should have the same + // configuration as the session cookie. Since this cookie, like the + // session cookie, should be kept very secure, it's probably safe + // to assume the settings are the same. $config = Config::get('session'); Cookie::forever(Auth::remember_key, $cookie, $config['path'], $config['domain'], $config['secure']); @@ -167,9 +161,9 @@ protected static function remember($id, $username) /** * Log the current user out of the application. * - * The "logout" closure in the authenciation configuration file - * will be called. All authentication cookies will be deleted - * and the user ID will be removed from the session. + * The "logout" closure in the authenciation configuration file will be + * called. All authentication cookies will be deleted and the user ID + * will be removed from the session. * * @return void */ diff --git a/laravel/security/crypter.php b/laravel/security/crypter.php index ca02a47d..a85fd7d8 100644 --- a/laravel/security/crypter.php +++ b/laravel/security/crypter.php @@ -1,6 +1,6 @@ * // Encrypt a string using the Mcrypt PHP extension @@ -55,7 +54,9 @@ public static function encrypt($value) $iv = mcrypt_create_iv(static::iv_size(), $randomizer); - return base64_encode($iv.mcrypt_encrypt(static::$cipher, Config::get('application.key'), $value, static::$mode, $iv)); + $key = Config::$items['application']['key']; + + return base64_encode($iv.mcrypt_encrypt(static::$cipher, $key, $value, static::$mode, $iv)); } /** @@ -85,7 +86,9 @@ public static function decrypt($value) $value = substr($value, static::iv_size()); - return rtrim(mcrypt_decrypt(static::$cipher, Config::get('application.key'), $value, static::$mode, $iv), "\0"); + $key = Config::$items['application']['key']; + + return rtrim(mcrypt_decrypt(static::$cipher, $key, $value, static::$mode, $iv), "\0"); } /** diff --git a/laravel/security/hasher.php b/laravel/security/hasher.php index 9d41fb28..d600ae21 100644 --- a/laravel/security/hasher.php +++ b/laravel/security/hasher.php @@ -5,10 +5,11 @@ class Hasher { /** * Hash a password using the Bcrypt hashing scheme. * - * Bcrypt provides a future-proof hashing algorithm by allowing the number - * of "rounds" to be increased, thus increasing the time is takes to generate - * the hashed value. The longer is takes to generate the hash, the more - * impractical a rainbow table attack against the hashes becomes. + * Bcrypt provides a future-proof hashing algorithm by allowing the + * number of "rounds" to be increased, thus increasing the time it + * takes to generate the hashed value. The longer it takes takes + * to generate the hash, the more impractical a rainbow table + * attack against the hashes becomes. * * * // Create a Bcrypt hash of a value @@ -42,14 +43,14 @@ public static function check($value, $hash) /** * Get a salt for use during Bcrypt hashing. * + * Bcrypt expects salts to be 22 alpha-numeric characters including + * dots and forward slashes. OpenSSL will be used if available and + * the Str::random method will be used if it isn't. + * * @return string */ protected static function salt() { - // If OpenSSL is installed, we will use it to gather random bytes for generating - // the salt value. Otherwise, we will use the Str::random method. Bcrypt expects - // the salt to be a 22 character alpha-numeric string. The salt may also contain - // dots, plus signs, and forward slashes. if (function_exists('openssl_random_pseudo_bytes')) { return substr(strtr(base64_encode(openssl_random_pseudo_bytes(16)), '+', '.'), 0 , 22); diff --git a/laravel/session/manager.php b/laravel/session/manager.php index 51a3ceb6..5e9728da 100644 --- a/laravel/session/manager.php +++ b/laravel/session/manager.php @@ -29,6 +29,20 @@ class Manager { */ public static $regenerated = false; + /** + * The driver being used by the session. + * + * @var Drivers\Driver + */ + protected static $driver; + + /** + * The session ID transporter used by the session. + * + * @var Transporters\Transpoter + */ + protected static $transporter; + /** * Start the session handling for the current request. * @@ -40,28 +54,30 @@ public static function start(Driver $driver, Transporter $transporter) { $config = Config::$items['session']; - static::$session = $driver->load($transporter->get($config)); + $session = $driver->load($transporter->get($config)); // If the session is expired, a new session will be generated and all of // the data from the previous session will be lost. The new session will // be assigned a random, long string ID to uniquely identify it among // the application's current users. - if (is_null(static::$session) or (time() - static::$session['last_activity']) > ($config['lifetime'] * 60)) + if (is_null($session) or (time() - $session['last_activity']) > ($config['lifetime'] * 60)) { static::$exists = false; - static::$session = array('id' => Str::random(40), 'data' => array()); + $session = array('id' => Str::random(40), 'data' => array()); } + static::$session = $session; + // If a CSRF token is not present in the session, we will generate one. // These tokens are generated per session to protect against Cross-Site - // Request Forgery attacks on the application. It is up to the developer - // to take advantage of them using the token methods on the Form class - // and the "csrf" route filter. + // Request Forgery attacks on the application. if ( ! static::has('csrf_token')) { static::put('csrf_token', Str::random(16)); } + + list(static::$driver, static::$transporter) = array($driver, $transporter); } /** @@ -253,12 +269,10 @@ protected static function replace($search, $replace, $keys) /** * Close the session handling for the request. * - * @param Drivers\Driver $driver - * @param Transporters\Transporter $transporter - * @param array $flash + * @param array $flash * @return void */ - public static function close(Driver $driver, Transporter $transporter, $flash = array()) + public static function close($flash = array()) { $config = Config::$items['session']; @@ -267,17 +281,17 @@ public static function close(Driver $driver, Transporter $transporter, $flash = static::flash($key, $value); } - $driver->save(static::age(), $config, static::$exists); + static::$driver->save(static::age(), $config, static::$exists); - $transporter->put(static::$session['id'], $config); + static::$transporter->put(static::$session['id'], $config); // Some session drivers may implement the Sweeper interface, meaning the // driver must do its garbage collection manually. Alternatively, some // drivers such as APC and Memcached are not required to manually // clean up their sessions. - if (mt_rand(1, $config['sweepage'][1]) <= $config['sweepage'][0] and $driver instanceof Drivers\Sweeper) + if (mt_rand(1, $config['sweepage'][1]) <= $config['sweepage'][0] and static::$driver instanceof Drivers\Sweeper) { - $driver->sweep(time() - ($config['lifetime'] * 60)); + static::$driver->sweep(time() - ($config['lifetime'] * 60)); } } diff --git a/laravel/str.php b/laravel/str.php index a0d23407..5f9b2d9a 100644 --- a/laravel/str.php +++ b/laravel/str.php @@ -99,12 +99,17 @@ public static function length($value) } /** - * Limit the number of chars in a string + * Limit the number of characters in a string. + * + * Word integrity is preserved, so the number of characters in the + * truncated string will be rounded to the nearest word ending. * * - * // Limit the characters - * echo Str::limit_chars('taylor otwell', 3); - * results in 'tay...' + * // Returns "Taylor..." + * echo Str::limit('Taylor Otwell', 3); + * + * // Limit the number of characters and append a custom ending + * echo Str::limit('Taylor Otwell', 3, '---'); * * * @param string $value @@ -112,25 +117,24 @@ public static function length($value) * @param string $end * @return string */ - public static function limit($value, $length = 100, $end = '...') + public static function limit($value, $limit = 100, $end = '...') { - if (static::length($value) <= $length) return $value; + if (static::length($value) < $limit) return $value; - if (function_exists('mb_substr')) - { - return mb_substr($value, 0, $length, Config::get('application.encoding')).$end; - } + $limit = preg_replace('/\s+?(\S+)?$/', '', substr($value, 0, $limit)); - return substr($value, 0, $length).$end; + return (static::length($limit) == static::length($value)) ? $value : $limit.$end; } /** * Limit the number of words in a string * * - * // Limit the words - * echo Str::limit_chars('This is a sentence.', 3); - * results in 'This is a...' + * // Returns "This is a..." + * echo Str::words('This is a sentence.', 3); + * + * // Limit the number of words and append a custom ending + * echo Str::words('This is a sentence.', 3, '---'); * * * @param string $value @@ -138,13 +142,13 @@ public static function limit($value, $length = 100, $end = '...') * @param string $end * @return string */ - public static function limit_words($value, $length = 100, $end = '...') + public static function words($value, $words = 100, $end = '...') { - $count = str_word_count($value,1); + $count = str_word_count($value, 1); - if ($count <= $length) return $value; + if ($count <= $words) return $value; - return implode(' ',array_slice($count,0,$length)).$end; + return implode(' ', array_slice($count, 0, $words)).$end; } /** diff --git a/laravel/validation/messages.php b/laravel/validation/messages.php index d8e3d2e2..4e5c61cb 100644 --- a/laravel/validation/messages.php +++ b/laravel/validation/messages.php @@ -93,7 +93,12 @@ public function get($key = null, $format = ':message') { if (is_null($key)) return $this->all($format); - return (array_key_exists($key, $this->messages)) ? $this->format($this->messages[$key], $format) : array(); + if (array_key_exists($key, $this->messages)) + { + return $this->format($this->messages[$key], $format); + } + + return array(); } /** diff --git a/laravel/validation/validator.php b/laravel/validation/validator.php index 91d6ec77..25c313b0 100644 --- a/laravel/validation/validator.php +++ b/laravel/validation/validator.php @@ -10,11 +10,25 @@ class Validator { /** - * The registered custom validators. + * The database connection that should be used by the validator. + * + * @var Database\Connection + */ + public $connection; + + /** + * The array being validated. * * @var array */ - protected static $validators = array(); + public $attributes; + + /** + * The post-validation error messages. + * + * @var Messages + */ + public $errors; /** * The validation rules. @@ -37,6 +51,13 @@ class Validator { */ protected $language; + /** + * The registered custom validators. + * + * @var array + */ + protected static $validators = array(); + /** * The size related validation rules. * @@ -51,27 +72,6 @@ class Validator { */ protected $numeric_rules = array('numeric', 'integer'); - /** - * The database connection that should be used by the validator. - * - * @var Database\Connection - */ - public $connection; - - /** - * The array being validated. - * - * @var array - */ - public $attributes; - - /** - * The post-validation error messages. - * - * @var Messages - */ - public $errors; - /** * Create a new validator instance. * @@ -163,29 +163,50 @@ protected function check($attribute, $rule) throw new \Exception("Validation rule [$rule] doesn't exist."); } - // No validation will be run for attributes that do not exist unless the rule being validated - // is "required" or "accepted". No other rules have implicit "required" checks. + // Extract the actual value for the attribute. We don't want every rule + // to worry about obtaining the value from the array of attributes. + $value = (isset($this->attributes[$attribute])) ? $this->attributes[$attribute] : null; + + // No validation will be run for attributes that do not exist unless the + // rule being validated is "required" or "accepted". No other rules have + // implicit "required" checks for validation. if ( ! $this->validate_required($attribute) and ! in_array($rule, array('required', 'accepted'))) return; - if ( ! $this->$validator($attribute, $parameters, $this)) + if ( ! $this->$validator($attribute, $value, $parameters, $this)) { - $message = $this->format_message($this->get_message($attribute, $rule), $attribute, $rule, $parameters); - - $this->errors->add($attribute, $message, $attribute, $rule, $parameters); + $this->error($attribute, $rule, $parameters); } } + /** + * Add an error message to the validator's collection of messages. + * + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return void + */ + protected function error($attribute, $rule, $parameters) + { + $message = $this->get_message($attribute, $rule); + + $message = $this->format_message($message, $attribute, $rule, $parameters); + + $this->errors->add($attribute, $message); + } + /** * Validate that a required attribute exists in the attributes array. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_required($attribute) + protected function validate_required($attribute, $value) { - if ( ! array_key_exists($attribute, $this->attributes)) return false; + if (is_null($value)) return false; - if (is_string($this->attributes[$attribute]) and trim($this->attributes[$attribute]) === '') return false; + if (is_string($value) and trim($value) === '') return false; return true; } @@ -194,12 +215,11 @@ protected function validate_required($attribute) * Validate that an attribute has a matching confirmation attribute. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_confirmed($attribute) + protected function validate_confirmed($attribute, $value) { - $value = $this->attributes[$attribute]; - $confirmation = $this->attributes[$attribute.'_confirmation']; return array_key_exists($attribute.'_confirmation', $this->attributes) and $value == $confirmation; @@ -211,12 +231,11 @@ protected function validate_confirmed($attribute) * This validation rule implies the attribute is "required". * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_accepted($attribute) + protected function validate_accepted($attribute, $value) { - $value = $this->attributes[$attribute]; - return $this->validate_required($attribute) and ($value == 'yes' or $value == '1'); } @@ -224,32 +243,35 @@ protected function validate_accepted($attribute) * Validate that an attribute is numeric. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_numeric($attribute) + protected function validate_numeric($attribute, $value) { - return is_numeric($this->attributes[$attribute]); + return is_numeric($value); } /** * Validate that an attribute is an integer. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_integer($attribute) + protected function validate_integer($attribute, $value) { - return filter_var($this->attributes[$attribute], FILTER_VALIDATE_INT) !== false; + return filter_var($value, FILTER_VALIDATE_INT) !== false; } /** * Validate the size of an attribute. * * @param string $attribute + * @param mixed $value * @param array $parameters * @return bool */ - protected function validate_size($attribute, $parameters) + protected function validate_size($attribute, $value, $parameters) { return $this->get_size($attribute) == $parameters[0]; } @@ -258,10 +280,11 @@ protected function validate_size($attribute, $parameters) * Validate the size of an attribute is between a set of values. * * @param string $attribute + * @param mixed $value * @param array $parameters * @return bool */ - protected function validate_between($attribute, $parameters) + protected function validate_between($attribute, $value, $parameters) { return $this->get_size($attribute) >= $parameters[0] and $this->get_size($attribute) <= $parameters[1]; } @@ -270,10 +293,11 @@ protected function validate_between($attribute, $parameters) * Validate the size of an attribute is greater than a minimum value. * * @param string $attribute + * @param mixed $value * @param array $parameters * @return bool */ - protected function validate_min($attribute, $parameters) + protected function validate_min($attribute, $value, $parameters) { return $this->get_size($attribute) >= $parameters[0]; } @@ -282,10 +306,11 @@ protected function validate_min($attribute, $parameters) * Validate the size of an attribute is less than a maximum value. * * @param string $attribute + * @param mixed $value * @param array $parameters * @return bool */ - protected function validate_max($attribute, $parameters) + protected function validate_max($attribute, $value, $parameters) { return $this->get_size($attribute) <= $parameters[0]; } @@ -305,31 +330,40 @@ protected function get_size($attribute) $value = $this->attributes[$attribute]; - return (array_key_exists($attribute, Input::file())) ? $value['size'] / 1024 : Str::length(trim($value)); + if (array_key_exists($attribute, Input::file())) + { + return $value['size'] / 1024; + } + else + { + return Str::length(trim($value)); + } } /** * Validate an attribute is contained within a list of values. * * @param string $attribute + * @param mixed $value * @param array $parameters * @return bool */ - protected function validate_in($attribute, $parameters) + protected function validate_in($attribute, $value, $parameters) { - return in_array($this->attributes[$attribute], $parameters); + return in_array($value, $parameters); } /** * Validate an attribute is not contained within a list of values. * * @param string $attribute + * @param mixed $value * @param array $parameters * @return bool */ - protected function validate_not_in($attribute, $parameters) + protected function validate_not_in($attribute, $value, $parameters) { - return ! in_array($this->attributes[$attribute], $parameters); + return ! in_array($value, $parameters); } /** @@ -338,49 +372,53 @@ protected function validate_not_in($attribute, $parameters) * If a database column is not specified, the attribute name will be used. * * @param string $attribute + * @param mixed $value * @param array $parameters * @return bool */ - protected function validate_unique($attribute, $parameters) + protected function validate_unique($attribute, $value, $parameters) { if ( ! isset($parameters[1])) $parameters[1] = $attribute; if (is_null($this->connection)) $this->connection = DB::connection(); - return $this->connection->table($parameters[0])->where($parameters[1], '=', $this->attributes[$attribute])->count() == 0; + return $this->connection->table($parameters[0])->where($parameters[1], '=', $value)->count() == 0; } /** * Validate than an attribute is a valid e-mail address. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_email($attribute) + protected function validate_email($attribute, $value) { - return filter_var($this->attributes[$attribute], FILTER_VALIDATE_EMAIL) !== false; + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; } /** * Validate than an attribute is a valid URL. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_url($attribute) + protected function validate_url($attribute, $value) { - return filter_var($this->attributes[$attribute], FILTER_VALIDATE_URL) !== false; + return filter_var($value, FILTER_VALIDATE_URL) !== false; } /** * Validate that an attribute is an active URL. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_active_url($attribute) + protected function validate_active_url($attribute, $value) { - $url = str_replace(array('http://', 'https://', 'ftp://'), '', Str::lower($this->attributes[$attribute])); + $url = str_replace(array('http://', 'https://', 'ftp://'), '', Str::lower($value)); return checkdnsrr($url); } @@ -389,9 +427,10 @@ protected function validate_active_url($attribute) * Validate the MIME type of a file is an image MIME type. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_image($attribute) + protected function validate_image($attribute, $value) { return $this->validate_mimes($attribute, array('jpg', 'png', 'gif', 'bmp')); } @@ -400,33 +439,36 @@ protected function validate_image($attribute) * Validate than an attribute contains only alphabetic characters. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_alpha($attribute) + protected function validate_alpha($attribute, $value) { - return preg_match('/^([a-z])+$/i', $this->attributes[$attribute]); + return preg_match('/^([a-z])+$/i', $value); } /** * Validate than an attribute contains only alpha-numeric characters. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_alpha_num($attribute) + protected function validate_alpha_num($attribute, $value) { - return preg_match('/^([a-z0-9])+$/i', $this->attributes[$attribute]); + return preg_match('/^([a-z0-9])+$/i', $value); } /** * Validate than an attribute contains only alpha-numeric characters, dashes, and underscores. * * @param string $attribute + * @param mixed $value * @return bool */ - protected function validate_alpha_dash($attribute) + protected function validate_alpha_dash($attribute, $value) { - return preg_match('/^([-a-z0-9_-])+$/i', $this->attributes[$attribute]); + return preg_match('/^([-a-z0-9_-])+$/i', $value); } /** @@ -549,9 +591,9 @@ protected function has_rule($attribute, $rules) */ protected function parse($rule) { - // The format for specifying validation rules and parameters follows a {rule}:{parameters} - // convention. For instance, "max:3" specifies that the value may only be 3 characters in - // length. And "unique:users" specifies that a value must be unique on the "users" table. + // The format for specifying validation rules and parameters follows + // a {rule}:{parameters} convention. For instance, "max:3" specifies + // that the value may only be 3 characters in length. $parameters = (($colon = strpos($rule, ':')) !== false) ? explode(',', substr($rule, $colon + 1)) : array(); return array(is_numeric($colon) ? substr($rule, 0, $colon) : $rule, $parameters); @@ -586,9 +628,9 @@ public function connection(\Laravel\Database\Connection $connection) */ public function __call($method, $parameters) { - // First we will slice the "validate_" prefix off of the validator since custom - // validators are not registered with such a prefix. We will then call the validator - // and pass it the parameters we received. + // First we will slice the "validate_" prefix off of the validator + // since custom validators are not registered with such a prefix. + // Then, if a custom validator exists, we will call it. if (isset(static::$validators[$method = substr($method, 9)])) { return call_user_func_array(static::$validators[$method], $parameters); diff --git a/laravel/view.php b/laravel/view.php index 47bb17f8..805291ab 100644 --- a/laravel/view.php +++ b/laravel/view.php @@ -133,9 +133,10 @@ protected static function name($name) { if (is_null(static::$composers)) static::$composers = require APP_PATH.'composers'.EXT; - // The view's name may specified in several different ways in the composers file. - // The composer may simple have a string value, which is the name. Or, the composer - // could have an array value in which a "name" key exists. + // The view's name may specified in several different ways in the + // composers file. The composer may simple have a string value, + // which is the name. Or, the composer could have an array + // value in which a "name" key exists. foreach (static::$composers as $key => $value) { if ($name === $value or (is_array($value) and $name === Arr::get($value, 'name'))) return $key; @@ -170,17 +171,15 @@ public function render() { static::compose($this); - // All nested views and responses are evaluated before the main view. This allows - // the assets used by these views to be added to the asset container before the + // All nested views and responses are evaluated before the main view. + // This allows the assets used by these views to be added to the asset + // container before the // main view is evaluated and dumps the links to the assets. foreach ($this->data as &$data) { if ($data instanceof View or $data instanceof Response) $data = $data->render(); } - // We don't want the view's contents to be rendered immediately, so we will fire - // up an output buffer to catch the view output. The output of the view will be - // rendered automatically later in the request lifecycle. ob_start() and extract($this->data, EXTR_SKIP); // If the view is a "Blade" view, we need to check the view for modifications @@ -200,14 +199,15 @@ public function render() */ protected function compile() { - // For simplicity, compiled views are stored in a single directory by the MD5 hash of - // their name. This allows us to avoid recreating the entire view directory structure - // within the compiled views directory. + // For simplicity, compiled views are stored in a single directory by + // the MD5 hash of their name. This allows us to avoid recreating the + // entire view directory structure within the compiled views directory. $compiled = STORAGE_PATH.'views/'.md5($this->view); - // The view will only be re-compiled if the view has been modified since the last compiled - // version of the view was created or no compiled view exists. Otherwise, the path will - // be returned without re-compiling. + // The view will only be re-compiled if the view has been modified + // since the last compiled version of the view was created or no + // compiled view exists. Otherwise, the path will be returned + // without re-compiling. if ((file_exists($compiled) and filemtime($this->path) > filemtime($compiled)) or ! file_exists($compiled)) { file_put_contents($compiled, Blade::parse($this->path));