From 30c83f265da82ee6256c0eda2495eca65536e5f0 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 14 Jun 2011 17:27:11 -0500 Subject: [PATCH] overall code refactoring. --- application/filters.php | 2 +- public/.htaccess | 8 +- public/css/.gitignore | 0 public/img/.gitignore | 0 public/index.php | 6 +- public/js/.gitignore | 0 system/config.php | 20 ++ system/crypt.php | 3 + system/db.php | 2 +- system/db/eloquent.php | 214 ++++++++++++++++-- system/db/eloquent/factory.php | 23 -- .../db/eloquent/{hydrate.php => hydrator.php} | 35 ++- system/db/eloquent/meta.php | 24 -- system/db/eloquent/relate.php | 97 -------- system/db/eloquent/warehouse.php | 69 ------ system/db/query/compiler.php | 30 ++- system/download.php | 3 + system/error.php | 14 +- system/filter.php | 18 +- system/hash.php | 7 + system/inflector.php | 6 + system/input.php | 88 +++---- system/lang.php | 13 ++ system/loader.php | 12 + system/redirect.php | 43 +++- system/request.php | 51 ++--- system/response.php | 129 ++++------- system/route.php | 4 +- system/route/finder.php | 86 +++++++ system/route/loader.php | 49 ++++ system/route/parser.php | 45 ++++ system/router.php | 117 +--------- system/session.php | 28 ++- system/url.php | 18 +- system/view.php | 15 ++ system/views/{error => }/exception.php | 0 36 files changed, 720 insertions(+), 559 deletions(-) create mode 100644 public/css/.gitignore create mode 100644 public/img/.gitignore create mode 100644 public/js/.gitignore delete mode 100644 system/db/eloquent/factory.php rename system/db/eloquent/{hydrate.php => hydrator.php} (86%) delete mode 100644 system/db/eloquent/meta.php delete mode 100644 system/db/eloquent/relate.php delete mode 100644 system/db/eloquent/warehouse.php create mode 100644 system/route/finder.php create mode 100644 system/route/loader.php create mode 100644 system/route/parser.php rename system/views/{error => }/exception.php (100%) diff --git a/application/filters.php b/application/filters.php index 5da720da9..0598a8ddc 100644 --- a/application/filters.php +++ b/application/filters.php @@ -35,7 +35,7 @@ return array( 'csrf' => function() { - return (Input::get('csrf_token') !== Form::raw_token()) ? Response::view('error/500', 500) : null; + return (Input::get('csrf_token') !== Form::raw_token()) ? Response::make(View::make('error/500'), 500) : null; }, ); \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess index 7bcb61ec7..e84ee2be0 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,8 +1,8 @@ - RewriteEngine on + RewriteEngine on - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^(.*)$ index.php/$1 [L] + RewriteRule ^(.*)$ index.php/$1 [L] \ No newline at end of file diff --git a/public/css/.gitignore b/public/css/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/public/img/.gitignore b/public/img/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/public/index.php b/public/index.php index 3a22aab4a..5ab4b7f9f 100644 --- a/public/index.php +++ b/public/index.php @@ -85,7 +85,7 @@ if (System\Config::get('session.driver') != '') // -------------------------------------------------------------- // Execute the global "before" filter. // -------------------------------------------------------------- -$response = System\Filter::call('before'); +$response = System\Filter::call('before', array(), true); // -------------------------------------------------------------- // Only execute the route function if the "before" filter did @@ -107,12 +107,12 @@ if (is_null($response)) } else { - $response = System\Response::view('error/404', 404); + $response = System\Response::make(View::make('error/404'), 404); } } else { - $response = ( ! $response instanceof System\Response) ? new System\Response($response) : $response; + $response = System\Response::prepare($response); } // ---------------------------------------------------------- diff --git a/public/js/.gitignore b/public/js/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/system/config.php b/system/config.php index 844742e22..25c8c3118 100644 --- a/system/config.php +++ b/system/config.php @@ -17,8 +17,14 @@ class Config { */ public static function get($key) { + // ----------------------------------------------------- + // Parse the key to separate the file and key name. + // ----------------------------------------------------- list($file, $key) = static::parse($key); + // ----------------------------------------------------- + // Load the appropriate configuration file. + // ----------------------------------------------------- static::load($file); return (array_key_exists($key, static::$items[$file])) ? static::$items[$file][$key] : null; @@ -33,8 +39,14 @@ class Config { */ public static function set($key, $value) { + // ----------------------------------------------------- + // Parse the key to separate the file and key name. + // ----------------------------------------------------- list($file, $key) = static::parse($key); + // ----------------------------------------------------- + // Load the appropriate configuration file. + // ----------------------------------------------------- static::load($file); static::$items[$file][$key] = $value; @@ -55,6 +67,11 @@ class Config { throw new \Exception("Invalid configuration key [$key]."); } + // ----------------------------------------------------- + // The left side of the dot is the file name, while + // the right side of the dot is the item within that + // file being requested. + // ----------------------------------------------------- return array($segments[0], implode('.', array_slice($segments, 1))); } @@ -66,6 +83,9 @@ class Config { */ public static function load($file) { + // ----------------------------------------------------- + // If we have already loaded the file, bail out. + // ----------------------------------------------------- if (array_key_exists($file, static::$items)) { return; diff --git a/system/crypt.php b/system/crypt.php index e00cd98f8..46edb0f90 100644 --- a/system/crypt.php +++ b/system/crypt.php @@ -48,6 +48,9 @@ class Crypt { mt_srand(); } + // ----------------------------------------------------- + // Create the Mcrypt input vector and encrypt the value. + // ----------------------------------------------------- $iv = mcrypt_create_iv(static::iv_size(), $random); $value = mcrypt_encrypt(static::$cipher, static::key(), $value, static::$mode, $iv); diff --git a/system/db.php b/system/db.php index bcfd2841c..987b60f3d 100644 --- a/system/db.php +++ b/system/db.php @@ -65,7 +65,7 @@ class DB { // For UPDATE and DELETE statements, return the number // or rows affected by the query. // - // For INSERT statements, return a boolean. + // For everything else, return a boolean. // --------------------------------------------------- if (strpos(Str::upper($sql), 'SELECT') === 0) { diff --git a/system/db/eloquent.php b/system/db/eloquent.php index 2284061c5..5f31bc2ae 100644 --- a/system/db/eloquent.php +++ b/system/db/eloquent.php @@ -68,6 +68,46 @@ abstract class Eloquent { */ public $query; + /** + * Get the table name for a model. + * + * @param string $class + * @return string + */ + public static function table($class) + { + // ----------------------------------------------------- + // Check for a table name override. + // ----------------------------------------------------- + if (property_exists($class, 'table')) + { + return $class::$table; + } + + return \System\Str::lower(\System\Inflector::plural($class)); + } + + /** + * Factory for creating new Eloquent model instances. + * + * @param string $class + * @return object + */ + public static function make($class) + { + // ----------------------------------------------------- + // Instantiate the Eloquent model. + // ----------------------------------------------------- + $model = new $class; + + // ----------------------------------------------------- + // Set the fluent query builder on the model. + // ----------------------------------------------------- + $model->query = Query::table(static::table($class)); + + return $model; + } + /** * Create a new model instance and set the relationships * that should be eagerly loaded. @@ -76,7 +116,14 @@ abstract class Eloquent { */ public static function with() { - $model = Eloquent\Factory::make(get_called_class()); + // ----------------------------------------------------- + // Create a new model instance. + // ----------------------------------------------------- + $model = static::make(get_called_class()); + + // ----------------------------------------------------- + // Set the relationships that should be eager loaded. + // ----------------------------------------------------- $model->includes = func_get_args(); return $model; @@ -90,7 +137,7 @@ abstract class Eloquent { */ public static function find($id) { - return Eloquent\Factory::make(get_called_class())->where('id', '=', $id)->first(); + return static::make(get_called_class())->where('id', '=', $id)->first(); } /** @@ -100,7 +147,7 @@ abstract class Eloquent { */ private function _get() { - return Eloquent\Hydrate::from($this); + return Eloquent\Hydrator::hydrate($this); } /** @@ -110,14 +157,7 @@ abstract class Eloquent { */ private function _first() { - $results = Eloquent\Hydrate::from($this->take(1)); - - if (count($results) > 0) - { - reset($results); - - return current($results); - } + return (count($results = Eloquent\Hydrator::hydrate($this->take(1))) > 0) ? reset($results) : null; } /** @@ -129,7 +169,8 @@ abstract class Eloquent { */ public function has_one($model, $foreign_key = null) { - return Eloquent\Relate::has_one($model, $foreign_key, $this); + $this->relating = __FUNCTION__; + return $this->has_one_or_many($model, $foreign_key); } /** @@ -141,23 +182,65 @@ abstract class Eloquent { */ public function has_many($model, $foreign_key = null) { - return Eloquent\Relate::has_many($model, $foreign_key, $this); + $this->relating = __FUNCTION__; + return $this->has_one_or_many($model, $foreign_key); + } + + /** + * Retrieve the query for a 1:1 or 1:* relationship. + * + * @param string $model + * @param string $foreign_key + * @return mixed + */ + private function has_one_or_many($model, $foreign_key) + { + // ----------------------------------------------------- + // Determine the foreign key for the relationship. + // + // The foreign key is typically the name of the related + // model with an appeneded _id. + // ----------------------------------------------------- + $this->relating_key = (is_null($foreign_key)) ? \System\Str::lower(get_class($this)).'_id' : $foreign_key; + + return static::make($model)->where($this->relating_key, '=', $this->id); } /** * Retrieve the query for a 1:1 belonging relationship. * * @param string $model + * @param string $foreign_key * @return mixed */ - public function belongs_to($model) + public function belongs_to($model, $foreign_key = null) { - // ----------------------------------------------------- - // Get the calling function name. - // ----------------------------------------------------- - list(, $caller) = debug_backtrace(false); + $this->relating = __FUNCTION__; - return Eloquent\Relate::belongs_to($caller, $model, $this); + // ----------------------------------------------------- + // Determine the foreign key of the relationship. + // ----------------------------------------------------- + if ( ! is_null($foreign_key)) + { + $this->relating_key = $foreign_key; + } + else + { + // ----------------------------------------------------- + // Get the calling function name. + // ----------------------------------------------------- + list(, $caller) = debug_backtrace(false); + + // ----------------------------------------------------- + // Determine the foreign key for the relationship. + // + // The foreign key for belonging relationships is the + // name of the relationship method with an appended _id. + // ----------------------------------------------------- + $this->relating_key = $caller['function'].'_id'; + } + + return static::make($model)->where('id', '=', $this->attributes[$this->relating_key]); } /** @@ -167,9 +250,39 @@ abstract class Eloquent { * @param string $table * @return mixed */ - public function has_many_and_belongs_to($model, $table = null) + public function has_and_belongs_to_many($model, $table = null) { - return Eloquent\Relate::has_many_and_belongs_to($model, $table, $this); + $this->relating = __FUNCTION__; + + // ----------------------------------------------------- + // Determine the intermediate table name. + // ----------------------------------------------------- + if ( ! is_null($table)) + { + $this->relating_table = $table; + } + else + { + // ----------------------------------------------------- + // By default, the intermediate table name is the plural + // names of the models arranged alphabetically and + // concatenated with an underscore. + // ----------------------------------------------------- + $models = array(\System\Inflector::plural($model), \System\Inflector::plural(get_class($this))); + sort($models); + + $this->relating_table = \System\Str::lower($models[0].'_'.$models[1]); + } + + // ----------------------------------------------------- + // Determine the foreign key for the relationship. + // ----------------------------------------------------- + $this->relating_key = $this->relating_table.'.'.\System\Str::lower(get_class($this)).'_id'; + + return static::make($model) + ->select(static::table($model).'.*') + ->join($this->relating_table, static::table($model).'.id', '=', $this->relating_table.'.'.\System\Str::lower($model).'_id') + ->where($this->relating_key, '=', $this->id); } /** @@ -188,7 +301,43 @@ abstract class Eloquent { return true; } - $result = Eloquent\Warehouse::put($this); + // ----------------------------------------------------- + // Get the class name of the Eloquent model. + // ----------------------------------------------------- + $model = get_class($this); + + // ----------------------------------------------------- + // Get a fresh query instance for the model. + // ----------------------------------------------------- + $this->query = Query::table(static::table($model)); + + // ----------------------------------------------------- + // Set the creation and update timestamps. + // ----------------------------------------------------- + if (property_exists($model, 'timestamps') and $model::$timestamps) + { + $this->updated_at = date('Y-m-d H:i:s'); + + if ( ! $this->exists) + { + $this->created_at = $this->updated_at; + } + } + + // ----------------------------------------------------- + // If the model already exists in the database, we only + // need to update it. Otherwise, we'll insert it. + // ----------------------------------------------------- + if ($this->exists) + { + $result = $this->query->where('id', '=', $this->attributes['id'])->update($this->dirty) == 1; + } + else + { + $this->attributes['id'] = $this->query->insert_get_id($this->attributes); + + $result = $this->exists = is_numeric($this->id); + } // ----------------------------------------------------- // The dirty attributes can be cleared after each save. @@ -209,7 +358,7 @@ abstract class Eloquent { // ----------------------------------------------------- if ($this->exists) { - return Eloquent\Warehouse::forget($this); + return Query::table(static::table(get_class($this)))->delete($this->id) == 1; } return $this->query->delete($id); @@ -252,12 +401,15 @@ abstract class Eloquent { public function __set($key, $value) { // ----------------------------------------------------- - // Is the key actually a relationship? + // If the key is a relationship, add it to the ignored. // ----------------------------------------------------- if (method_exists($this, $key)) { $this->ignore[$key] = $value; } + // ----------------------------------------------------- + // Set the attribute and add it to the dirty array. + // ----------------------------------------------------- else { $this->attributes[$key] = $value; @@ -288,11 +440,17 @@ abstract class Eloquent { */ public function __call($method, $parameters) { + // ----------------------------------------------------- + // Retrieve an array of models. + // ----------------------------------------------------- if ($method == 'get') { return $this->_get(); } + // ----------------------------------------------------- + // Retrieve the first model result. + // ----------------------------------------------------- if ($method == 'first') { return $this->_first(); @@ -320,13 +478,19 @@ abstract class Eloquent { */ public static function __callStatic($method, $parameters) { - $model = Eloquent\Factory::make(get_called_class()); + $model = static::make(get_called_class()); + // ----------------------------------------------------- + // Retrieve the entire table of models. + // ----------------------------------------------------- if ($method == 'get') { return $model->_get(); } + // ----------------------------------------------------- + // Retrieve the first model result. + // ----------------------------------------------------- if ($method == 'first') { return $model->_first(); diff --git a/system/db/eloquent/factory.php b/system/db/eloquent/factory.php deleted file mode 100644 index 221107007..000000000 --- a/system/db/eloquent/factory.php +++ /dev/null @@ -1,23 +0,0 @@ -query = \System\DB\Query::table(Meta::table($class)); - - return $model; - } - -} \ No newline at end of file diff --git a/system/db/eloquent/hydrate.php b/system/db/eloquent/hydrator.php similarity index 86% rename from system/db/eloquent/hydrate.php rename to system/db/eloquent/hydrator.php index 4b2a563b7..4689c955a 100644 --- a/system/db/eloquent/hydrate.php +++ b/system/db/eloquent/hydrator.php @@ -1,6 +1,6 @@ attributes = (array) $model; - $result->exists = true; + // ----------------------------------------------------- + // Set the attributes and existence flag on the model. + // ----------------------------------------------------- + $model->attributes = (array) $result; + $model->exists = true; // ----------------------------------------------------- // The results are keyed by the ID on the record. // ----------------------------------------------------- - $results[$result->id] = $result; + $models[$model->id] = $model; } - return $results; + return $models; } /** @@ -97,6 +100,9 @@ class Hydrate { $result->ignore[$include] = (strpos($eloquent->relating, 'has_many') === 0) ? array() : null; } + // ----------------------------------------------------- + // Eagerly load the relationship. + // ----------------------------------------------------- if ($eloquent->relating == 'has_one' or $eloquent->relating == 'has_many') { static::eagerly_load_one_or_many($eloquent->relating_key, $eloquent->relating, $include, $model, $results); @@ -203,7 +209,9 @@ class Hydrate { // We also add the foreign key to the select which will allow us // to match the models back to their parents. // ----------------------------------------------------- - $inclusions = $model->query->where_in($relating_key, array_keys($results))->get(Meta::table(get_class($model)).'.*', $relating_table.'.'.$foreign_key); + $inclusions = $model->query + ->where_in($relating_key, array_keys($results)) + ->get(\System\DB\Eloquent::table(get_class($model)).'.*', $relating_table.'.'.$foreign_key); // ----------------------------------------------------- // Get the class name of the related model. @@ -217,6 +225,9 @@ class Hydrate { { $related = new $class; + // ----------------------------------------------------- + // Set the attributes and existence flag on the model. + // ----------------------------------------------------- $related->exists = true; $related->attributes = (array) $inclusion; diff --git a/system/db/eloquent/meta.php b/system/db/eloquent/meta.php deleted file mode 100644 index 0900c3c5c..000000000 --- a/system/db/eloquent/meta.php +++ /dev/null @@ -1,24 +0,0 @@ -relating = __FUNCTION__; - return static::has_one_or_many($model, $foreign_key, $eloquent); - } - - /** - * Retrieve the query for a 1:* relationship. - * - * @param string $model - * @param string $foreign_key - * @param object $eloquent - * @return mixed - */ - public static function has_many($model, $foreign_key, $eloquent) - { - $eloquent->relating = __FUNCTION__; - return static::has_one_or_many($model, $foreign_key, $eloquent); - } - - /** - * Retrieve the query for a 1:1 or 1:* relationship. - * - * @param string $model - * @param string $foreign_key - * @param object $eloquent - * @return mixed - */ - private static function has_one_or_many($model, $foreign_key, $eloquent) - { - $eloquent->relating_key = (is_null($foreign_key)) ? \System\Str::lower(get_class($eloquent)).'_id' : $foreign_key; - return Factory::make($model)->where($eloquent->relating_key, '=', $eloquent->id); - } - - /** - * Retrieve the query for a 1:1 belonging relationship. - * - * @param array $caller - * @param string $model - * @param object $eloquent - * @return mixed - */ - public static function belongs_to($caller, $model, $eloquent) - { - $eloquent->relating = __FUNCTION__; - $eloquent->relating_key = $caller['function'].'_id'; - - return Factory::make($model)->where('id', '=', $eloquent->attributes[$eloquent->relating_key]); - } - - /** - * Retrieve the query for a *:* relationship. - * - * @param string $model - * @param string $table - * @param object $eloquent - * @return mixed - */ - public static function has_many_and_belongs_to($model, $table, $eloquent) - { - // ----------------------------------------------------- - // Figure out the intermediate table name. - // ----------------------------------------------------- - if (is_null($table)) - { - $models = array(\System\Str::lower($model), \System\Str::lower(get_class($eloquent))); - sort($models); - - $eloquent->relating_table = implode('_', $models); - } - else - { - $eloquent->relating_table = $table; - } - - $eloquent->relating = __FUNCTION__; - $eloquent->relating_key = $eloquent->relating_table.'.'.\System\Str::lower(get_class($eloquent)).'_id'; - - return Factory::make($model) - ->select(Meta::table($model).'.*') - ->join($eloquent->relating_table, Meta::table($model).'.id', '=', $eloquent->relating_table.'.'.\System\Str::lower($model).'_id') - ->where($eloquent->relating_key, '=', $eloquent->id); - } - -} \ No newline at end of file diff --git a/system/db/eloquent/warehouse.php b/system/db/eloquent/warehouse.php deleted file mode 100644 index 6fdc1c6ec..000000000 --- a/system/db/eloquent/warehouse.php +++ /dev/null @@ -1,69 +0,0 @@ -query = \System\DB\Query::table(Meta::table($model)); - - // ----------------------------------------------------- - // Set the creation and update timestamps. - // ----------------------------------------------------- - if (property_exists($model, 'timestamps') and $model::$timestamps) - { - static::timestamp($eloquent); - } - - if ($eloquent->exists) - { - return ($eloquent->query->where('id', '=', $eloquent->attributes['id'])->update($eloquent->dirty) == 1) ? true : false; - } - else - { - $eloquent->attributes['id'] = $eloquent->query->insert_get_id($eloquent->attributes); - } - - $eloquent->exists = true; - - return true; - } - - /** - * Delete an Eloquent model from the database. - * - * @param object $eloquent - * @return bool - */ - public static function forget($eloquent) - { - return \System\DB::table(Meta::table(get_class($eloquent)))->where('id', '=', $eloquent->id)->delete() == 1; - } - - /** - * Set the activity timestamps on a model. - * - * @param object $eloquent - * @return void - */ - private static function timestamp($eloquent) - { - $eloquent->updated_at = date('Y-m-d H:i:s'); - - if ( ! $eloquent->exists) - { - $eloquent->created_at = $eloquent->updated_at; - } - } - -} \ No newline at end of file diff --git a/system/db/query/compiler.php b/system/db/query/compiler.php index 61889f98a..2a68924c2 100644 --- a/system/db/query/compiler.php +++ b/system/db/query/compiler.php @@ -10,18 +10,30 @@ class Compiler { */ public static function select($query) { + // ---------------------------------------------------- + // Add the SELECT, FROM, and WHERE clause to the query. + // ---------------------------------------------------- $sql = $query->select.' '.$query->from.' '.$query->where; + // ---------------------------------------------------- + // Add the ORDER BY clause to the query. + // ---------------------------------------------------- if (count($query->orderings) > 0) { $sql .= ' ORDER BY '.implode(', ', $query->orderings); } + // ---------------------------------------------------- + // Add the LIMIT clause to the query. + // ---------------------------------------------------- if ( ! is_null($query->limit)) { $sql .= ' LIMIT '.$query->limit; } + // ---------------------------------------------------- + // Add the OFFSET clause to the query. + // ---------------------------------------------------- if ( ! is_null($query->offset)) { $sql .= ' OFFSET '.$query->offset; @@ -39,11 +51,14 @@ class Compiler { */ public static function insert($query, $values) { + // ----------------------------------------------------- + // Begin the INSERT statement. + // ----------------------------------------------------- $sql = 'INSERT INTO '.$query->table.' ('; - // --------------------------------------------------- + // ---------------------------------------------------- // Wrap each column name in keyword identifiers. - // --------------------------------------------------- + // ---------------------------------------------------- $columns = array(); foreach (array_keys($values) as $column) @@ -51,6 +66,9 @@ class Compiler { $columns[] = $query->wrap($column); } + // ----------------------------------------------------- + // Concatenante the columns and values to the statement. + // ----------------------------------------------------- return $sql .= implode(', ', $columns).') VALUES ('.$query->parameterize($values).')'; } @@ -63,10 +81,13 @@ class Compiler { */ public static function update($query, $values) { + // ---------------------------------------------------- + // Initialize the UPDATE statement. + // ---------------------------------------------------- $sql = 'UPDATE '.$query->table.' SET '; // --------------------------------------------------- - // Add each column set the query. + // Add each column set the statement. // --------------------------------------------------- $columns = array(); @@ -75,6 +96,9 @@ class Compiler { $columns[] = $query->wrap($column).' = ?'; } + // --------------------------------------------------- + // Concatenate the SETs to the statement. + // --------------------------------------------------- return $sql .= implode(', ', $columns).' '.$query->where; } diff --git a/system/download.php b/system/download.php index a8785d8b6..37778019b 100644 --- a/system/download.php +++ b/system/download.php @@ -109,6 +109,9 @@ class Download { */ public static function file($path, $name = null) { + // ----------------------------------------------------- + // If no filename was given, use the path basename. + // ----------------------------------------------------- if (is_null($name)) { $name = basename($path); diff --git a/system/error.php b/system/error.php index b11b7e763..2520a8a46 100644 --- a/system/error.php +++ b/system/error.php @@ -72,13 +72,13 @@ class Error { if (Config::get('error.detail')) { - $view = View::make('error/exception') - ->bind('severity', $severity) - ->bind('message', $message) - ->bind('file', $file) - ->bind('line', $e->getLine()) - ->bind('trace', $e->getTraceAsString()) - ->bind('contexts', static::context($file, $e->getLine())); + $view = View::make('exception') + ->bind('severity', $severity) + ->bind('message', $message) + ->bind('file', $file) + ->bind('line', $e->getLine()) + ->bind('trace', $e->getTraceAsString()) + ->bind('contexts', static::context($file, $e->getLine())); Response::make($view, 500)->send(); } diff --git a/system/filter.php b/system/filter.php index 20b492ca2..f5ed5faaa 100644 --- a/system/filter.php +++ b/system/filter.php @@ -14,15 +14,22 @@ class Filter { * * @param string $filter * @param array $parameters + * @param bool $override * @return mixed */ - public static function call($filters, $parameters = array()) + public static function call($filters, $parameters = array(), $override = false) { + // -------------------------------------------------------------- + // Load the filters if necessary. + // -------------------------------------------------------------- if (is_null(static::$filters)) { static::$filters = require APP_PATH.'filters'.EXT; } + // -------------------------------------------------------------- + // Filters can be comma-delimited, so spin through each one. + // -------------------------------------------------------------- foreach (explode(', ', $filters) as $filter) { if ( ! isset(static::$filters[$filter])) @@ -33,10 +40,13 @@ class Filter { $response = call_user_func_array(static::$filters[$filter], $parameters); // -------------------------------------------------------------- - // If the filter returned a response, return it since route - // filters can override route methods. + // If overriding is set to true and the filter returned a + // response, return that response. + // + // Overriding allows for convenient halting of the request + // flow for things like authentication, CSRF protection, etc. // -------------------------------------------------------------- - if ( ! is_null($response)) + if ( ! is_null($response) and $override) { return $response; } diff --git a/system/hash.php b/system/hash.php index b044c0b7a..66a87e2d6 100644 --- a/system/hash.php +++ b/system/hash.php @@ -25,7 +25,14 @@ class Hash { */ public function __construct($value, $salt = null) { + // ------------------------------------------------------- + // If no salt is given, we'll create a random salt to + // use when hashing the password. + // + // Otherwise, we will use the given salt. + // ------------------------------------------------------- $this->salt = (is_null($salt)) ? Str::random(16) : $salt; + $this->value = sha1($value.$this->salt); } diff --git a/system/inflector.php b/system/inflector.php index cc7e4fbed..36a637cbf 100644 --- a/system/inflector.php +++ b/system/inflector.php @@ -121,6 +121,9 @@ class Inflector { */ public static function plural($value) { + // ----------------------------------------------------- + // If we have already pluralized this word, return it. + // ----------------------------------------------------- if (array_key_exists($value, static::$plural_cache)) { return static::$plural_cache[$value]; @@ -169,6 +172,9 @@ class Inflector { */ public static function singular($value) { + // ----------------------------------------------------- + // If we have already singularized this word, return it. + // ----------------------------------------------------- if (array_key_exists($value, static::$singular_cache)) { return static::$singular_cache[$value]; diff --git a/system/input.php b/system/input.php index ba9d75df0..513ca658d 100644 --- a/system/input.php +++ b/system/input.php @@ -20,17 +20,6 @@ class Input { return ( ! is_null(static::get($key))); } - /** - * Determine if the old input data contains an item. - * - * @param string $key - * @return bool - */ - public static function has_old($key) - { - return ( ! is_null(static::old($key))); - } - /** * Get an item from the input data. * @@ -40,10 +29,28 @@ class Input { */ public static function get($key = null, $default = null) { - static::hydrate(); + // ----------------------------------------------------- + // Has the input data been hydrated for the request? + // ----------------------------------------------------- + if (is_null(static::$input)) + { + static::hydrate(); + } + return static::from_array(static::$input, $key, $default); } + /** + * Determine if the old input data contains an item. + * + * @param string $key + * @return bool + */ + public static function has_old($key) + { + return ( ! is_null(static::old($key))); + } + /** * Get input data from the previous request. * @@ -86,39 +93,36 @@ class Input { */ public static function hydrate() { - if (is_null(static::$input)) + switch (Request::method()) { - switch (Request::method()) - { - case 'GET': - static::$input =& $_GET; - break; + case 'GET': + static::$input =& $_GET; + break; - case 'POST': + case 'POST': + static::$input =& $_POST; + break; + + case 'PUT': + case 'DELETE': + // ---------------------------------------------------------------------- + // Typically, browsers do not support PUT and DELETE methods on HTML + // forms. So, we simulate them using a hidden POST variable. + // + // If the request method is being "spoofed", we'll move the POST array + // into the PUT / DELETE array. + // ---------------------------------------------------------------------- + if (isset($_POST['request_method']) and ($_POST['request_method'] == 'PUT' or $_POST['request_method'] == 'DELETE')) + { static::$input =& $_POST; - break; - - case 'PUT': - case 'DELETE': - // ---------------------------------------------------------------------- - // Typically, browsers do not support PUT and DELETE methods on HTML - // forms. So, we simulate them using a hidden POST variable. - // - // If the request method is being "spoofed", we'll move the POST array - // into the PUT / DELETE array. - // ---------------------------------------------------------------------- - if (isset($_POST['request_method']) and ($_POST['request_method'] == 'PUT' or $_POST['request_method'] == 'DELETE')) - { - static::$input =& $_POST; - } - // ---------------------------------------------------------------------- - // If the request is a true PUT request, read the php://input file. - // ---------------------------------------------------------------------- - else - { - parse_str(file_get_contents('php://input'), static::$input); - } - } + } + // ---------------------------------------------------------------------- + // If the request is a true PUT request, read the php://input file. + // ---------------------------------------------------------------------- + else + { + parse_str(file_get_contents('php://input'), static::$input); + } } } diff --git a/system/lang.php b/system/lang.php index ad5620f7f..2f5e8fcc2 100644 --- a/system/lang.php +++ b/system/lang.php @@ -65,8 +65,14 @@ class Lang { $language = Config::get('application.language'); } + // ----------------------------------------------------- + // Parse the key to separate the file and key name. + // ----------------------------------------------------- list($file, $line) = $this->parse($this->key); + // ----------------------------------------------------- + // Load the appropriate language file. + // ----------------------------------------------------- $this->load($file, $language); // -------------------------------------------------------------- @@ -107,6 +113,10 @@ class Lang { throw new \Exception("Invalid language key [$key]."); } + // -------------------------------------------------------------- + // The left side of the dot is the file name, while the right + // side of the dot is the item within that file being requested. + // -------------------------------------------------------------- return array($segments[0], implode('.', array_slice($segments, 1))); } @@ -119,6 +129,9 @@ class Lang { */ private function load($file, $language) { + // -------------------------------------------------------------- + // If we have already loaded the language file, bail out. + // -------------------------------------------------------------- if (in_array($language.$file, static::$loaded)) { return; diff --git a/system/loader.php b/system/loader.php index cb0db2d24..e7c963a8f 100644 --- a/system/loader.php +++ b/system/loader.php @@ -18,18 +18,30 @@ return function($class) { return class_alias($aliases[$class], $class); } + // ---------------------------------------------------------- + // Is the class a Laravel framework class? + // ---------------------------------------------------------- if (file_exists($path = BASE_PATH.$file.EXT)) { require $path; } + // ---------------------------------------------------------- + // Is the class in the application/models directory? + // ---------------------------------------------------------- elseif (file_exists($path = APP_PATH.'models/'.$file.EXT)) { require $path; } + // ---------------------------------------------------------- + // Is the class in the application/packages directory? + // ---------------------------------------------------------- elseif (file_exists($path = APP_PATH.'packages/'.$file.EXT)) { require $path; } + // ---------------------------------------------------------- + // Is the class anywhere in the application directory? + // ---------------------------------------------------------- elseif (file_exists($path = APP_PATH.$file.EXT)) { require $path; diff --git a/system/redirect.php b/system/redirect.php index 029432ca2..3790844fd 100644 --- a/system/redirect.php +++ b/system/redirect.php @@ -2,6 +2,24 @@ class Redirect { + /** + * The redirect response. + * + * @var Response + */ + public $response; + + /** + * Create a new redirect instance. + * + * @param Response $response + * @return void + */ + public function __construct($response) + { + $this->response = $response; + } + /** * Create a redirect response. * @@ -16,8 +34,29 @@ class Redirect { $url = URL::to($url, $https); return ($method == 'refresh') - ? Response::make('', $status)->header('Refresh', '0;url='.$url) - : Response::make('', $status)->header('Location', $url); + ? new static(Response::make('', $status)->header('Refresh', '0;url='.$url)) + : new static(Response::make('', $status)->header('Location', $url)); + } + + /** + * Add an item to the session flash data. + * + * @param string $key + * @param mixed $value + * @return Response + */ + public function with($key, $value) + { + // ---------------------------------------------------- + // Since this method uses sessions, make sure a driver + // has been specified in the configuration file. + // ---------------------------------------------------- + if (Config::get('session.driver') != '') + { + Session::flash($key, $value); + } + + return $this; } /** diff --git a/system/request.php b/system/request.php index 32da45ba8..671de97bc 100644 --- a/system/request.php +++ b/system/request.php @@ -16,44 +16,43 @@ class Request { */ public static function uri() { + // ------------------------------------------------------- + // If we have already determined the URI, return it. + // ------------------------------------------------------- if ( ! is_null(static::$uri)) { return static::$uri; } + // ------------------------------------------------------- + // If the PATH_INFO is available, use it. + // ------------------------------------------------------- if (isset($_SERVER['PATH_INFO'])) { - return static::$uri = static::tidy($_SERVER['PATH_INFO']); + $uri = $_SERVER['PATH_INFO']; } - - if ( ! isset($_SERVER['REQUEST_URI'])) + // ------------------------------------------------------- + // No PATH_INFO? Let's try REQUEST_URI. + // ------------------------------------------------------- + elseif (isset($_SERVER['REQUEST_URI'])) { - throw new \Exception('Unable to determine the request URI.'); + $uri = str_replace('/index.php', '', $_SERVER['REQUEST_URI']); } - - $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); - - // -------------------------------------------------------------- - // Slice the application URL off of the URI. - // -------------------------------------------------------------- - if (strpos($uri, $base_url = parse_url(Config::get('application.url'), PHP_URL_PATH)) === 0) + // ------------------------------------------------------- + // Neither PATH_INFO or REQUEST_URI are available. + // ------------------------------------------------------- + else { - $uri = substr($uri, strlen($base_url)); + throw new \Exception('Unable to determine the request URI.'); } - return static::$uri = static::tidy($uri); - } + $uri = trim($uri, '/'); - /** - * Tidy up a URI for use by Laravel. For empty URIs, a forward - * slash will be returned. - * - * @param string $uri - * @return string - */ - private static function tidy($uri) - { - return ($uri != '/') ? Str::lower(trim($uri, '/')) : '/'; + // ------------------------------------------------------- + // If the requests is to the root of the application, we + // always return a single forward slash. + // ------------------------------------------------------- + return static::$uri = ($uri == '') ? '/' : Str::lower($uri); } /** @@ -64,8 +63,8 @@ class Request { public static function method() { // -------------------------------------------------------------- - // The method can be spoofed using a POST variable. This allows - // HTML forms to simulate PUT and DELETE methods. + // The method can be spoofed using a POST variable, allowing HTML + // forms to simulate PUT and DELETE requests. // -------------------------------------------------------------- return (isset($_POST['request_method'])) ? $_POST['request_method'] : $_SERVER['REQUEST_METHOD']; } diff --git a/system/response.php b/system/response.php index af68c0b18..c4468e44d 100644 --- a/system/response.php +++ b/system/response.php @@ -102,15 +102,25 @@ class Response { } /** - * Factory for creating new view response instances. + * Take a value returned by a route and prepare a Response instance. * - * @param string $view - * @param int $status + * @param mixed $response * @return Response */ - public static function view($view, $status = 200) + public static function prepare($response) { - return static::make(View::make($view), $status); + // -------------------------------------------------------------- + // If the response is a Redirect instance, grab the Response. + // -------------------------------------------------------------- + if ($response instanceof Redirect) + { + $response = $response->response; + } + + // -------------------------------------------------------------- + // Make sure the response is an instance of the Response class. + // -------------------------------------------------------------- + return ( ! $response instanceof Response) ? new static($response) : $response; } /** @@ -120,6 +130,9 @@ class Response { */ public function send() { + // ------------------------------------------------- + // If a Content-Type header has not been set, do it. + // ------------------------------------------------- if ( ! array_key_exists('Content-Type', $this->headers)) { $this->header('Content-Type', 'text/html; charset=utf-8'); @@ -130,14 +143,7 @@ class Response { // ------------------------------------------------- if ( ! headers_sent()) { - $protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'; - - header($protocol.' '.$this->status.' '.$this->statuses[$this->status]); - - foreach ($this->headers as $name => $value) - { - header($name.': '.$value, true); - } + $this->send_headers(); } // ------------------------------------------------- @@ -146,6 +152,32 @@ class Response { echo (string) $this->content; } + /** + * Send the response headers to the browser. + * + * @return void + */ + public function send_headers() + { + // ------------------------------------------------- + // Get the proper protocol. + // ------------------------------------------------- + $protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'; + + // ------------------------------------------------- + // Send the protocol and status header. + // ------------------------------------------------- + header($protocol.' '.$this->status.' '.$this->statuses[$this->status]); + + // ------------------------------------------------- + // Send the rest of the response headers. + // ------------------------------------------------- + foreach ($this->headers as $name => $value) + { + header($name.': '.$value, true); + } + } + /** * Add a header to the response. * @@ -159,23 +191,6 @@ class Response { return $this; } - /** - * Add an item to the session flash data. - * - * @param string $key - * @param mixed $value - * @return Response - */ - public function with($key, $value) - { - if (Config::get('session.driver') != '') - { - Session::flash($key, $value); - } - - return $this; - } - /** * Determine if the response is a redirect. * @@ -186,58 +201,4 @@ class Response { return $this->status == 301 or $this->status == 302; } - /** - * Magic Method for getting response View data. - */ - public function __get($key) - { - // ------------------------------------------------------ - // Attempt to get the data from the View. - // ------------------------------------------------------ - if ($this->content instanceof View) - { - return $this->content->$key; - } - } - - /** - * Magic Method for setting response View data. - */ - public function __set($key, $value) - { - // ------------------------------------------------------ - // Attempt to set the data on the View. - // ------------------------------------------------------ - if ($this->content instanceof View) - { - $this->content->bind($key, $value); - } - } - - /** - * Magic Method for handling dynamic method calls. - */ - public function __call($method, $parameters) - { - // ------------------------------------------------------ - // Attempt to the pass the method to the View instance. - // ------------------------------------------------------ - if ($this->content instanceof View and method_exists($this->content, $method)) - { - call_user_func_array(array($this->content, $method), $parameters); - - return $this; - } - - throw new \Exception("Method [$method] does not exist on the Response class."); - } - - /** - * Get the content of the response. - */ - public function __toString() - { - return (string) $this->content; - } - } \ No newline at end of file diff --git a/system/route.php b/system/route.php index 5f89d53fb..f778d90fe 100644 --- a/system/route.php +++ b/system/route.php @@ -55,7 +55,7 @@ class Route { // -------------------------------------------------------------- // Call the "before" route filters. // -------------------------------------------------------------- - $response = isset($this->route['before']) ? Filter::call($this->route['before']) : null; + $response = isset($this->route['before']) ? Filter::call($this->route['before'], array(), true) : null; // -------------------------------------------------------------- // Call the route callback. @@ -66,7 +66,7 @@ class Route { } } - $response = ( ! $response instanceof Response) ? new Response($response) : $response; + $response = Response::prepare($response); // -------------------------------------------------------------- // Call the "after" route filters. diff --git a/system/route/finder.php b/system/route/finder.php new file mode 100644 index 000000000..fb932a7ec --- /dev/null +++ b/system/route/finder.php @@ -0,0 +1,86 @@ +getSubIterator(); + + if ($route['name'] == $name) + { + return static::$names[$name] = array($arrayIterator->key() => iterator_to_array($route)); + } + } + } + + /** + * Load all of the routes from the routes directory. + * + * @return array + */ + private static function load() + { + $routes = array(); + + // -------------------------------------------------------------- + // Merge all of the various route files together. + // -------------------------------------------------------------- + foreach (glob(APP_PATH.'routes/*') as $file) + { + if (filetype($file) == 'file') + { + $routes = array_merge(require $file, $routes); + } + } + + return $routes; + } + +} \ No newline at end of file diff --git a/system/route/loader.php b/system/route/loader.php new file mode 100644 index 000000000..cc5929e6f --- /dev/null +++ b/system/route/loader.php @@ -0,0 +1,49 @@ +getSubIterator(); - - if ($route['name'] == $name) - { - return static::$names[$name] = array($arrayIterator->key() => iterator_to_array($route)); - } - } - } - - /** - * Get the parameters that should be passed to the route callback. - * - * @param array $uri_segments - * @param array $route_segments - * @return array - */ - private static function parameters($uri_segments, $route_segments) - { - $parameters = array(); - - for ($i = 0; $i < count($route_segments); $i++) - { - // -------------------------------------------------------------- - // Any segment wrapped in parentheses is a parameter. - // -------------------------------------------------------------- - if (strpos($route_segments[$i], '(') === 0) - { - $parameters[] = $uri_segments[$i]; - } - } - - return $parameters; - } - - /** - * Load the routes based on the request URI. - * - * @param string $uri - * @return void - */ - private static function routes($uri) - { - // -------------------------------------------------------------- - // If a route directory is being used, load the route file - // corresponding to the first segment of the URI. - // -------------------------------------------------------------- - if (is_dir(APP_PATH.'routes')) - { - if ($uri == '/') - { - if ( ! file_exists(APP_PATH.'routes/home'.EXT)) - { - throw new \Exception("A [home] route file is required when using a route directory."); - } - - return require APP_PATH.'routes/home'.EXT; - } - else - { - $segments = explode('/', trim($uri, '/')); - - if ( ! file_exists(APP_PATH.'routes/'.$segments[0].EXT)) - { - throw new \Exception("No route file defined for routes beginning with [".$segments[0]."]"); - } - - return require APP_PATH.'routes/'.$segments[0].EXT; - } - } - // -------------------------------------------------------------- - // If no route directory is being used, we can simply load the - // routes file from the application directory. - // -------------------------------------------------------------- - else - { - return require APP_PATH.'routes'.EXT; - } - } - } \ No newline at end of file diff --git a/system/session.php b/system/session.php index 5059aa160..695b71314 100644 --- a/system/session.php +++ b/system/session.php @@ -23,6 +23,9 @@ class Session { */ public static function driver() { + // ----------------------------------------------------- + // Create the session driver if we haven't already. + // ----------------------------------------------------- if (is_null(static::$driver)) { static::$driver = Session\Factory::make(Config::get('session.driver')); @@ -109,21 +112,6 @@ class Session { return $default; } - /** - * Get an item from the session and delete it. - * - * @param string $key - * @return mixed - */ - public static function once($key, $default = null) - { - $value = static::get($key, $default); - - static::forget($key); - - return $value; - } - /** * Write an item to the session. * @@ -187,9 +175,19 @@ class Session { */ public static function close() { + // ----------------------------------------------------- + // Flash the old input data to the session. + // ----------------------------------------------------- static::flash('laravel_old_input', Input::get()); + + // ----------------------------------------------------- + // Age the flash data. + // ----------------------------------------------------- static::age_flash(); + // ----------------------------------------------------- + // Save the session data to storage. + // ----------------------------------------------------- static::driver()->save(static::$session); // ----------------------------------------------------- diff --git a/system/url.php b/system/url.php index 22ac97010..f57d18824 100644 --- a/system/url.php +++ b/system/url.php @@ -52,13 +52,13 @@ class URL { */ public static function to_route($name, $parameters = array(), $https = false) { - if ( ! is_null($route = Router::find($name))) + if ( ! is_null($route = Route\Finder::find($name))) { - $uris = explode(', ', key($route)); - // ---------------------------------------------------- // Get the first URI assigned to the route. // ---------------------------------------------------- + $uris = explode(', ', key($route)); + $uri = substr($uris[0], strpos($uris[0], '/')); // ---------------------------------------------------- @@ -75,6 +75,18 @@ class URL { throw new \Exception("Error generating named route for route [$name]. Route is not defined."); } + /** + * Generate a HTTPS URL from a route name. + * + * @param string $name + * @param array $parameters + * @return string + */ + public static function to_secure_route($name, $parameters = array()) + { + return static::to_route($name, $parameters, true); + } + /** * Generate a URL friendly "slug". * diff --git a/system/view.php b/system/view.php index 7844ed44c..fee9a1a56 100644 --- a/system/view.php +++ b/system/view.php @@ -56,10 +56,16 @@ class View { */ private function load($view) { + // ----------------------------------------------------- + // Does the view exist in the application directory? + // ----------------------------------------------------- if (file_exists($path = APP_PATH.'views/'.$view.EXT)) { return file_get_contents($path); } + // ----------------------------------------------------- + // Does the view exist in the system directory? + // ----------------------------------------------------- elseif (file_exists($path = SYS_PATH.'views/'.$view.EXT)) { return file_get_contents($path); @@ -90,6 +96,9 @@ class View { */ public function get() { + // ----------------------------------------------------- + // Set the last rendered view name to the current view. + // ----------------------------------------------------- static::$last = $this->view; // ----------------------------------------------------- @@ -103,8 +112,14 @@ class View { } } + // ----------------------------------------------------- + // Extract the view data into the local scope. + // ----------------------------------------------------- extract($this->data, EXTR_SKIP); + // ----------------------------------------------------- + // Get the string content of the view. + // ----------------------------------------------------- ob_start(); echo eval('?>'.$this->load($this->view)); diff --git a/system/views/error/exception.php b/system/views/exception.php similarity index 100% rename from system/views/error/exception.php rename to system/views/exception.php