diff --git a/application/bundles.php b/application/bundles.php new file mode 100644 index 00000000..49d126bb --- /dev/null +++ b/application/bundles.php @@ -0,0 +1,36 @@ + array( +| 'location' => 'admin', +| 'handles' => 'admin', +| ), +| +| Note that the "location" is relative to the "bundles" directory. +| Now the bundle will be recognized by Laravel and will be able +| to respond to requests beginning with "admin"! +| +| Have a bundle that lives in the root of the bundle directory +| and doesn't respond to any requests? Just add the bundle +| name to the array and we'll take care of the rest. +| +*/ + +return array(); \ No newline at end of file diff --git a/application/config/application.php b/application/config/application.php index fe0f3472..d81b4221 100644 --- a/application/config/application.php +++ b/application/config/application.php @@ -97,35 +97,6 @@ 'timezone' => 'UTC', - /* - |-------------------------------------------------------------------------- - | Bundle Options - |-------------------------------------------------------------------------- - | - | Here you may specify options related to application bundles, such as the - | amount of time the bundle manifest is cached. Each option is detailed - | below with suggestions for sensible values. - | - | Cache: - | - | All bundles have a "bundle.info" file which contains information such - | as the name of a bundle and the URIs it responds to. This value is - | the number of minutes that bundle info is cached. - | - | Auto: - | - | You may wish to auto-start some bundles instead of lazy-loading them. - | This is useful for debug bundles as well as bundles that are used - | throughout your application. You may specify which bundles should - | be auto-loaded in this array. - | - */ - - 'bundle' => array( - 'cache' => 0, - 'auto' => array(), - ), - /* |-------------------------------------------------------------------------- | Class Aliases @@ -168,6 +139,7 @@ 'Redis' => 'Laravel\\Redis', 'Request' => 'Laravel\\Request', 'Response' => 'Laravel\\Response', + 'Route' => 'Laravel\\Routing\\Route', 'Router' => 'Laravel\\Routing\\Router', 'Schema' => 'Laravel\\Database\\Schema', 'Section' => 'Laravel\\Section', diff --git a/application/controllers/base.php b/application/controllers/base.php index 71d03416..177d887a 100644 --- a/application/controllers/base.php +++ b/application/controllers/base.php @@ -7,7 +7,7 @@ class Base_Controller extends Controller { * * @param string $method * @param array $parameters - * @return Laravel\Response + * @return Response */ public function __call($method, $parameters) { diff --git a/application/routes.php b/application/routes.php index 7bfd2439..464ceb29 100644 --- a/application/routes.php +++ b/application/routes.php @@ -12,32 +12,57 @@ | | Let's respond to a simple GET request to http://example.com/hello: | -| Router::register('GET /hello', function() +| Route::get('hello', function() | { | return 'Hello World!'; | }); | | You can even respond to more than one URI: | -| Router::register('GET /hello, GET /world', function() +| Route::post('hello, world', function() | { | return 'Hello World!'; | }); | | It's easy to allow URI wildcards using (:num) or (:any): | -| Router::register('GET /hello/(:any)', function($name) +| Route::put('hello/(:any)', function($name) | { | return "Welcome, $name."; | }); | */ -Router::register(array('GET /', 'GET /home'), function() +Route::get('/, home', function() { return View::make('home.index'); }); +/* +|-------------------------------------------------------------------------- +| Application 404 & 500 Error Handlers +|-------------------------------------------------------------------------- +| +| To centralize and simplify 404 handling, Laravel uses an awesome event +| system to retrieve the response. Feel free to modify this function to +| your tastes and the needs of your application. +| +| Similarly, we use an event to handle the display of 500 level errors +| within the application. These errors are fired when there is an +| uncaught exception thrown in the application. +| +*/ + +Event::listen('404', function() +{ + return Response::error('404'); +}); + +Event::listen('500', function() +{ + return Response::error('500'); +}); + /* |-------------------------------------------------------------------------- | Route Filters diff --git a/application/start.php b/application/start.php index 368531bc..d64bbd98 100644 --- a/application/start.php +++ b/application/start.php @@ -1,5 +1,24 @@ path('app').'controllers/base.php', +)); + /* |-------------------------------------------------------------------------- | Auto-Loader PSR-0 Directories @@ -18,24 +37,4 @@ Autoloader::psr(array( path('app').'models', path('app').'libraries', -)); - -/* -|-------------------------------------------------------------------------- -| Auto-Loader Mappings -|-------------------------------------------------------------------------- -| -| Laravel uses a simple array of class to path mappings to drive the class -| auto-loader. This simple approach helps avoid the performance problems -| of searching through directories by some kind of convention. It also -| gives you the freedom to organize your application how you want. -| -| Registering a mapping couldn't be easier. Just pass an array of class -| to path maps into the "map" function of Autoloader. Then, when you -| want to use that class, just use it. It's a piece of cake. -| -*/ - -Autoloader::map(array( - 'Base_Controller' => path('app').'controllers/base.php', )); \ No newline at end of file diff --git a/laravel/autoloader.php b/laravel/autoloader.php index f42e87f8..9b33d788 100644 --- a/laravel/autoloader.php +++ b/laravel/autoloader.php @@ -104,7 +104,7 @@ protected static function load_psr($class, $directory = null) { // The PSR-0 standard indicates that class namespaces and underscores // shoould be used to indcate the directory tree in which the class - // resides, so we'll convert them to directory slashes. + // resides, so we'll convert them to slashes. $file = str_replace(array('\\', '_'), '/', $class); $directories = $directory ?: static::$psr; diff --git a/laravel/bundle.php b/laravel/bundle.php index 14f1364b..6e683b43 100644 --- a/laravel/bundle.php +++ b/laravel/bundle.php @@ -33,103 +33,35 @@ class Bundle { public static $routed = array(); /** - * The cache key for the bundle manifest. + * Register the bundle for the application. * - * @var string - */ - const manifest = 'laravel.bundle.manifest'; - - /** - * Detect all of the installed bundles from disk. - * - * @param string $path - * @return array - */ - public static function detect($path) - { - return static::search($path); - } - - /** - * Detect all of the installed bundles from disk. - * - * @param string $path - * @return array - */ - protected static function search($path) - { - $bundles = array(); - - $items = new fIterator($path); - - foreach ($items as $item) - { - // If the item is a directory, we'll search for a bundle.info file. - // If one exists, we will add it to the bundle array. We will set - // the location automatically since we know it. - if ($item->isDir()) - { - $path = $item->getRealPath().DS.'bundle.php'; - - // If we found a file, we'll require in the array it contains - // and add it to the directory. The info array will contain - // basic info like the bundle name and any URIs it may - // handle incoming requests for. - if (file_exists($path)) - { - $info = require $path; - - $info['location'] = dirname($path).DS; - - $bundles[$info['name']] = $info; - - continue; - } - // If a bundle.info file doesn't exist within a directory, - // we'll recurse into the directory to keep searching in - // the bundle directory for nested bundles. - else - { - $recurse = static::detect($item->getRealPath()); - - $bundles = array_merge($bundles, $recurse); - } - } - } - - return $bundles; - } - - /** - * Register a bundle for the application. - * - * @param array $config + * @param string $bundle + * @param array $config * @return void */ - public static function register($config) + public static function register($bundle, $config = array()) { $defaults = array('handles' => null, 'auto' => false); - // If a handles clause has been specified, we will cap it with a trailing - // slash so the bundle is not extra greedy with its routes. Otherwise a - // bundle that handles "s" would handle all routes beginning with "s". - if (isset($config['handles'])) + // If the given configuration is actually a string, we will assume it is a + // location and set the bundle name to match it. This is common for most + // bundles who simply live in the root bundle directory. + if (is_string($config)) { - $config['handles'] = str_finish($config['handles'], '/'); + $bundle = $config; + + $config = array('location' => $bundle); } - static::$bundles[$config['name']] = array_merge($defaults, $config); - } + // IF no location is set, we will set the location to match the name of + // the bundle. This is for bundles who are installed to the root of + // the bundle directory so a location was not set. + if ( ! isset($config['location'])) + { + $config['location'] = $bundle; + } - /** - * Disable a bundle for the current request. - * - * @param string $bundle - * @return void - */ - public static function disable($bundle) - { - unset(static::$bundles[$bundle]); + static::$bundles[$bundle] = array_merge($defaults, $config); } /** @@ -151,8 +83,7 @@ public static function start($bundle) // Each bundle may have a "start" script which is responsible for preparing // the bundle for use by the application. The start script may register any - // classes the bundle uses with the auto-loader, or perhaps will start any - // dependent bundles so that they are available. + // classes the bundle uses with the auto-loader, etc. if (file_exists($path = static::path($bundle).'start'.EXT)) { require $path; @@ -178,6 +109,11 @@ public static function routes($bundle) { $path = static::path($bundle).'routes'.EXT; + // By setting the bundle property on the router the router knows what + // value to replace the (:bundle) place-holder with when the bundle + // routes are added, keeping the routes flexible. + Routing\Router::$bundle = static::option($bundle, 'handles'); + if ( ! static::routed($bundle) and file_exists($path)) { require $path; @@ -186,6 +122,17 @@ public static function routes($bundle) static::$routed[] = $bundle; } + /** + * Disable a bundle for the current request. + * + * @param string $bundle + * @return void + */ + public static function disable($bundle) + { + unset(static::$bundles[$bundle]); + } + /** * Determine which bundle handles the given URI. * @@ -200,7 +147,10 @@ public static function handles($uri) foreach (static::$bundles as $key => $value) { - if (starts_with($uri, $value['handles'])) return $key; + if (isset($value['handles']) and starts_with($uri, $value['handles'].'/')) + { + return $key; + } } return DEFAULT_BUNDLE; @@ -217,6 +167,19 @@ public static function exists($bundle) return $bundle == DEFAULT_BUNDLE or in_array(strtolower($bundle), static::names()); } + /** + * Get the full path location of a given bundle. + * + * @param string $bundle + * @return string + */ + public static function location($bundle) + { + $location = array_get(static::$bundles, $bundle.'.location'); + + return path('bundle').str_finish($location, DS); + } + /** * Determine if a given bundle has been started for the request. * @@ -277,7 +240,7 @@ public static function class_prefix($bundle) */ public static function path($bundle) { - return ($bundle == DEFAULT_BUNDLE) ? path('app') : static::$bundles[$bundle]['location']; + return ($bundle == DEFAULT_BUNDLE) ? path('app') : static::location($bundle); } /** @@ -401,7 +364,7 @@ public static function parse($identifier) * Get the information for a given bundle. * * @param string $bundle - * @return array + * @return object */ public static function get($bundle) { diff --git a/laravel/cache/drivers/apc.php b/laravel/cache/drivers/apc.php index b2a48d25..2852aee8 100644 --- a/laravel/cache/drivers/apc.php +++ b/laravel/cache/drivers/apc.php @@ -39,7 +39,7 @@ public function has($key) */ protected function retrieve($key) { - if ( ! is_null($cache = apc_fetch($this->key.$key))) + if (($cache = apc_fetch($this->key.$key)) !== false) { return $cache; } diff --git a/laravel/cli/artisan.php b/laravel/cli/artisan.php index ea6d8dd5..a838f8e0 100644 --- a/laravel/cli/artisan.php +++ b/laravel/cli/artisan.php @@ -10,26 +10,6 @@ */ Bundle::start(DEFAULT_BUNDLE); -/** - * Set the CLI options on the $_SERVER global array so we can easily - * retrieve them from the various parts of the CLI code. We can use - * the Request class to access them conveniently. - */ -list($arguments, $_SERVER['CLI']) = Command::options($_SERVER['argv']); - -$_SERVER['CLI'] = array_change_key_case($_SERVER['CLI'], CASE_UPPER); - -/** - * The Laravel environment may be specified on the CLI using the "env" - * option, allowing the developer to easily use local configuration - * files from the CLI since the environment is usually controlled - * by server environmenet variables. - */ -if (isset($_SERVER['CLI']['ENV'])) -{ - $_SERVER['LARAVEL_ENV'] = $_SERVER['CLI']['ENV']; -} - /** * The default database connection may be set by specifying a value * for the "database" CLI option. This allows migrations to be run diff --git a/laravel/cli/tasks/bundle/bundler.php b/laravel/cli/tasks/bundle/bundler.php index 78913ce2..9743671b 100644 --- a/laravel/cli/tasks/bundle/bundler.php +++ b/laravel/cli/tasks/bundle/bundler.php @@ -55,10 +55,8 @@ public function install($bundles) $this->download($bundle, $path); - echo "Bundle [{$bundle['name']}] has been installed!".PHP_EOL; + echo "Bundle [{$bundle['name']}] installed!".PHP_EOL; } - - $this->refresh(); } /** @@ -83,7 +81,7 @@ public function upgrade($bundles) // First we want to retrieve the information for the bundle, such as // where it is currently installed. This will allow us to upgrade // the bundle into it's current installation path. - $bundle = Bundle::get($name); + $location = Bundle::location($name); // If the bundle exists, we will grab the data about the bundle from // the API so we can make the right bundle provider for the bundle, @@ -98,89 +96,12 @@ public function upgrade($bundles) // Once we have the bundle information from the API, we'll simply // recursively delete the bundle and then re-download it using // the correct provider assigned to the bundle. - File::rmdir($bundle['location']); + File::rmdir($location); - $this->download($response['bundle'], $bundle['location']); + $this->download($response['bundle'], $location); echo "Bundle [{$name}] has been upgraded!".PHP_EOL; } - - $this->refresh(); - } - - /** - * Publish bundle assets to the public directory. - * - * @param array $bundles - * @return void - */ - public function publish($bundles) - { - if (count($bundles) == 0) $bundles = Bundle::names(); - - array_walk($bundles, array(IoC::resolve('bundle.publisher'), 'publish')); - } - - /** - * Create a new bundle stub. - * - * @param array $arguments - * @return void - */ - public function make($arguments) - { - if ( ! isset($arguments[0])) - { - throw new \Exception("We need to know the bundle name!"); - } - - // First we'll grab the name from the argument list and make sure a bundle - // with that name doesn't already exist. If it does, we'll bomb out and - // notify the developer of the problem. Bundle names must be unique - // since classes are prefixed with the name. - $options['name'] = $name = $arguments[0]; - - if (Bundle::exists($name)) - { - throw new \Exception("That bundle already exists!"); - } - - // The developer may specify a location to which the bundle should be - // installed. If a location is not specified, the bundle name will - // be used as the default installation location. - $location = Request::server('cli.location') ?: $name; - - $location = path('bundle').$location; - - $options['handles'] = Request::server('cli.handles'); - - // We'll create the actual PHP that should be inserted into the info - // file for the bundle. This contains the bundle's name as well as - // any URIs it is setup to handle. - $info = 'refresh(); - } - - /** - * Clear the bundle manifest cache. - * - * @return void - */ - public function refresh() - { - Cache::forget(Bundle::manifest); - - echo 'Bundle cache cleared!'.PHP_EOL; } /** @@ -197,7 +118,7 @@ protected function get($bundles) { // First we'll call the bundle repository to gather the bundle data // array, which contains all of the information needed to install - // the bundle into the application. + // the bundle into the Laravel application. $response = $this->retrieve($bundle); if ($response['status'] == 'not-found') @@ -207,12 +128,14 @@ protected function get($bundles) // If the bundle was retrieved successfully, we will add it to // our array of bundles, as well as merge all of the bundle's - // dependencies into the array of responses so that they are - // installed along with the consuming dependency. + // dependencies into the array of responses. $bundle = $response['bundle']; $responses[] = $bundle; + // We'll also get the bundle's declared dependenceis so they + // can be installed along with the bundle, making it easy + // to install a group of bundles. $dependencies = $this->get($bundle['dependencies']); $responses = array_merge($responses, $dependencies); @@ -221,6 +144,19 @@ protected function get($bundles) return $responses; } + /** + * Publish bundle assets to the public directory. + * + * @param array $bundles + * @return void + */ + public function publish($bundles) + { + if (count($bundles) == 0) $bundles = Bundle::names(); + + array_walk($bundles, array(IoC::resolve('bundle.publisher'), 'publish')); + } + /** * Install a bundle using a provider. * diff --git a/laravel/cli/tasks/migrate/migrator.php b/laravel/cli/tasks/migrate/migrator.php index 42f949ad..b7727f52 100644 --- a/laravel/cli/tasks/migrate/migrator.php +++ b/laravel/cli/tasks/migrate/migrator.php @@ -115,7 +115,7 @@ public function rollback($arguments = array()) // along with their bundles and names. We will iterate through each // migration and run the "down" method, removing them from the // database as we go. - foreach ($migrations as $migration) + foreach (array_reverse($migrations) as $migration) { $migration['migration']->down(); diff --git a/laravel/core.php b/laravel/core.php index 62484791..2c37c458 100644 --- a/laravel/core.php +++ b/laravel/core.php @@ -30,6 +30,40 @@ */ spl_autoload_register(array('Laravel\\Autoloader', 'load')); +/** + * Register the Laravel namespace so that the auto-loader loads it + * according to the PSR-0 naming conventions. This should provide + * fast resolution of all core classes. + */ +Autoloader::namespaces(array('Laravel' => path('sys'))); + +/** + * Set the CLI options on the $_SERVER global array so we can easily + * retrieve them from the various parts of the CLI code. We can use + * the Request class to access them conveniently. + */ +if (defined('STDIN')) +{ + $console = CLI\Command::options($_SERVER['argv']); + + list($arguments, $options) = $console; + + $options = array_change_key_case($options, CASE_UPPER); + + $_SERVER['CLI'] = $options; +} + +/** + * The Laravel environment may be specified on the CLI using the env + * option, allowing the developer to easily use local configuration + * files from the CLI since the environment is usually controlled + * by server environmenet variables. + */ +if (isset($_SERVER['CLI']['ENV'])) +{ + $_SERVER['LARAVEL_ENV'] = $_SERVER['CLI']['ENV']; +} + /** * Register all of the core class aliases. These aliases provide a * convenient way of working with the Laravel core classes without @@ -38,35 +72,6 @@ */ Autoloader::$aliases = Config::get('application.aliases'); -/** - * Register the Laravel namespace so that the auto-loader loads it - * according to the PSR-0 naming conventions. This should provide - * fast resolution of all core classes. - */ -Autoloader::namespaces(array('Laravel' => path('sys'))); - -/** - * Grab the bundle manifest for the application. This contains an - * array of all of the installed bundles, plus information about - * each of them. If it's not cached, we'll detect them and then - * cache it to save time later. - */ -$bundles = Cache::remember(Bundle::manifest, function() -{ - return Bundle::detect(path('bundle')); - -}, Config::get('application.bundle.cache')); - -/** - * Register all of the bundles that are defined in the main bundle - * manifest. This informs the framework where the bundle lives - * and which URIs it can respnod to. - */ -foreach ($bundles as $bundle) -{ - Bundle::register($bundle); -} - /** * Register the default timezone for the application. This will * be the default timezone used by all date functions through @@ -74,4 +79,16 @@ */ $timezone = Config::get('application.timezone'); -date_default_timezone_set($timezone); \ No newline at end of file +date_default_timezone_set($timezone); + +/** + * Finally we'll grab all of the bundles and register them + * with the bundle class. All of the bundles are stored in + * an array within the application directory. + */ +$bundles = require path('app').'bundles'.EXT; + +foreach ($bundles as $bundle => $config) +{ + Bundle::register($bundle, $config); +} \ No newline at end of file diff --git a/laravel/database/connectors/mysql.php b/laravel/database/connectors/mysql.php index 16cbc2a7..78d35917 100644 --- a/laravel/database/connectors/mysql.php +++ b/laravel/database/connectors/mysql.php @@ -20,11 +20,11 @@ public function connect($config) // Check for any optional MySQL PDO options. These options are not required // to establish a PDO connection; however, may be needed in certain server // or hosting environments used by the developer. - foreach (array('port', 'unix_socket') as $key => $value) + foreach (array('port', 'unix_socket') as $key) { if (isset($config[$key])) { - $dsn .= ";{$key}={$value}"; + $dsn .= ";{$key}={$config[$key]}"; } } diff --git a/laravel/database/schema/grammars/mysql.php b/laravel/database/schema/grammars/mysql.php index 2927237e..375183dd 100644 --- a/laravel/database/schema/grammars/mysql.php +++ b/laravel/database/schema/grammars/mysql.php @@ -23,14 +23,14 @@ public function create(Table $table, Fluent $command) { $columns = implode(', ', $this->columns($table)); - // First we will generate the base table creation statement. Other than - // auto-incrementing keys, no indexes will be created during the first - // creation of the table. They will be added in separate commands. + // First we will generate the base table creation statement. Other than incrementing + // keys, no indexes will be created during the first creation of the table since + // they will be added in separate commands. $sql = 'CREATE TABLE '.$this->wrap($table).' ('.$columns.')'; - // MySQL supports various "engines" for database tables. If an engine - // was specified by the developer, we will set it after adding the - // columns the table creation statement. + // MySQL supports various "engines" for database tables. If an engine ws specified + // by the developer, we will set it after adding the columns the table creation + // statement. Some engines support extra indexes. if ( ! is_null($table->engine)) { $sql .= ' ENGINE = '.$table->engine; @@ -50,9 +50,9 @@ public function add(Table $table, Fluent $command) { $columns = $this->columns($table); - // Once we the array of column definitions, we need to add "add" - // to the front of each definition, then we'll concatenate the - // definitions using commas like normal and generate the SQL. + // Once we the array of column definitions, we need to add "add" to the front + // of each definition, then we'll concatenate the definitions using commas + // like normal and generate the SQL. $columns = implode(', ', array_map(function($column) { return 'ADD '.$column; @@ -77,7 +77,7 @@ protected function columns(Table $table) // Each of the data type's have their own definition creation method, // which is responsible for creating the SQL for the type. This lets // us to keep the syntax easy and fluent, while translating the - // types to the types used by the database. + // types to the correct types. $sql = $this->wrap($column).' '.$this->type($column); $elements = array('nullable', 'defaults', 'incrementer'); @@ -223,9 +223,9 @@ public function drop_column(Table $table, Fluent $command) { $columns = array_map(array($this, 'wrap'), $command->columns); - // Once we the array of column names, we need to add "drop" to the - // front of each column, then we'll concatenate the columns using - // commas and generate the alter statement SQL. + // Once we the array of column names, we need to add "drop" to the front + // of each column, then we'll concatenate the columns using commas and + // generate the alter statement SQL. $columns = implode(', ', array_map(function($column) { return 'DROP '.$column; diff --git a/laravel/database/schema/grammars/postgres.php b/laravel/database/schema/grammars/postgres.php index 1fb6d21a..ac34b282 100644 --- a/laravel/database/schema/grammars/postgres.php +++ b/laravel/database/schema/grammars/postgres.php @@ -16,9 +16,9 @@ public function create(Table $table, Fluent $command) { $columns = implode(', ', $this->columns($table)); - // First we will generate the base table creation statement. Other than - // auto-incrementing keys, no indexes will be created during the first - // creation of the table. They will be added in separate commands. + // First we will generate the base table creation statement. Other than incrementing + // keys, no indexes will be created during the first creation of the table since + // they will be added in separate commands. $sql = 'CREATE TABLE '.$this->wrap($table).' ('.$columns.')'; return $sql; @@ -35,9 +35,9 @@ public function add(Table $table, Fluent $command) { $columns = $this->columns($table); - // Once we the array of column definitions, we'll add "add column" - // to the front of each definition, then we'll concatenate the - // definitions using commas like normal and generate the SQL. + // Once we the array of column definitions, we need to add "add" to the front + // of each definition, then we'll concatenate the definitions using commas + // like normal and generate the SQL. $columns = implode(', ', array_map(function($column) { return 'ADD COLUMN '.$column; @@ -114,10 +114,9 @@ protected function defaults(Table $table, Fluent $column) */ protected function incrementer(Table $table, Fluent $column) { - // We don't actually need to specify an "auto_increment" keyword since - // we handle the auto-increment definition in the type definition for - // integers by changing the type to "serial", which is a convenient - // notational short-cut provided by Postgres. + // We don't actually need to specify an "auto_increment" keyword since we + // handle the auto-increment definition in the type definition for + // integers by changing the type to "serial". if ($column->type == 'integer' and $column->increment) { return ' PRIMARY KEY'; @@ -218,9 +217,9 @@ public function drop_column(Table $table, Fluent $command) { $columns = array_map(array($this, 'wrap'), $command->columns); - // Once we the array of column names, we need to add "drop" to the - // front of each column, then we'll concatenate the columns using - // commas and generate the alter statement SQL. + // Once we the array of column names, we need to add "drop" to the front + // of each column, then we'll concatenate the columns using commas and + // generate the alter statement SQL. $columns = implode(', ', array_map(function($column) { return 'DROP COLUMN '.$column; diff --git a/laravel/database/schema/grammars/sqlite.php b/laravel/database/schema/grammars/sqlite.php index b86b9fb2..f704cc1c 100644 --- a/laravel/database/schema/grammars/sqlite.php +++ b/laravel/database/schema/grammars/sqlite.php @@ -16,26 +16,22 @@ public function create(Table $table, Fluent $command) { $columns = implode(', ', $this->columns($table)); - // First we will generate the base table creation statement. Other than - // auto-incrementing keys, no indexes will be created during the first - // creation of the table. They will be added in separate commands. + // First we will generate the base table creation statement. Other than incrementing + // keys, no indexes will be created during the first creation of the table since + // they will be added in separate commands. $sql = 'CREATE TABLE '.$this->wrap($table).' ('.$columns; - // SQLite does not allow adding a primary key as a command apart from - // when the table is initially created, so we'll need to sniff out - // any primary keys here and add them to the table. - // - // Because of this, this class does not have the typical "primary" - // method as it would be pointless since the primary keys can't - // be set on anything but the table creation statement. + // SQLite does not allow adding a primary key as a command apart from the creation + // of the table, so we'll need to sniff out any primary keys here and add them to + // the table now during this command. $primary = array_first($table->commands, function($key, $value) { return $value->type == 'primary'; }); - // If we found primary key in the array of commands, we'll create - // the SQL for the key addition and append it to the SQL table - // creation statement for the schema table. + // If we found primary key in the array of commands, we'll create the SQL for + // the key addition and append it to the SQL table creation statement for + // the schema table so the index is properly generated. if ( ! is_null($primary)) { $columns = $this->columnize($primary->columns); @@ -57,18 +53,18 @@ public function add(Table $table, Fluent $command) { $columns = $this->columns($table); - // Once we have an array of all of the column definitions, we need to - // spin through each one and prepend "ADD COLUMN" to each of them, - // which is the syntax used by SQLite when adding columns. + // Once we the array of column definitions, we need to add "add" to the front + // of each definition, then we'll concatenate the definitions using commas + // like normal and generate the SQL. $columns = array_map(function($column) { return 'ADD COLUMN '.$column; }, $columns); - // SQLite only allows one column to be added in an ALTER statement, - // so we will create an array of statements and return them all to - // the schema manager, which will execute each one. + // SQLite only allows one column to be added in an ALTER statement, so we + // will create an array of statements and return them all to the schema + // manager, which will execute each one separately. foreach ($columns as $column) { $sql[] = 'ALTER TABLE '.$this->wrap($table).' '.$column; diff --git a/laravel/database/schema/grammars/sqlserver.php b/laravel/database/schema/grammars/sqlserver.php index b4b5342c..7b20fdd5 100644 --- a/laravel/database/schema/grammars/sqlserver.php +++ b/laravel/database/schema/grammars/sqlserver.php @@ -23,9 +23,9 @@ public function create(Table $table, Fluent $command) { $columns = implode(', ', $this->columns($table)); - // First we will generate the base table creation statement. Other than - // auto-incrementing keys, no indexes will be created during the first - // creation of the table. They will be added in separate commands. + // First we will generate the base table creation statement. Other than incrementing + // keys, no indexes will be created during the first creation of the table since + // they will be added in separate commands. $sql = 'CREATE TABLE '.$this->wrap($table).' ('.$columns.')'; return $sql; @@ -42,9 +42,9 @@ public function add(Table $table, Fluent $command) { $columns = $this->columns($table); - // Once we the array of column definitions, we need to add "add" - // to the front of each definition, then we'll concatenate the - // definitions using commas like normal and generate the SQL. + // Once we the array of column definitions, we need to add "add" to the front + // of each definition, then we'll concatenate the definitions using commas + // like normal and generate the SQL. $columns = implode(', ', array_map(function($column) { return 'ADD '.$column; @@ -166,18 +166,18 @@ public function fulltext(Table $table, Fluent $command) { $columns = $this->columnize($command->columns); - // SQL Server requires the creation of a full-text "catalog" before - // creating a full-text index, so we'll first create the catalog - // then add another statement for the index. The catalog will - // be updated automatically by the server. + $table = $this->wrap($table); + + // SQL Server requires the creation of a full-text "catalog" before creating + // a full-text index, so we'll first create the catalog then add another + // separate statement for the index. $sql[] = "CREATE FULLTEXT CATALOG {$command->catalog}"; - $create = "CREATE FULLTEXT INDEX ON ".$this->wrap($table)." ({$columns}) "; + $create = "CREATE FULLTEXT INDEX ON ".$table." ({$columns}) "; - // Full-text indexes must specify a unique, non-nullable column as - // the index "key" and this should have been created manually by - // the developer in a separate column addition command, so we - // can just specify it in this statement. + // Full-text indexes must specify a unique, non-null column as the index + // "key" and this should have been created manually by the developer in + // a separate column addition command. $sql[] = $create .= "KEY INDEX {$command->key} ON {$command->catalog}"; return $sql; @@ -235,9 +235,9 @@ public function drop_column(Table $table, Fluent $command) { $columns = array_map(array($this, 'wrap'), $command->columns); - // Once we the array of column names, we need to add "drop" to the - // front of each column, then we'll concatenate the columns using - // commas and generate the alter statement SQL. + // Once we the array of column names, we need to add "drop" to the front + // of each column, then we'll concatenate the columns using commas and + // generate the alter statement SQL. $columns = implode(', ', array_map(function($column) { return 'DROP '.$column; diff --git a/laravel/error.php b/laravel/error.php index 15bebd98..edcb7ad3 100644 --- a/laravel/error.php +++ b/laravel/error.php @@ -25,9 +25,15 @@ public static function exception($exception)

Stack Trace:

".$exception->getTraceAsString()."
"; } + + // If we're not using detailed error messages, we'll use the event + // system to get the response that should be sent to the browser. + // Using events gives the developer more freedom. else { - Response::error('500')->send(); + $response = Event::first('500'); + + return Response::prepare($response)->send(); } exit(1); @@ -48,8 +54,7 @@ public static function native($code, $error, $file, $line) // For a PHP error, we'll create an ErrorExcepetion and then feed that // exception to the exception method, which will create a simple view - // of the exception details. The ErrorException class is built-in to - // PHP for converting native errors. + // of the exception details for the developer. $exception = new \ErrorException($error, $code, 0, $file, $line); if (in_array($code, Config::get('error.ignore'))) @@ -71,8 +76,10 @@ public static function shutdown() { // If a fatal error occured that we have not handled yet, we will // create an ErrorException and feed it to the exception handler, - // as it will not have been handled by the error handler. - if ( ! is_null($error = error_get_last())) + // as it will not yet have been handled. + $error = error_get_last(); + + if ( ! is_null($error)) { extract($error, EXTR_SKIP); diff --git a/laravel/event.php b/laravel/event.php index 10190e8b..4ca96a77 100644 --- a/laravel/event.php +++ b/laravel/event.php @@ -40,6 +40,26 @@ public static function listen($event, $callback) static::$events[$event][] = $callback; } + /** + * Fire an event and return the first response. + * + * + * // Fire the "start" event + * $response = Event::first('start'); + * + * // Fire the "start" event passing an array of parameters + * $response = Event::first('start', array('Laravel', 'Framework')); + * + * + * @param string $event + * @param array $parameters + * @return mixed + */ + public static function first($event, $parameters = array()) + { + return head(static::fire($event, $parameters)); + } + /** * Fire an event so that all listeners are called. * diff --git a/laravel/helpers.php b/laravel/helpers.php index 6af310da..0e946e5e 100644 --- a/laravel/helpers.php +++ b/laravel/helpers.php @@ -90,14 +90,14 @@ function array_set(&$array, $key, $value) // This loop allows us to dig down into the array to a dynamic depth by // setting the array value for each level that we dig into. Once there // is one key left, we can fall out of the loop and set the value as - // we should be at the proper depth within the array. + // we should be at the proper depth. while (count($keys) > 1) { $key = array_shift($keys); // If the key doesn't exist at this depth, we will just create an // empty array to hold the next value, allowing us to create the - // arrays to hold the final value at the proper depth. + // arrays to hold the final value. if ( ! isset($array[$key]) or ! is_array($array[$key])) { $array[$key] = array(); @@ -131,7 +131,7 @@ function array_forget(&$array, $key) // This loop functions very similarly to the loop in the "set" method. // We will iterate over the keys, setting the array value to the new // depth at each iteration. Once there is only one key left, we will - // be at the proper depth in the array to "forget" the value. + // be at the proper depth in the array. while (count($keys) > 1) { $key = array_shift($keys); @@ -139,7 +139,7 @@ function array_forget(&$array, $key) // Since this method is supposed to remove a value from the array, // if a value higher up in the chain doesn't exist, there is no // need to keep digging into the array, since it is impossible - // for the final value to even exist in the array. + // for the final value to even exist. if ( ! isset($array[$key]) or ! is_array($array[$key])) { return; @@ -339,6 +339,18 @@ function starts_with($haystack, $needle) return strpos($haystack, $needle) === 0; } +/** + * Determine if a given string ends with a given value. + * + * @param string $haystack + * @param string $needle + * @return bool + */ +function ends_with($haystack, $needle) +{ + return $needle == substr($haystack, strlen($haystack) - strlen($needle)); +} + /** * Determine if a given string contains a given sub-string. * diff --git a/laravel/laravel.php b/laravel/laravel.php index dcfddd24..fa4b82cd 100644 --- a/laravel/laravel.php +++ b/laravel/laravel.php @@ -132,15 +132,25 @@ Bundle::start(DEFAULT_BUNDLE); /** - * Start all of the bundles that are specified in the configuration - * array of auto-loaded bundles. This lets the developer have an - * easy way to load bundles for every request. + * Auto-start any bundles configured to start on every request. + * This is especially useful for debug bundles or bundles that + * are used throughout the application. */ -foreach (Config::get('application.bundle.auto') as $bundle) +foreach (Bundle::$bundles as $bundle => $config) { - Bundle::start($bundle); + if ($config['auto']) Bundle::start($bundle); } +/** + * Register the "catch-all" route that handles 404 responses for + * routes that can not be matched to any other route within the + * application. We'll just raise the 404 event. + */ +Routing\Router::register('*', '(:all)', function() +{ + return Event::first('404'); +}); + /** * If the requset URI has too many segments, we will bomb out of * the request. This is too avoid potential DDoS attacks against @@ -162,16 +172,6 @@ */ Request::$route = Routing\Router::route(Request::method(), $uri); -if (is_null(Request::$route)) -{ - Request::$route = new Routing\Route('GET /404', array(function() - { - return Response::error('404'); - })); - - $response = Response::error('404'); -} - $response = Request::$route->call(); /** diff --git a/laravel/redirect.php b/laravel/redirect.php index 7dd606eb..fded4014 100644 --- a/laravel/redirect.php +++ b/laravel/redirect.php @@ -35,6 +35,11 @@ public static function to_secure($url, $status = 302) return static::to($url, $status, true); } + public static function to_action($action, $parameters = array()) + { + + } + /** * Create a redirect response to a named route. * @@ -49,25 +54,11 @@ public static function to_secure($url, $status = 302) * @param string $route * @param array $parameters * @param int $status - * @param bool $https * @return Redirect */ - public static function to_route($route, $parameters = array(), $status = 302, $https = false) + public static function to_route($route, $parameters = array(), $status = 302) { - return static::to(URL::to_route($route, $parameters, $https), $status); - } - - /** - * Create a redirect response to a named route using HTTPS. - * - * @param string $route - * @param array $parameters - * @param int $status - * @return Redirect - */ - public static function to_secure_route($route, $parameters = array(), $status = 302) - { - return static::to_route($route, $parameters, $status, true); + return static::to(URL::to_route($route, $parameters), $status); } /** diff --git a/laravel/request.php b/laravel/request.php index 69d20a23..fc6b1b51 100644 --- a/laravel/request.php +++ b/laravel/request.php @@ -1,13 +1,11 @@ - * @@ -48,47 +56,52 @@ abstract class Controller { */ public static function call($destination, $parameters = array()) { + static::references($destination, $parameters); + list($bundle, $destination) = Bundle::parse($destination); // We will always start the bundle, just in case the developer is pointing // a route to another bundle. This allows us to lazy load the bundle and - // improve performance since the bundle is not loaded on every request. + // improve speed since the bundle is not loaded on every request. Bundle::start($bundle); list($controller, $method) = explode('@', $destination); - list($method, $parameters) = static::backreference($method, $parameters); - $controller = static::resolve($bundle, $controller); // If the controller could not be resolved, we're out of options and // will return the 404 error response. If we found the controller, // we can execute the requested method on the instance. - if (is_null($controller)) return Response::error('404'); + if (is_null($controller)) + { + return Event::first('404'); + } return $controller->execute($method, $parameters); } /** - * Replace all back-references on the given method. + * Replace all back-references on the given destination. * - * @param string $method + * @param string $destination * @param array $parameters * @return array */ - protected static function backreference($method, $parameters) + protected static function references(&$destination, &$parameters) { // Controller delegates may use back-references to the action parameters, // which allows the developer to setup more flexible routes to various - // controllers with much less code than usual. + // controllers with much less code than would be usual. foreach ($parameters as $key => $value) { - $method = str_replace('(:'.($key + 1).')', $value, $method, $count); + $search = '(:'.($key + 1).')'; + + $destination = str_replace($search, $value, $destination, $count); if ($count > 0) unset($parameters[$key]); } - return array(str_replace('(:1)', 'index', $method), $parameters); + return array($destination, $parameters); } /** @@ -100,18 +113,23 @@ protected static function backreference($method, $parameters) */ public static function resolve($bundle, $controller) { - if ( ! static::load($bundle, $controller)) return; + $identifier = Bundle::identifier($bundle, $controller); // If the controller is registered in the IoC container, we will resolve // it out of the container. Using constructor injection on controllers - // via the container allows more flexible and testable applications. - $resolver = 'controller: '.Bundle::identifier($bundle, $controller); + // via the container allows more flexible applications. + $resolver = 'controller: '.$identifier; if (IoC::registered($resolver)) { return IoC::resolve($resolver); } + // If we couldn't resolve the controller out of the IoC container we'll + // format the controller name into its proper class name and load it + // by convention out of the bundle's controller directory. + if ( ! static::load($bundle, $controller)) return; + $controller = static::format($bundle, $controller); $controller = new $controller; @@ -169,11 +187,12 @@ protected static function format($bundle, $controller) */ public function execute($method, $parameters = array()) { + $filters = $this->filters('before', $method); + // Again, as was the case with route closures, if the controller "before" // filters return a response, it will be considered the response to the - // request and the controller method will not be used to handle the - // request to the application. - $response = Filter::run($this->filters('before', $method), array(), true); + // request and the controller method will not be used . + $response = Filter::run($filters, array(), true); if (is_null($response)) { @@ -186,7 +205,7 @@ public function execute($method, $parameters = array()) // The "after" function on the controller is simply a convenient hook // so the developer can work on the response before it's returned to - // the browser. This is useful for setting partials on the layout. + // the browser. This is useful for templating, etc. $this->after($response); Filter::run($this->filters('after', $method), array($response)); @@ -321,7 +340,7 @@ public function __call($method, $parameters) * Dynamically resolve items from the application IoC container. * * - * // Retrieve an object registered in the container as "mailer" + * // Retrieve an object registered in the container * $mailer = $this->mailer; * * // Equivalent call using the IoC container instance @@ -330,9 +349,10 @@ public function __call($method, $parameters) */ public function __get($key) { - if (IoC::registered($key)) return IoC::resolve($key); - - throw new \Exception("Accessing undefined property [$key] on controller."); + if (IoC::registered($key)) + { + return IoC::resolve($key); + } } } \ No newline at end of file diff --git a/laravel/routing/route.php b/laravel/routing/route.php index 2f77a1a9..8a4f27c0 100644 --- a/laravel/routing/route.php +++ b/laravel/routing/route.php @@ -2,23 +2,24 @@ use Closure; use Laravel\Bundle; +use Laravel\Request; use Laravel\Response; class Route { /** - * The route key, including request method and URI. + * The URI the route response to. * * @var string */ - public $key; + public $uri; /** - * The URI the route responds to. + * The request method the route responds to. * * @var string */ - public $uris; + public $method; /** * The bundle in which the route was registered. @@ -44,65 +45,72 @@ class Route { /** * Create a new Route instance. * - * @param string $key + * @param string $method + * @param string $uri * @param array $action * @param array $parameters * @return void */ - public function __construct($key, $action, $parameters = array()) + public function __construct($method, $uri, $action, $parameters = array()) { - $this->key = $key; + $this->uri = $uri; + $this->method = $method; $this->action = $action; - // Extract each URI from the route key. Since the route key has the request - // method, we will extract that from the string. If the URI points to the - // root of the application, a single forward slash is returned. - $uris = array_get($action, 'handles', array($key)); - - $this->uris = array_map(array($this, 'destination'), $uris); - // Determine the bundle in which the route was registered. We will know // the bundle by using the bundle::handles method, which will return // the bundle assigned to that URI. - $this->bundle = Bundle::handles($this->uris[0]); + $this->bundle = Bundle::handles($uri); - $defaults = array_get($action, 'defaults', array()); - - $this->parameters = array_merge($parameters, $defaults); - - // Once we have set the parameters and URIs, we'll transpose the route - // parameters onto the URIs so that the routes response naturally to - // the handles without the wildcards messing them up. - foreach ($this->uris as &$uri) - { - $uri = $this->transpose($uri, $this->parameters); - } + // We'll set the parameters based on the number of parameters passed + // compared to the parameters that were needed. If more parameters + // are needed, we'll merge in defaults. + $this->parameters($uri, $action, $parameters); } /** - * Substitute the parameters in a given URI. + * Set the parameters array to the correct value. * * @param string $uri + * @param array $action * @param array $parameters - * @return string + * @return void */ - public static function transpose($uri, $parameters) + protected function parameters($uri, $action, $parameters) { - // Spin through each route parameter and replace the route wildcard segment - // with the corresponding parameter passed to the method. Afterwards, we'll - // replace all of the remaining optional URI segments. - foreach ((array) $parameters as $parameter) + $wildcards = 0; + + $defaults = (array) array_get($action, 'defaults'); + + // We need to determine how many of the default paramters should be merged + // into the parameter array. First, we will count the number of wildcards + // in the route URI and then merge the defaults. + foreach (array_keys(Router::patterns()) as $wildcard) { - if ( ! is_null($parameter)) - { - $uri = preg_replace('/\(.+?\)/', $parameter, $uri, 1); - } + $wildcards += substr_count($uri, $wildcard); } - // If there are any remaining optional place-holders, we'll just replace - // them with empty strings since not every optional parameter has to be - // in the array of parameters that were passed. - return str_replace(array_keys(Router::$optional), '', $uri); + $needed = $wildcards - count($parameters); + + // If there are less parameters than wildcards, we will figure out how + // many parameters we need to inject from the array of defaults and + // merge them in into the main array for the route. + if ($needed > 0) + { + $defaults = array_slice($defaults, count($defaults) - $needed); + + $parameters = array_merge($parameters, $defaults); + } + + // If the final number of parameters doesn't match the count of the + // wildcards, we'll pad parameter array with null to cover any of + // the default values that were forgotten. + if (count($parameters) !== $wildcards) + { + $parameters = array_pad($parameters, $wildcards, null); + } + + $this->parameters = $parameters; } /** @@ -141,19 +149,22 @@ public function call() */ public function response() { - // If the action is a string, it is simply pointing the route to a - // controller action, and we can just call the action and return - // its response. This is the most basic form of route, and is - // the simplest to handle. - if ( ! is_null($delegate = $this->delegate())) + // If the action is a string, it is pointing the route to a controller + // action, and we can just call the action and return its response. + // We'll just pass the action off to the Controller class. + $delegate = $this->delegate(); + + if ( ! is_null($delegate)) { return Controller::call($delegate, $this->parameters); } - // If the route does not have a delegate, it should either be a - // Closure instance or have a Closure in its action array, so - // we will attempt to get the Closure and call it. - elseif ( ! is_null($handler = $this->handler())) + // If the route does not have a delegate, then it must be a Closure + // instance or have a Closure in its action array, so we will try + // to locate the Closure and call it directly. + $handler = $this->handler(); + + if ( ! is_null($handler)) { return call_user_func_array($handler, $this->parameters); } @@ -162,21 +173,23 @@ public function response() /** * Get the filters that are attached to the route for a given event. * - * If the route belongs to a bundle, the bundle's global filters are returned too. - * * @param string $event * @return array */ protected function filters($event) { - $filters = array_unique(array($event, Bundle::prefix($this->bundle).$event)); + $global = Bundle::prefix($this->bundle).$event; - // Next wee will check to see if there are any filters attached - // for the given event. If there are, we'll merge them in with - // the global filters for the application event. + $filters = array_unique(array($event, $global)); + + // Next we will check to see if there are any filters attached to + // the route for the given event. If there are, we'll merge them + // in with the global filters for the event. if (isset($this->action[$event])) { - $filters = array_merge($filters, Filter::parse($this->action[$event])); + $assigned = Filter::parse($this->action[$event]); + + $filters = array_merge($filters, $assigned); } return array(new Filter_Collection($filters)); @@ -197,8 +210,6 @@ protected function delegate() /** * Get the anonymous function assigned to handle the route. * - * If no anonymous function is assigned, null will be returned by the method. - * * @return Closure */ protected function handler() @@ -222,72 +233,116 @@ protected function handler() */ public function is($name) { - return is_array($this->action) and array_get($this->action, 'name') === $name; + return array_get($this->action, 'name') === $name; } /** - * Determine if the route handles a given URI. + * Register a controller with the router. * - * @param string $uri - * @return bool + * @param string|array $controller + * @param string|array $defaults + * @return void */ - public function handles($uri) + public static function controller($controllers, $defaults = 'index') { - $pattern = ($uri !== '/') ? str_replace('*', '(.*)', $uri).'\z' : '^/$'; - - return ! is_null(array_first($this->uris, function($key, $uri) use ($pattern) - { - return preg_match('#'.$pattern.'#', $uri); - })); + Router::controller($controllers, $defaults); } /** - * Register a route with the router. + * Register a secure controller with the router. * - * - * // Register a route with the router - * Router::register('GET /', function() {return 'Home!';}); - * - * // Register a route that handles multiple URIs with the router - * Router::register(array('GET /', 'GET /home'), function() {return 'Home!';}); - * + * @param string|array $controllers + * @param string|array $defaults + * @return void + */ + public static function secure_controller($controllers, $defaults = 'index') + { + Router::controller($controllers, $defaults, true); + } + + /** + * Register a GET route with the router. * * @param string|array $route * @param mixed $action - * @param bool $https * @return void */ - public static function to($route, $action) + public static function get($route, $action) { - Router::register($route, $action); + Router::register('GET', $route, $action); + } + + /** + * Register a POST route with the router. + * + * @param string|array $route + * @param mixed $action + * @return void + */ + public static function post($route, $action) + { + Router::register('POST', $route, $action); + } + + /** + * Register a PUT route with the router. + * + * @param string|array $route + * @param mixed $action + * @return void + */ + public static function put($route, $action) + { + Router::register('PUT', $route, $action); + } + + /** + * Register a DELETE route with the router. + * + * @param string|array $route + * @param mixed $action + * @return void + */ + public static function delete($route, $action) + { + Router::register('DELETE', $route, $action); + } + + /** + * Register a route that handles any request method. + * + * @param string|array $route + * @param mixed $action + * @return void + */ + public static function any($route, $action) + { + Router::register('*', $route, $action); + } + + /** + * Register a group of routes that share attributes. + * + * @param array $attributes + * @param Closure $callback + * @return void + */ + public static function group($attributes, Closure $callback) + { + Router::group($attributes, $callback); } /** * Register a HTTPS route with the router. * + * @param string $method * @param string|array $route * @param mixed $action * @return void */ - public static function secure($route, $action) + public static function secure($method, $route, $action) { - Router::secure($route, $action); - } - - /** - * Extract the URI string from a route destination. - * - * - * // Returns "home/index" as the destination's URI - * $uri = Route::uri('GET /home/index'); - * - * - * @param string $destination - * @return string - */ - public static function destination($destination) - { - return trim(substr($destination, strpos($destination, '/')), '/') ?: '/'; + Router::secure($method, $route, $action); } } \ No newline at end of file diff --git a/laravel/routing/router.php b/laravel/routing/router.php index d06a6cf5..8cc4b219 100644 --- a/laravel/routing/router.php +++ b/laravel/routing/router.php @@ -1,7 +1,26 @@ - '([0-9]+)', '(:any)' => '([a-zA-Z0-9\.\-_%]+)', + '(:all)' => '(.*)', ); /** @@ -48,6 +73,7 @@ class Router { public static $optional = array( '/(:num?)' => '(?:/([0-9]+)', '/(:any?)' => '(?:/([a-zA-Z0-9\.\-_%]+)', + '/(:all?)' => '(?:/(.*)', ); /** @@ -60,13 +86,40 @@ class Router { /** * Register a HTTPS route with the router. * + * @param string $method * @param string|array $route * @param mixed $action * @return void */ - public static function secure($route, $action) + public static function secure($method, $route, $action) { - static::register($route, $action, true); + $action = static::action($action); + + $action['https'] = true; + + static::register($method, $route, $action); + } + + /** + * Register a group of routes that share attributes. + * + * @param array $attributes + * @param Closure $callback + * @return void + */ + public static function group($attributes, Closure $callback) + { + // Route groups allow the developer to specify attributes for a group + // of routes. To register them, we'll set a static property on the + // router so that the register method will see them. + static::$group = $attributes; + + call_user_func($callback); + + // Once the routes have been registered, we want to set the group to + // null so the attributes will not be assigned to any of the routes + // that are added after the group is declared. + static::$group = null; } /** @@ -74,18 +127,18 @@ public static function secure($route, $action) * * * // Register a route with the router - * Router::register('GET /', function() {return 'Home!';}); + * Router::register('GET' ,'/', function() {return 'Home!';}); * * // Register a route that handles multiple URIs with the router - * Router::register(array('GET /', 'GET /home'), function() {return 'Home!';}); + * Router::register(array('GET', '/', 'GET /home'), function() {return 'Home!';}); * * + * @param string $method * @param string|array $route * @param mixed $action - * @param bool $https * @return void */ - public static function register($route, $action, $https = false) + public static function register($method, $route, $action) { if (is_string($route)) $route = explode(', ', $route); @@ -94,18 +147,23 @@ public static function register($route, $action, $https = false) // If the URI begins with a splat, we'll call the universal method, which // will register a route for each of the request methods supported by // the router. This is just a notational short-cut. - if (starts_with($uri, '*')) + if ($method == '*') { - static::universal(substr($uri, 2), $action); + foreach (static::$methods as $method) + { + static::register($method, $route, $action); + } continue; } + $uri = str_replace('(:bundle)', static::$bundle, $uri); + // If the URI begins with a wildcard, we want to add this route to the // array of "fallback" routes. Fallback routes are always processed // last when parsing routes since they are very generic and could // overload bundle routes that are registered. - if (str_contains($uri, ' /(')) + if ($uri[0] == '(') { $routes =& static::$fallback; } @@ -114,60 +172,155 @@ public static function register($route, $action, $https = false) $routes =& static::$routes; } - // If the action is a string, it is a pointer to a controller, so we - // need to add it to the action array as a "uses" clause, which will - // indicate to the route to call the controller when the route is - // executed by the application. - if (is_string($action)) + // If the action is an array, we can simply add it to the array of + // routes keyed by the URI. Otherwise, we will need to call into + // the action method to get a valid action array. + if (is_array($action)) { - $routes[$uri]['uses'] = $action; + $routes[$method][$uri] = $action; } - // If the action is not a string, we can just simply cast it as an - // array, then we will add all of the URIs to the action array as - // the "handes" clause so we can easily check which URIs are - // handled by the route instance. else { - if ($action instanceof Closure) $action = array($action); - - $routes[$uri] = (array) $action; + $routes[$method][$uri] = static::action($action); } - - // If the HTTPS option is not set on the action, we will use the - // value given to the method. The "secure" method passes in the - // HTTPS value in as a parameter short-cut, just so the dev - // doesn't always have to add it to an array. - if ( ! isset($routes[$uri]['https'])) + + // If a group is being registered, we'll merge all of the group + // options into the action, giving preference to the action + // for options that are specified in both. + if ( ! is_null(static::$group)) { - $routes[$uri]['https'] = $https; + $routes[$method][$uri] += static::$group; } - $routes[$uri]['handles'] = (array) $route; + // If the HTTPS option is not set on the action, we'll use the + // value given to the method. The secure method passes in the + // HTTPS value in as a parameter short-cut. + if ( ! isset($routes[$method][$uri]['https'])) + { + $routes[$method][$uri]['https'] = false; + } } } /** - * Register a route for all HTTP verbs. + * Convert a route action to a valid action array. * - * @param string $route - * @param mixed $action - * @return void + * @param mixed $action + * @return array */ - protected static function universal($route, $action) + protected static function action($action) { - $count = count(static::$methods); - - $routes = array_fill(0, $count, $route); - - // When registering a universal route, we'll iterate through all of the - // verbs supported by the router and prepend each one of the URIs with - // one of the request verbs, then we'll register the routes. - for ($i = 0; $i < $count; $i++) + // If the action is a string, it is a pointer to a controller, so we + // need to add it to the action array as a "uses" clause, which will + // indicate to the route to call the controller. + if (is_string($action)) { - $routes[$i] = static::$methods[$i].' '.$routes[$i]; + $action = array('uses' => $action); + } + // If the action is a Closure, we will manually put it in an array + // to work around a bug in PHP 5.3.2 which causes Closures cast + // as arrays to become null. We'll remove this. + elseif ($action instanceof Closure) + { + $action = array($action); } - static::register($routes, $action); + return (array) $action; + } + + /** + * Register a secure controller with the router. + * + * @param string|array $controllers + * @param string|array $defaults + * @return void + */ + public static function secure_controller($controllers, $defaults = 'index') + { + static::controller($controllers, $defaults, true); + } + + /** + * Register a controller with the router. + * + * @param string|array $controller + * @param string|array $defaults + * @param bool $https + * @return void + */ + public static function controller($controllers, $defaults = 'index', $https = false) + { + foreach ((array) $controllers as $identifier) + { + list($bundle, $controller) = Bundle::parse($identifier); + + // First we need to replace the dots with slashes in thte controller name + // so that it is in directory format. The dots allow the developer to use + // a cleaner syntax when specifying the controller. We will also grab the + // root URI for the controller's bundle. + $controller = str_replace('.', '/', $controller); + + $root = Bundle::option($bundle, 'handles'); + + // If the controller is a "home" controller, we'll need to also build a + // index method route for the controller. We'll remove "home" from the + // route root and setup a route to point to the index method. + if (ends_with($controller, 'home')) + { + static::root($identifier, $controller, $root); + } + + // The number of method arguments allowed for a controller is set by a + // "segments" constant on this class which allows for the developer to + // increase or decrease the limit on method arguments. + $wildcards = static::repeat('(:any?)', static::$segments); + + // Once we have the path and root URI we can build a simple route for + // the controller that should handle a conventional controller route + // setup of controller/method/segment/segment, etc. + $pattern = trim("{$root}/{$controller}/{$wildcards}", '/'); + + // Finally we can build the "uses" clause and the attributes for the + // controller route and register it with the router with a wildcard + // method so it is available on every request method. + $uses = "{$identifier}@(:1)"; + + $attributes = compact('uses', 'defaults', 'https'); + + static::register('*', $pattern, $attributes); + } + } + + /** + * Register a route for the root of a controller. + * + * @param string $identifier + * @param string $controller + * @param string $root + * @return void + */ + protected static function root($identifier, $controller, $root) + { + // First we need to strip "home" off of the controller name to create the + // URI needed to match the controller's folder, which should match the + // root URI we want to point to the index method. + if ($controller !== 'home') + { + $home = dirname($controller); + } + else + { + $home = ''; + } + + // After we trim the "home" off of the controller name we'll build the + // pattern needed to map to the controller and then register a route + // to point the pattern to the controller's index method. + $pattern = trim($root.'/'.$home, '/') ?: '/'; + + $attributes = array('uses' => "{$identifier}@(:1)", 'defaults' => 'index'); + + static::register('*', $pattern, $attributes); } /** @@ -182,7 +335,7 @@ public static function find($name) // If no route names have been found at all, we will assume no reverse // routing has been done, and we will load the routes file for all of - // the bundle that are installed for the application. + // the bundles that are installed for the application. if (count(static::$names) == 0) { foreach (Bundle::names() as $bundle) @@ -193,10 +346,10 @@ public static function find($name) // To find a named route, we will iterate through every route defined // for the application. We will cache the routes by name so we can - // load them very quickly if we need to find them a second time. - foreach (static::routes() as $key => $value) + // load them very quickly the next time. + foreach (static::all() as $key => $value) { - if (isset($value['name']) and $value['name'] == $name) + if (array_get($value, 'name') === $name) { return static::$names[$name] = array($key => $value); } @@ -204,35 +357,31 @@ public static function find($name) } /** - * Find the route that uses the given action and method. + * Find the route that uses the given action. * * @param string $action - * @param string $method * @return array */ - public static function uses($action, $method = 'GET') + public static function uses($action) { // If the action has already been reverse routed before, we'll just // grab the previously found route to save time. They are cached // in a static array on the class. - if (isset(static::$uses[$method.$action])) + if (isset(static::$uses[$action])) { - return static::$uses[$method.$action]; + return static::$uses[$action]; } Bundle::routes(Bundle::name($action)); - foreach (static::routes() as $uri => $route) + // To find the route, we'll simply spin through the routes looking + // for a route with a "uses" key matching the action, and if we + // find one we cache and return it. + foreach (static::all() as $uri => $route) { - // To find the route, we'll simply spin through the routes looking - // for a route with a "uses" key matching the action, then we'll - // check the request method for a match. - if (isset($route['uses']) and $route['uses'] == $action) + if (array_get($route, 'uses') == $action) { - if (starts_with($uri, $method)) - { - return static::$uses[$method.$action] = array($uri => $route); - } + return static::$uses[$action] = array($uri => $route); } } } @@ -246,164 +395,56 @@ public static function uses($action, $method = 'GET') */ public static function route($method, $uri) { - // First we will make sure the bundle that handles the given URI has - // been started for the current request. Bundles may handle any URI - // as long as it begins with the string in the "handles" item of - // the bundle's registration array. Bundle::start($bundle = Bundle::handles($uri)); - // All route URIs begin with the request method and have a leading - // slash before the URI. We'll put the request method and URI in - // that format so we can easily check for literal matches. - $destination = $method.' /'.trim($uri, '/'); - - if (array_key_exists($destination, static::$routes)) + // Of course literal route matches are the quickest to find, so we will + // check for those first. If the destination key exists in teh routes + // array we can just return that route now. + if (array_key_exists($uri, static::$routes[$method])) { - return new Route($destination, static::$routes[$destination], array()); + $action = static::$routes[$method][$uri]; + + return new Route($method, $uri, $action); } - // If we can't find a literal match, we'll iterate through all of - // the registered routes to find a matching route that uses some - // regular expressions or wildcards. - if ( ! is_null($route = static::match($destination))) + // If we can't find a literal match we'll iterate through all of the + // registered routes to find a matching route based on the route's + // regular expressions and wildcards. + if ( ! is_null($route = static::match($method, $uri))) { return $route; } - - // If the bundle handling the request is not the default bundle, - // we want to remove the root "handles" string from the URI so - // it will not interfere with searching for a controller. - // - // If we left it on the URI, the root controller for the bundle - // would need to be nested in directories matching the clause. - // This will not intefere with the Route::handles method - // as the destination is used to set the route's URIs. - if ($bundle !== DEFAULT_BUNDLE) - { - $uri = str_replace(Bundle::option($bundle, 'handles'), '', $uri); - - $uri = ltrim($uri, '/'); - } - - $segments = Str::segments($uri); - - return static::controller($bundle, $method, $destination, $segments); } /** * Iterate through every route to find a matching route. * - * @param string $destination + * @param string $method + * @param string $uri * @return Route */ - protected static function match($destination) + protected static function match($method, $uri) { - foreach (static::routes() as $route => $action) + foreach (static::routes($method) as $route => $action) { - // We only need to check routes with regular expressions since - // all other routes would have been able to be caught by the - // check for literal matches we just did. - if (strpos($route, '(') !== false) + // We only need to check routes with regular expression since all other + // would have been able to be matched by the search for literal matches + // we just did before we started searching. + if (str_contains($route, '(')) { $pattern = '#^'.static::wildcards($route).'$#'; - // If we get a match, we'll return the route and slice off - // the first parameter match, as preg_match sets the first - // array item to the full-text match. - if (preg_match($pattern, $destination, $parameters)) + // If we get a match we'll return the route and slice off the first + // parameter match, as preg_match sets the first array item to the + // full-text match of the pattern. + if (preg_match($pattern, $uri, $parameters)) { - return new Route($route, $action, array_slice($parameters, 1)); + return new Route($method, $route, $action, array_slice($parameters, 1)); } } } } - /** - * Attempt to find a controller for the incoming request. - * - * @param string $bundle - * @param string $method - * @param string $destination - * @param array $segments - * @return Route - */ - protected static function controller($bundle, $method, $destination, $segments) - { - if (count($segments) == 0) - { - $uri = '/'; - - // If the bundle is not the default bundle for the application, we'll - // set the root URI as the root URI registered with the bundle in the - // bundle configuration file for the application. It's registered in - // the bundle configuration using the "handles" clause. - if ($bundle !== DEFAULT_BUNDLE) - { - $uri = '/'.Bundle::get($bundle)->handles; - } - - // We'll generate a default "uses" clause for the route action that - // points to the default controller and method for the bundle so - // that the route will execute the default. - $action = array('uses' => Bundle::prefix($bundle).'home@index'); - - return new Route($method.' '.$uri, $action); - } - - $directory = Bundle::path($bundle).'controllers/'; - - if ( ! is_null($key = static::locate($segments, $directory))) - { - // First, we'll extract the controller name, then, since we need - // to extract the method and parameters, we will remove the name - // of the controller from the URI. Then we can shift the method - // off of the array of segments. Any remaining segments are the - // parameters for the method. - $controller = implode('.', array_slice($segments, 0, $key)); - - $segments = array_slice($segments, $key); - - $method = (count($segments) > 0) ? array_shift($segments) : 'index'; - - // We need to grab the prefix to the bundle so we can prefix - // the route identifier with it. This informs the controller - // class out of which bundle the controller instance should - // be resolved when it is needed by the app. - $prefix = Bundle::prefix($bundle); - - $action = array('uses' => $prefix.$controller.'@'.$method); - - return new Route($destination, $action, $segments); - } - } - - /** - * Locate the URI segment matching a controller name. - * - * @param array $segments - * @param string $directory - * @return int - */ - protected static function locate($segments, $directory) - { - for ($i = count($segments) - 1; $i >= 0; $i--) - { - // To find the proper controller, we need to iterate backwards through - // the URI segments and take the first file that matches. That file - // should be the deepest possible controller matched by the URI. - if (file_exists($directory.implode('/', $segments).EXT)) - { - return $i + 1; - } - - // If a controller did not exist for the segments, we will pop - // the last segment off of the array so that on the next run - // through the loop we'll check one folder up from the one - // we checked on this iteration. - array_pop($segments); - } - } - /** * Translate route URI wildcards into regular expressions. * @@ -416,7 +457,7 @@ protected static function wildcards($key) // For optional parameters, first translate the wildcards to their // regex equivalent, sans the ")?" ending. We'll add the endings - // back on after we know the replacement count. + // back on when we know the replacement count. $key = str_replace($search, $replace, $key, $count); if ($count > 0) @@ -428,13 +469,58 @@ protected static function wildcards($key) } /** - * Get all of the registered routes, with fallbacks at the end. + * Get all of the routes across all request methods. * * @return array */ - public static function routes() + public static function all() { - return array_merge(static::$routes, static::$fallback); + $all = array(); + + // To get all the routes, we'll just loop through each request + // method supported by the router and merge in each of the + // arrays into the main array of routes. + foreach (static::$methods as $method) + { + $all = array_merge($all, static::routes($method)); + } + + return $all; + } + + /** + * Get all of the registered routes, with fallbacks at the end. + * + * @param string $method + * @return array + */ + public static function routes($method = null) + { + $routes = array_get(static::$routes, $method, array()); + + return array_merge($routes, array_get(static::$fallback, $method, array())); + } + + /** + * Get all of the wildcard patterns + * + * @return array + */ + public static function patterns() + { + return array_merge(static::$patterns, static::$optional); + } + + /** + * Get a string repeating a URI pattern any number of times. + * + * @param string $pattern + * @param int $times + * @return string + */ + protected static function repeat($pattern, $times) + { + return implode('/', array_fill(0, $times, $pattern)); } } \ No newline at end of file diff --git a/laravel/uri.php b/laravel/uri.php index 177c0db4..b94c95e0 100644 --- a/laravel/uri.php +++ b/laravel/uri.php @@ -21,7 +21,20 @@ class URI { * * @var array */ - protected static $attempt = array('PATH_INFO', 'REQUEST_URI', 'PHP_SELF', 'REDIRECT_URL'); + protected static $attempt = array( + 'PATH_INFO', 'REQUEST_URI', + 'PHP_SELF', 'REDIRECT_URL' + ); + + /** + * Get the full URI including the query string. + * + * @return string + */ + public static function full() + { + return static::current().static::query(); + } /** * Get the URI for the current request. @@ -39,7 +52,7 @@ public static function current() // If you ever encounter this error, please inform the nerdy Laravel // dev team with information about your server. We want to support - // Laravel an as many server environments as possible! + // Laravel an as many servers as we possibly can! if (is_null(static::$uri)) { throw new \Exception("Could not detect request URI."); @@ -81,16 +94,16 @@ protected static function detect() */ protected static function format($uri) { - // First we want to remove the application's base URL from the URI - // if it is in the string. It is possible for some of the server - // variables to include the entire document root. + // First we want to remove the application's base URL from the URI if it is + // in the string. It is possible for some of the parsed server variables to + // include the entire document root in the string. $uri = static::remove_base($uri); $index = '/'.Config::get('application.index'); - // Next we'll remove the index file from the URI if it is there - // and then finally trim down the URI. If the URI is left with - // nothing but spaces, we use a single slash for root. + // Next we'll remove the index file from the URI if it is there and then + // finally trim down the URI. If the URI is left with spaces, we'll use + // a single slash for the root URI. if ($index !== '/') { $uri = static::remove($uri, $index); @@ -99,6 +112,29 @@ protected static function format($uri) return trim($uri, '/') ?: '/'; } + /** + * Determine if the current URI matches a given pattern. + * + * @param string $pattern + * @return bool + */ + public static function is($pattern) + { + // Asterisks are translated into zero-or-more regular expression wildcards + // to make it convenient to check if the URI starts with a given pattern + // such as "library/*". This is only done when not root. + if ($pattern !== '/') + { + $pattern = str_replace('*', '(.*)', $pattern).'\z'; + } + else + { + $pattern = '^/$'; + } + + return preg_match('#'.$pattern.'#', static::current()); + } + /** * Parse the PATH_INFO server variable. * @@ -201,4 +237,14 @@ protected static function remove($uri, $value) return (strpos($uri, $value) === 0) ? substr($uri, strlen($value)) : $uri; } + /** + * Get the query string for the current request. + * + * @return string + */ + protected static function query() + { + return (count((array) $_GET) > 0) ? '?'.http_build_query($_GET) : ''; + } + } \ No newline at end of file diff --git a/laravel/url.php b/laravel/url.php index 409fe678..d3f9ee5c 100644 --- a/laravel/url.php +++ b/laravel/url.php @@ -1,4 +1,4 @@ -get($this->language); diff --git a/laravel/view.php b/laravel/view.php index 1b153d55..a8ec4796 100644 --- a/laravel/view.php +++ b/laravel/view.php @@ -67,7 +67,7 @@ public function __construct($view, $data = array()) // // This makes error display in the view extremely convenient, since the // developer can always assume they have a message container instance - // available to them in the view. + // available to them in the view's variables. if ( ! isset($this->data['errors'])) { if (Session::started() and Session::has('errors')) diff --git a/storage/cache/.gitignore b/storage/cache/.gitignore index 8ba4aece..c96a04f0 100644 --- a/storage/cache/.gitignore +++ b/storage/cache/.gitignore @@ -1 +1,2 @@ -laravel.bundle.manifest \ No newline at end of file +* +!.gitignore \ No newline at end of file