merged skunkworks into develop.

This commit is contained in:
Taylor Otwell 2012-01-16 13:59:24 -06:00
parent 610d8827c4
commit b5442c67fc
117 changed files with 7268 additions and 3999 deletions

42
application/bundle.php Normal file
View File

@ -0,0 +1,42 @@
<?php
/*
|--------------------------------------------------------------------------
| Auto-Loader PSR-0 Directories
|--------------------------------------------------------------------------
|
| The Laravel auto-loader can search directories for files using the PSR-0
| naming convention. This convention basically organizes classes by using
| the class namespace to indicate the directory structure.
|
| So you don't have to manually map all of your models, we've added the
| models and libraries directories for you. So, you can model away and
| the auto-loader will take care of the rest.
|
*/
Autoloader::psr(array(
APP_PATH.'models',
APP_PATH.'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(
//'User' => APP_PATH.'models/user.php',
//'Role' => APP_PATH.'models/role.php',
));

View File

@ -1,47 +0,0 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| View Names & Composers
|--------------------------------------------------------------------------
|
| Named views give you beautiful syntax when working with your views.
|
| Here's how to define a named view:
|
| 'home.index' => array('name' => 'home')
|
| Now, you can create an instance of that view using the very expressive
| View::of dynamic method. Take a look at this example:
|
| return View::of_home();
|
| View composers provide a convenient way to add common elements to a view
| each time it is created. For example, you may wish to bind a header and
| footer partial each time the view is created.
|
| The composer will receive an instance of the view being created, and is
| free to modify the view however you wish. Here is how to define one:
|
| 'home.index' => function($view)
| {
| //
| }
|
| Of course, you may define a view name and a composer for a single view:
|
| 'home.index' => array('name' => 'home', function($view)
| {
| //
| })
|
*/
'home.index' => array('name' => 'home', function($view)
{
// This composer is called for the "home.index" view.
}),
);

View File

@ -7,7 +7,9 @@
| Application URL | Application URL
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The URL used to access your application. No trailing slash. | The URL used to access your application without a trailing slash. The URL
| does nto have to be set. If it isn't we'll try our best to guess the URL
| of your application.
| |
*/ */
@ -20,8 +22,8 @@
| |
| If you are including the "index.php" in your URLs, you can ignore this. | If you are including the "index.php" in your URLs, you can ignore this.
| |
| However, if you are using mod_rewrite or something similar to get | However, if you are using mod_rewrite to get cleaner URLs, just set
| cleaner URLs, set this option to an empty string. | this option to an empty string and we'll take care of the rest.
| |
*/ */
@ -32,11 +34,10 @@
| Application Key | Application Key
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The application key should be a random, 32 character string.
|
| This key is used by the encryption and cookie classes to generate secure | This key is used by the encryption and cookie classes to generate secure
| encrypted strings and hashes. It is extremely important that this key | encrypted strings and hashes. It is extremely important that this key
| remain secret and should not be shared with anyone. | remain secret and should not be shared with anyone. Make it about 32
| characters of random gibberish.
| |
*/ */
@ -48,7 +49,8 @@
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The default character encoding used by your application. This encoding | The default character encoding used by your application. This encoding
| will be used by the Str, Text, and Form classes. | will be used by the Str, Text, Form, and any other classes that need
| to know what type of encoding to use for your awesome application.
| |
*/ */
@ -89,12 +91,28 @@
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The default timezone of your application. This timezone will be used when | The default timezone of your application. This timezone will be used when
| Laravel needs a date, such as when writing to a log file. | Laravel needs a date, such as when writing to a log file or travelling
| to a distant star at warp speed.
| |
*/ */
'timezone' => 'UTC', 'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Autoloaded Bundles
|--------------------------------------------------------------------------
|
| Bundles can provide a ton of awesome drop-in functionality for your web
| application. Everything from Twitter integration to an admin backend.
|
| Here you may specify the bundles that should be automatically started
| on every request to your application.
|
*/
'bundles' => array(),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Class Aliases | Class Aliases
@ -112,26 +130,26 @@
*/ */
'aliases' => array( 'aliases' => array(
'Arr' => 'Laravel\\Arr',
'Asset' => 'Laravel\\Asset',
'Auth' => 'Laravel\\Auth', 'Auth' => 'Laravel\\Auth',
'Asset' => 'Laravel\\Asset',
'Autoloader' => 'Laravel\\Autoloader', 'Autoloader' => 'Laravel\\Autoloader',
'Benchmark' => 'Laravel\\Benchmark', 'Bundle' => 'Laravel\\Bundle',
'Cache' => 'Laravel\\Cache\\Manager', 'Cache' => 'Laravel\\Cache',
'Config' => 'Laravel\\Config', 'Config' => 'Laravel\\Config',
'Controller' => 'Laravel\\Routing\\Controller', 'Controller' => 'Laravel\\Routing\\Controller',
'Cookie' => 'Laravel\\Cookie', 'Cookie' => 'Laravel\\Cookie',
'Crypter' => 'Laravel\\Crypter', 'Crypter' => 'Laravel\\Crypter',
'DB' => 'Laravel\\Database\\Manager', 'DB' => 'Laravel\\Database',
'Eloquent' => 'Laravel\\Database\\Eloquent\\Model', 'Event' => 'Laravel\\Event',
'File' => 'Laravel\\File', 'File' => 'Laravel\\File',
'Filter' => 'Laravel\\Routing\\Filter',
'Form' => 'Laravel\\Form', 'Form' => 'Laravel\\Form',
'Hash' => 'Laravel\\Hash', 'Hash' => 'Laravel\\Hash',
'HTML' => 'Laravel\\HTML', 'HTML' => 'Laravel\\HTML',
'Inflector' => 'Laravel\\Inflector',
'Input' => 'Laravel\\Input', 'Input' => 'Laravel\\Input',
'IoC' => 'Laravel\\IoC', 'IoC' => 'Laravel\\IoC',
'Lang' => 'Laravel\\Lang', 'Lang' => 'Laravel\\Lang',
'Log' => 'Laravel\\Log',
'Memcached' => 'Laravel\\Memcached', 'Memcached' => 'Laravel\\Memcached',
'Paginator' => 'Laravel\\Paginator', 'Paginator' => 'Laravel\\Paginator',
'URL' => 'Laravel\\URL', 'URL' => 'Laravel\\URL',
@ -139,9 +157,13 @@
'Redis' => 'Laravel\\Redis', 'Redis' => 'Laravel\\Redis',
'Request' => 'Laravel\\Request', 'Request' => 'Laravel\\Request',
'Response' => 'Laravel\\Response', 'Response' => 'Laravel\\Response',
'Router' => 'Laravel\\Routing\\Router',
'Schema' => 'Laravel\\Database\\Schema',
'Section' => 'Laravel\\Section', 'Section' => 'Laravel\\Section',
'Session' => 'Laravel\\Facades\\Session', 'Session' => 'Laravel\\Session',
'Str' => 'Laravel\\Str', 'Str' => 'Laravel\\Str',
'Task' => 'Laravel\\CLI\\Tasks\\Task',
'URI' => 'Laravel\\URI',
'Validator' => 'Laravel\\Validator', 'Validator' => 'Laravel\\Validator',
'View' => 'Laravel\\View', 'View' => 'Laravel\\View',
), ),

View File

@ -2,45 +2,27 @@
return array( return array(
/*
|--------------------------------------------------------------------------
| Authentication Username
|--------------------------------------------------------------------------
|
} This option should be set to the "username" property of your users.
| Typically, this will be set to "email" or "username".
|
| The value of this property will be used by the "attempt" closure when
| searching for users by their username. It will also be used when the
| user is set to be "remembered", as the username is embedded into the
| encrypted cookie and is used to verify the user's identity.
|
*/
'username' => 'email',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Retrieve The Current User | Retrieve The Current User
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This closure is called by the Auth::user() method when attempting to | This closure is called by the Auth class' "user" method when trying to
| retrieve a user by their ID stored in the session. | retrieve a user by the ID that is stored in their session. If you find
| the user, just return the user object, but make sure it has an "id"
| property. If you can't find the user, just return null.
| |
| Simply return an object representing the user with the given ID. Or, if | Of course, a simple and elegant authentication solution has already
| no user with the given ID is registered to use your application, you do | been provided for you using the query builder and hashing engine.
| not need to return anything. | We love making your life as easy as possible.
|
| Of course, a simple, elegant authentication solution is already provided
| for you using Eloquent and the default Laravel hashing engine.
| |
*/ */
'user' => function($id) 'user' => function($id)
{ {
if ( ! is_null($id) and filter_var($id, FILTER_VALIDATE_INT) !== false) if (filter_var($id, FILTER_VALIDATE_INT) !== false)
{ {
return User::find($id); return DB::table('users')->find($id);
} }
}, },
@ -50,19 +32,19 @@
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This closure is called by the Auth::attempt() method when attempting to | This closure is called by the Auth::attempt() method when attempting to
| authenticate a user that is logging into your application. | authenticate a user that is logging into your application. It's like a
| super buff bouncer to your application.
| |
| If the provided credentials are correct, simply return an object that | If the provided credentials are correct, simply return an object that
| represents the user being authenticated. If the credentials are not | represents the user being authenticated. As long as it has a property
| valid, don't return anything. | for the "id", any object will work. If the credentials are not valid,
| | you don't meed to return anything.
| Note: If a user object is returned, it must have an "id" property.
| |
*/ */
'attempt' => function($username, $password, $config) 'attempt' => function($username, $password)
{ {
$user = User::where($config['username'], '=', $username)->first(); $user = DB::table('users')->where_username($username)->first();
if ( ! is_null($user) and Hash::check($password, $user->password)) if ( ! is_null($user) and Hash::check($password, $user->password))
{ {
@ -72,12 +54,12 @@
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Logout | Logout The Current User
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may do anything that needs to be done when a user logs out of | Here you may do anything that needs to be done when a user logs out of
| your application, such as call the logout method on a third-party API | your application, such as call the logout method on a third-party API
| you are using for authentication, or anything else you desire. | you are using for authentication or anything else you desire.
| |
*/ */

View File

@ -7,12 +7,15 @@
| Cache Driver | Cache Driver
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The name of the default cache driver for your application. | The name of the default cache driver for your application. Caching can
| be used to increase the performance of your application by storing any
| commonly accessed data in memory, a file, or some other storage.
| |
| Caching can be used to increase the performance of your application | A variety of awesome drivers are available for you to use with Laravel.
| by storing commonly accessed data in memory or in a file. | Some, like APC, are extremely fast. However, if that isn't an option
| in your environment, try file or database caching.
| |
| Supported Drivers: 'file', 'memcached', 'apc', 'redis'. | Drivers: 'file', 'memcached', 'apc', 'redis', 'database'.
| |
*/ */
@ -23,25 +26,39 @@
| Cache Key | Cache Key
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| This key will be prepended to item keys stored using Memcached and APC to | This key will be prepended to item keys stored using Memcached and APC
| prevent collisions with other applications on the server. | to prevent collisions with other applications on the server. Since the
| memory based stores could be shared by other applications, we need to
| be polite and use a prefix to uniquely identifier our items.
| |
*/ */
'key' => 'laravel', 'key' => 'laravel',
/*
|--------------------------------------------------------------------------
| Cache Database
|--------------------------------------------------------------------------
|
| When using the database cache driver, this database table will be used
| to store the cached item. You may also add a "connection" option to
| the array to specify which database connection should be used.
|
*/
'database' => array('table' => 'laravel_cache'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Memcached Servers | Memcached Servers
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The Memcached servers used by your application. | The Memcached servers used by your application. Memcached is a free and
| open source, high-performance, distributed memory caching system. It is
| generic in nature but intended for use in speeding up web applications
| by alleviating database load.
| |
| Memcached is a free and open source, high-performance, distributed memory | For more information, check out: http://memcached.org
| object caching system, generic in nature, but intended for use in speeding
| up dynamic web applications by alleviating database load.
|
| For more information about Memcached, check out: http://memcached.org
| |
*/ */

View File

@ -1,54 +0,0 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Inversion of Control Container
|--------------------------------------------------------------------------
|
| Here you may define resolvers for the Laravel inversion of control (IoC)
| container. An IoC container provides the ability to create more flexible
| and testable applications, as well as a convenient method of managing
| the instantiation of complex objects.
|
| To register a resolver in the container, simple create add an item to
| the array for the object with a closure that returns an instance of
| the object.
|
| For example, here's how to register a resolver for a Mailer class:
|
| 'mailer' => function($c)
| {
| return new Mailer($sender, $key);
| }
|
| Note that the container instance itself is passed into the resolver,
| allowing you to continue to resolve dependencies within the resolver
| itself. This allows you to easily resolve nested dependencies.
|
| When creating controller instances, Laravel will check to see if a
| resolver has been registered for the controller. If it has, it will
| be used to create the controller instance. All controller resolvers
| should be registered beginning using a {controllers}.{name} naming
| convention. For example:
|
| 'controllers.user' => function($c)
| {
| return new User_Controller($c->resolve('repository'));
| }
|
| Of course, sometimes you may wish to register an object as a singleton
| Singletons are resolved by the controller the first time they are
| resolved; however, that same resolved instance will continue to be
| returned by the container each time it is requested. Registering an
| object as a singleton couldn't be simpler:
|
| 'mailer' => array('singleton' => true, 'resolver' => function($c)
| {
| return new Mailer($sender, $key);
| })
|
*/
);

View File

@ -7,39 +7,29 @@
| Default Database Connection | Default Database Connection
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The name of your default database connection. | The name of your default database connection. This connection will used
| | as the default for all database operations unless a different name is
| This connection will be the default for all database operations unless a | given when performing said operation. This connection name should be
| different connection is specified when performing the operation. | listed in the array of connections below.
| |
*/ */
'default' => 'sqlite', 'default' => 'mysql',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Database Connections | Database Connections
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| All of the database connections used by your application. | All of the database connections used by your application. Many of your
| applications will no doubt only use one connection; however, you have
| the freedom to specify as many connections as you can handle.
| |
| Supported Drivers: 'mysql', 'pgsql', 'sqlite'. | All database work in Laravel is done through the PHP's PDO facilities,
| so make sure you have the PDO drivers for your particlar database of
| choice installed on your machine.
| |
| Note: When using the SQLite driver, the path and "sqlite" extention will | Drivers: 'mysql', 'pgsql', 'sqlsrv', 'sqlite'.
| be added automatically. You only need to specify the database name.
|
| Using a driver that isn't supported? You can still establish a PDO
| connection. Simply specify a driver and DSN option:
|
| 'odbc' => array(
| 'driver' => 'odbc',
| 'dsn' => 'your-dsn',
| 'username' => 'username',
| 'password' => 'password',
| )
|
| Note: When using an unsupported driver, Eloquent and the fluent query
| builder may not work as expected.
| |
*/ */
@ -68,6 +58,14 @@
'charset' => 'utf8', 'charset' => 'utf8',
), ),
'sqlsrv' => array(
'driver' => 'sqlsrv',
'host' => 'localhost',
'database' => 'database',
'username' => 'root',
'password' => 'password',
),
), ),
/* /*
@ -77,11 +75,9 @@
| |
| Redis is an open source, fast, and advanced key-value store. However, it | Redis is an open source, fast, and advanced key-value store. However, it
| provides a richer set of commands than a typical key-value store such as | provides a richer set of commands than a typical key-value store such as
| APC or memcached. | APC or memcached. All the cool kids are using it.
| |
| Here you may specify the hosts and ports for your Redis databases. | To get the scoop on Redis, check out: http://redis.io
|
| For more information regarding Redis, check out: http://redis.io
| |
*/ */

View File

@ -7,7 +7,7 @@
| Ignored Error Levels | Ignored Error Levels
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Here you may specify the error levels that should be ignored by the | Here you simply specify the error levels that should be ignored by the
| Laravel error handler. These levels will still be logged; however, no | Laravel error handler. These levels will still be logged; however, no
| information about about them will be displayed. | information about about them will be displayed.
| |
@ -22,10 +22,11 @@
| |
| Detailed error messages contain information about the file in which an | Detailed error messages contain information about the file in which an
| error occurs, as well as a PHP stack trace containing the call stack. | error occurs, as well as a PHP stack trace containing the call stack.
| You'll want them when you're trying to debug your application.
| |
| If your application is in production, consider turning off error details | If your application is in production, you'll want to turn off the error
| for enhanced security and user experience. The error stack trace could | details for enhanced security and user experience since the exception
| contain sensitive information that should not be publicly visible. | stack trace could contain sensitive information.
| |
*/ */
@ -56,18 +57,13 @@
| |
| You may log the error message however you like; however, a simple log | You may log the error message however you like; however, a simple log
| solution has been setup for you which will log all error messages to | solution has been setup for you which will log all error messages to
| a single text file within the application storage directory. | text files within the application storage directory.
|
| Of course, you are free to implement more complex solutions including
| emailing the exceptions details to your team, etc.
| |
*/ */
'logger' => function($exception) 'logger' => function($exception)
{ {
$message = (string) $exception; Log::exception($exception);
File::append(STORAGE_PATH.'log.txt', date('Y-m-d H:i:s').' - '.$message.PHP_EOL);
}, },
); );

View File

@ -7,12 +7,12 @@
| Session Driver | Session Driver
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The name of the session driver for your application. | The name of the session driver used by your application. Since HTTP is
| stateless, sessions are used to simulate "state" across requests made
| by the same user of your application. In other words, it's how an
| application knows who the heck you are.
| |
| Since HTTP is stateless, sessions are used to maintain "state" across | Drivers: 'cookie', 'file', 'database', 'memcached', 'apc', 'redis'.
| multiple requests from the same user of your application.
|
| Supported Drivers: 'cookie', 'file', 'database', 'memcached', 'apc', 'redis'.
| |
*/ */
@ -23,9 +23,9 @@
| Session Database | Session Database
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The database table on which the session should be stored. | The database table on which the session should be stored. It probably
| | goes without saying that this option only matters if you are using
| This option is only relevant when using the "database" session driver. | the super slick database session driver.
| |
*/ */
@ -40,8 +40,9 @@
| This option specifies the probability of session garbage collection | This option specifies the probability of session garbage collection
| occuring for any given request. | occuring for any given request.
| |
| For example, the default value states that garbage collection has about | For example, the default value states that garbage collection has a
| a 2% (2 / 100) chance of occuring for any given request. | 2% chance of occuring for any given request to the application.
| Feel free to tune this to your application's size and speed.
| |
*/ */

View File

@ -0,0 +1,119 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| String Inflection
|--------------------------------------------------------------------------
|
| This array contains the singular and plural forms of words. It's used by
| the "singular" and "plural" methods on the Str class to convert a given
| word from singular to plural and vice versa.
|
| This simple array is in constrast to the complicated regular expression
| patterns used by other frameworks. We think you'll enjoy the speed and
| simplicity of this solution.
|
| When adding a word to the array, the key should be the singular form,
| while the array value should be the plural form. We've included an
| example to get you started!
|
*/
'inflection' => array(
'user' => 'users',
'person' => 'people',
'comment' => 'comments',
),
/*
|--------------------------------------------------------------------------
| ASCII Characters
|--------------------------------------------------------------------------
|
| This array contains foreign characters and their 7-bit ASCII equivalents.
| The array is used by the "ascii" method on the Str class to get strings
| ready for inclusion in a URL slug.
|
| Of course, the "ascii" method may also be used by you for whatever your
| application requires. Feel free to add any characters we missed, and be
| sure to let us know about them!
|
*/
'ascii' => array(
'/æ|ǽ/' => 'ae',
'/œ/' => 'oe',
'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|А/' => 'A',
'/à|á|â|ã|ä|å|ǻ|ā|ă|ą|ǎ|ª|а/' => 'a',
'/Б/' => 'B',
'/б/' => 'b',
'/Ç|Ć|Ĉ|Ċ|Č|Ц/' => 'C',
'/ç|ć|ĉ|ċ|č|ц/' => 'c',
'/Ð|Ď|Đ|Д/' => 'Dj',
'/ð|ď|đ|д/' => 'dj',
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Е|Ё|Э/' => 'E',
'/è|é|ê|ë|ē|ĕ|ė|ę|ě|е|ё|э/' => 'e',
'/Ф/' => 'F',
'/ƒ|ф/' => 'f',
'/Ĝ|Ğ|Ġ|Ģ|Г/' => 'G',
'/ĝ|ğ|ġ|ģ|г/' => 'g',
'/Ĥ|Ħ|Х/' => 'H',
'/ĥ|ħ|х/' => 'h',
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|И/' => 'I',
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|и/' => 'i',
'/Ĵ|Й/' => 'J',
'/ĵ|й/' => 'j',
'/Ķ|К/' => 'K',
'/ķ|к/' => 'k',
'/Ĺ|Ļ|Ľ|Ŀ|Ł|Л/' => 'L',
'/ĺ|ļ|ľ|ŀ|ł|л/' => 'l',
'/М/' => 'M',
'/м/' => 'm',
'/Ñ|Ń|Ņ|Ň|Н/' => 'N',
'/ñ|ń|ņ|ň|ʼn|н/' => 'n',
'/Ö|Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|О/' => 'O',
'/ö|ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|о/' => 'o',
'/П/' => 'P',
'/п/' => 'p',
'/Ŕ|Ŗ|Ř|Р/' => 'R',
'/ŕ|ŗ|ř|р/' => 'r',
'/Ś|Ŝ|Ş|Š|С/' => 'S',
'/ś|ŝ|ş|š|ſ|с/' => 's',
'/Ţ|Ť|Ŧ|Т/' => 'T',
'/ţ|ť|ŧ|т/' => 't',
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|У/' => 'U',
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|у/' => 'u',
'/В/' => 'V',
'/в/' => 'v',
'/Ý|Ÿ|Ŷ|Ы/' => 'Y',
'/ý|ÿ|ŷ|ы/' => 'y',
'/Ŵ/' => 'W',
'/ŵ/' => 'w',
'/Ź|Ż|Ž|З/' => 'Z',
'/ź|ż|ž|з/' => 'z',
'/Æ|Ǽ/' => 'AE',
'/ß/'=> 'ss',
'/IJ/' => 'IJ',
'/ij/' => 'ij',
'/Œ/' => 'OE',
'/Ч/' => 'Ch',
'/ч/' => 'ch',
'/Ю/' => 'Ju',
'/ю/' => 'ju',
'/Я/' => 'Ja',
'/я/' => 'ja',
'/Ш/' => 'Sh',
'/ш/' => 'sh',
'/Щ/' => 'Shch',
'/щ/' => 'shch',
'/Ж/' => 'Zh',
'/ж/' => 'zh',
),
);

View File

@ -7,10 +7,10 @@ class Home_Controller extends Controller {
| The Default Controller | The Default Controller
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Instead of using RESTful routes and anonymous functions, you may wish to | Instead of using RESTful routes and anonymous functions, you might wish
| use controllers to organize your application API. You'll love them. | to use controllers to organize your application API. You'll love them.
| |
| To start using this controller, simply remove the default route from the | To start using this controller simply remove the default route from the
| application "routes.php" file. Laravel is smart enough to find this | application "routes.php" file. Laravel is smart enough to find this
| controller and call the default method, which is "action_index". | controller and call the default method, which is "action_index".
| |

View File

@ -1,68 +0,0 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Filters
|--------------------------------------------------------------------------
|
| Filters provide a convenient method for attaching functionality to your
| routes. Filters can run either before or after a route is exectued.
|
| The built-in "before" and "after" filters are called before and after
| every request to your application; however, you may create other filters
| that can be attached to individual routes.
|
| Filters also make common tasks such as authentication and CSRF protection
| a breeze. If a filter that runs before a route returns a response, that
| response will override the route action.
|
| Let's walk through an example...
|
| First, define a filter:
|
| 'simple_filter' => function()
| {
| return 'Filtered!';
| }
|
| Next, attach the filter to a route:
|
| 'GET /' => array('before' => 'simple_filter', function()
| {
| return 'Hello World!';
| })
|
| Now every requests to http://example.com will return "Filtered!", since
| the filter is overriding the route action by returning a value.
|
| To make your life easier, we have built authentication and CSRF filters
| that are ready to attach to your routes. Enjoy.
|
*/
'before' => function()
{
// Do stuff before every request to your application.
},
'after' => function($response)
{
// Do stuff after every request to your application.
},
'auth' => function()
{
if (Auth::guest()) return Redirect::to_login();
},
'csrf' => function()
{
if (Request::forged()) return Response::error('500');
},
);

View File

@ -8,8 +8,8 @@
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The following language lines are used by the paginator library to build | The following language lines are used by the paginator library to build
| the pagination links. They may be easily changed by the developer to | the pagination links. You're free to change them to anything you want.
| anything they wish. | If you come up with something more exciting, let us know.
| |
*/ */

View File

@ -8,11 +8,12 @@
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| The following language lines are used to swap attribute place-holders | The following language lines are used to swap attribute place-holders
| with something more reader friendly, such as "E-Mail Address" instead | with something more reader friendly such as "E-Mail Address" instead
| of "email". | of "email". Your users will thank you.
| |
| The Validator class will automatically search this array of lines when | The Validator class will automatically search this array of lines it
| attempting to replace the :attribute place-holder in error messages. | is attempting to replace the :attribute place-holder in messages.
| It's pretty slick. We think you'll like it.
| |
*/ */
@ -28,9 +29,9 @@
| such as the size (max, min, between) rules. These versions are used | such as the size (max, min, between) rules. These versions are used
| for different input types such as strings and files. | for different input types such as strings and files.
| |
| These language lines may be easily changed by the developer to provide | These language lines may be easily changed to provide custom error
| custom error messages in their application. Error messages for custom | messages in your application. Error messages for custom validation
| validation rules may also be added to this file. | rules may also be added to this file.
| |
*/ */

0
application/migrations/.gitignore vendored Normal file
View File

View File

@ -1,42 +1,87 @@
<?php <?php
return array( /*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Simply tell Laravel the HTTP verbs and URIs it should respond to. It is a
| breeze to setup your applications using Laravel's RESTful routing, and it
| is perfectly suited for building both large applications and simple APIs.
| Enjoy the fresh air and simplicity of the framework.
|
| Let's respond to a simple GET request to http://example.com/hello:
|
| Router::register('GET /hello', function()
| {
| return 'Hello World!';
| });
|
| You can even respond to more than one URI:
|
| Router::register('GET /hello, GET /world', function()
| {
| return 'Hello World!';
| });
|
| It's easy to allow URI wildcards using (:num) or (:any):
|
| Router::register('GET /hello/(:any)', function($name)
| {
| return "Welcome, $name.";
| });
|
*/
/* Router::register(array('GET /', 'GET /home'), function()
|-------------------------------------------------------------------------- {
| Application Routes return View::make('home.index');
|-------------------------------------------------------------------------- });
|
| Simply tell Laravel the HTTP verbs and URIs it should respond to. It's a
| piece of cake to create beautiful applications using the elegant RESTful
| routing available in Laravel.
|
| Let's respond to a simple GET request to http://example.com/hello:
|
| 'GET /hello' => function()
| {
| return 'Hello World!';
| }
|
| You can even respond to more than one URI:
|
| 'GET /hello, GET /world' => function()
| {
| return 'Hello World!';
| }
|
| It's easy to allow URI wildcards using (:num) or (:any):
|
| 'GET /hello/(:any)' => function($name)
| {
| return "Welcome, $name.";
| }
|
*/
'GET /' => function() /*
{ |--------------------------------------------------------------------------
return View::make('home.index'); | Route Filters
}, |--------------------------------------------------------------------------
|
| Filters provide a convenient method for attaching functionality to your
| routes. The built-in "before" and "after" filters are called before and
| after every request to your application, and you may even create other
| filters that can be attached to individual routes.
|
| Let's walk through an example...
|
| First, define a filter:
|
| Filter::register('filter', function()
| {
| return 'Filtered!';
| });
|
| Next, attach the filter to a route:
|
| Router::register('GET /', array('before' => 'filter', function()
| {
| return 'Hello World!';
| }));
|
*/
); Filter::register('before', function()
{
// Do stuff before every request to your application...
});
Filter::register('after', function()
{
// Do stuff after every request to your application...
});
Filter::register('csrf', function()
{
if (Request::forged()) return Response::error('500');
});
Filter::register('auth', function()
{
if (Auth::guest()) return Redirect::to('login');
});

View File

@ -1 +0,0 @@
*

View File

@ -1 +0,0 @@
*

View File

@ -1 +0,0 @@
*

0
application/tasks/.gitignore vendored Normal file
View File

49
artisan Normal file
View File

@ -0,0 +1,49 @@
<?php
/**
* Laravel (CLI) - A Command Line For Web Artisans
*
* @package Laravel
* @version 2.0.7
* @author Taylor Otwell <taylorotwell@gmail.com>
* @link http://laravel.com
*/
// --------------------------------------------------------------
// Define the directory separator for the environment.
// --------------------------------------------------------------
define('DS', DIRECTORY_SEPARATOR);
// --------------------------------------------------------------
// The path to the application directory.
// --------------------------------------------------------------
define('APP_PATH', realpath('application').'/');
// --------------------------------------------------------------
// The path to the bundles directory.
// --------------------------------------------------------------
define('BUNDLE_PATH', realpath('bundles').'/');
// --------------------------------------------------------------
// The path to the storage directory.
// --------------------------------------------------------------
define('STORAGE_PATH', realpath('storage').'/');
// --------------------------------------------------------------
// The path to the Laravel directory.
// --------------------------------------------------------------
define('SYS_PATH', realpath('laravel').'/');
// --------------------------------------------------------------
// The path to the public directory.
// --------------------------------------------------------------
define('PUBLIC_PATH', realpath('public').'/');
// --------------------------------------------------------------
// Bootstrap the Laravel core.
// --------------------------------------------------------------
require SYS_PATH.'core.php';
// --------------------------------------------------------------
// Launch the Laravel "Artisan" CLI.
// --------------------------------------------------------------
require SYS_PATH.'cli/artisan'.EXT;

0
bundles/.gitignore vendored Normal file
View File

View File

@ -1,30 +1,5 @@
# Laravel Change Log # Laravel Change Log
## Version 2.1.0
- Fix: Multiple wildcards / regular expressions per segment are now supported.
### Upgrading from 2.0.9
- Replace **laravel** directory.
## Version 2.0.9
- Minor: Made "timestamps" method in Eloquent model protected instead of private.
- Fix: Authentication cookies are not deleted properly when custom domains or paths are used.
### Upgrading from 2.0.8
- Replace **laravel** directory.
## Version 2.0.8
- Fix: Limited URI segments to 20 to protect against DDoS.
### Upgrading from 2.0.7
- Replace **laravel** directory.
## Version 2.0.7 ## Version 2.0.7
- Fix: Fixed raw_where in query builder. - Fix: Fixed raw_where in query builder.

View File

@ -1,166 +0,0 @@
<?php namespace Laravel; use Closure;
class Arr {
/**
* Get an item from an array.
*
* "Dot" notation may be used to dig deep into the array.
*
* <code>
* // Get the $array['user']['name'] value from the array
* $name = Arr::get($array, 'user.name');
*
* // Return a default from if the specified item doesn't exist
* $name = Arr::get($array, 'user.name', 'Taylor');
* </code>
*
* @param array $array
* @param string $key
* @param mixed $default
* @return mixed
*/
public static function get($array, $key, $default = null)
{
if (is_null($key)) return $array;
foreach (explode('.', $key) as $segment)
{
if ( ! is_array($array) or ! array_key_exists($segment, $array))
{
return ($default instanceof Closure) ? call_user_func($default) : $default;
}
$array = $array[$segment];
}
return $array;
}
/**
* Set an array item to a given value.
*
* The same "dot" syntax used by the "get" method may be used here.
*
* If no key is given to the method, the entire array will be replaced.
*
* <code>
* // Set the $array['user']['name'] value on the array
* Arr::set($array, 'user.name', 'Taylor');
* </code>
*
* @param array $array
* @param string $key
* @param mixed $value
* @return void
*/
public static function set(&$array, $key, $value)
{
if (is_null($key)) return $array = $value;
$keys = explode('.', $key);
while (count($keys) > 1)
{
$key = array_shift($keys);
if ( ! isset($array[$key]) or ! is_array($array[$key]))
{
$array[$key] = array();
}
$array =& $array[$key];
}
$array[array_shift($keys)] = $value;
}
/**
* Remove an array item from a given array.
*
* The same "dot" syntax used by the "get" method may be used here.
*
* <code>
* // Remove the $array['user']['name'] item from the array
* Arr::forget($array, 'user.name');
* </code>
*
* @param array $array
* @param string $key
* @return void
*/
public static function forget(&$array, $key)
{
if (is_null($key)) return;
$keys = explode('.', $key);
while (count($keys) > 1)
{
$key = array_shift($keys);
if ( ! isset($array[$key]) or ! is_array($array[$key]))
{
return;
}
$array =& $array[$key];
}
unset($array[array_shift($keys)]);
}
/**
* Return the first element in an array which passes a given truth test.
*
* <code>
* // Return the first array element that equals "Taylor"
* $value = Arr::first($array, function($k, $v) {return $v === 'Taylor';});
*
* // Return a default value if no matching element is found
* $value = Arr::first($array, function($k, $v) {return $v === 'Taylor'}, 'Default');
* </code>
*
* @param array $array
* @param Closure $callback
* @param mixed $default
* @return mixed
*/
public static function first($array, $callback, $default = null)
{
foreach ($array as $key => $value)
{
if (call_user_func($callback, $key, $value)) return $value;
}
return ($default instanceof Closure) ? call_user_func($default) : $default;
}
/**
* Remove all array values that are contained within a given array of values.
*
* <code>
* // Remove all array values that are empty strings
* $array = Arr::without($array, '');
*
* // Remove all array values that are "One", "Two", or "Three"
* $array = Arr::without($array, array('One', 'Two', 'Three'));
* </code>
*
* @param array $array
* @param array $without
* @return array
*/
public static function without($array, $without = array())
{
$without = (array) $without;
foreach ((array) $array as $key => $value)
{
if (in_array($value, $without)) unset($array[$key]);
}
return $array;
}
}

View File

@ -1,4 +1,4 @@
<?php namespace Laravel; <?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
class Asset { class Asset {
@ -34,7 +34,7 @@ public static function container($container = 'default')
} }
/** /**
* Magic Method for calling methods on the default Asset container. * Magic Method for calling methods on the default container.
* *
* <code> * <code>
* // Call the "styles" method on the default container * // Call the "styles" method on the default container
@ -60,6 +60,13 @@ class Asset_Container {
*/ */
public $name; public $name;
/**
* The bundle that the assets belong to.
*
* @var string
*/
public $bundle = DEFAULT_BUNDLE;
/** /**
* All of the registered assets. * All of the registered assets.
* *
@ -83,8 +90,8 @@ public function __construct($name)
* Add an asset to the container. * Add an asset to the container.
* *
* The extension of the asset source will be used to determine the type of * The extension of the asset source will be used to determine the type of
* asset being registered (CSS or JavaScript). If you are using a non-standard * asset being registered (CSS or JavaScript). When using a non-standard
* extension, you may use the style or script methods to register assets. * extension, the style/script methods may be used to register assets.
* *
* <code> * <code>
* // Add an asset to the container * // Add an asset to the container
@ -107,7 +114,7 @@ public function add($name, $source, $dependencies = array(), $attributes = array
{ {
$type = (pathinfo($source, PATHINFO_EXTENSION) == 'css') ? 'style' : 'script'; $type = (pathinfo($source, PATHINFO_EXTENSION) == 'css') ? 'style' : 'script';
return call_user_func(array($this, $type), $name, $source, $dependencies, $attributes); return $this->$type($name, $source, $dependencies, $attributes);
} }
/** /**
@ -147,6 +154,29 @@ public function script($name, $source, $dependencies = array(), $attributes = ar
return $this; return $this;
} }
/**
* Returns the full-path for an asset.
*
* @param string $source
* @return string
*/
public function path($source)
{
return Bundle::assets($this->bundle).$source;
}
/**
* Set the bundle that the container's assets belong to.
*
* @param string $bundle
* @return Asset_Container
*/
public function bundle($bundle)
{
$this->bundle = $bundle;
return $this;
}
/** /**
* Add an asset to the array of registered assets. * Add an asset to the array of registered assets.
* *
@ -219,6 +249,14 @@ protected function asset($group, $name)
$asset = $this->assets[$group][$name]; $asset = $this->assets[$group][$name];
// If the bundle source is not a complete URL, we will go ahead and prepend
// the bundle's asset path to the source provided with the asset. This will
// ensure that we attach the correct path to the asset.
if (filter_var($asset['source'], FILTER_VALIDATE_URL) === false)
{
$asset['source'] = Bundle::assets($this->bundle).$asset['source'];
}
return HTML::$group($asset['source'], $asset['attributes']); return HTML::$group($asset['source'], $asset['attributes']);
} }
@ -257,7 +295,7 @@ protected function evaluate_asset($asset, $value, $original, &$sorted, &$assets)
{ {
// If the asset has no more dependencies, we can add it to the sorted list // If the asset has no more dependencies, we can add it to the sorted list
// and remove it from the array of assets. Otherwise, we will not verify // and remove it from the array of assets. Otherwise, we will not verify
// the asset's dependencies and determine if they have already been sorted. // the asset's dependencies and determine if they've been sorted.
if (count($assets[$asset]['dependencies']) == 0) if (count($assets[$asset]['dependencies']) == 0)
{ {
$sorted[$asset] = $value; $sorted[$asset] = $value;
@ -289,7 +327,8 @@ protected function evaluate_asset($asset, $value, $original, &$sorted, &$assets)
* Verify that an asset's dependency is valid. * Verify that an asset's dependency is valid.
* *
* A dependency is considered valid if it exists, is not a circular reference, and is * A dependency is considered valid if it exists, is not a circular reference, and is
* not a reference to the owning asset itself. * not a reference to the owning asset itself. If the dependency doesn't exist, no
* error or warning will be given. For the other cases, an exception is thrown.
* *
* @param string $asset * @param string $asset
* @param string $dependency * @param string $dependency
@ -299,16 +338,20 @@ protected function evaluate_asset($asset, $value, $original, &$sorted, &$assets)
*/ */
protected function dependency_is_valid($asset, $dependency, $original, $assets) protected function dependency_is_valid($asset, $dependency, $original, $assets)
{ {
if ( ! isset($original[$dependency])) return false; if ( ! isset($original[$dependency]))
if ($dependency === $asset)
{ {
throw new \LogicException("Asset [$asset] is dependent on itself."); return false;
}
elseif ($dependency === $asset)
{
throw new \Exception("Asset [$asset] is dependent on itself.");
} }
elseif (isset($assets[$dependency]) and in_array($asset, $assets[$dependency]['dependencies'])) elseif (isset($assets[$dependency]) and in_array($asset, $assets[$dependency]['dependencies']))
{ {
throw new \LogicException("Assets [$asset] and [$dependency] have a circular dependency."); throw new \Exception("Assets [$asset] and [$dependency] have a circular dependency.");
} }
return true;
} }
} }

View File

@ -7,7 +7,7 @@ class Auth {
* *
* @var object * @var object
*/ */
protected static $user; public static $user;
/** /**
* The key used when storing the user ID in the session. * The key used when storing the user ID in the session.
@ -48,12 +48,6 @@ public static function check()
/** /**
* Get the current user of the application. * Get the current user of the application.
* *
* This method will call the "user" closure in the auth configuration file.
* If the user is not authenticated, null will be returned by the methd.
*
* If no user exists in the session, the method will check for a "remember me"
* cookie and attempt to login the user based on the value of that cookie.
*
* <code> * <code>
* // Get the current user of the application * // Get the current user of the application
* $user = Auth::user(); * $user = Auth::user();
@ -62,20 +56,26 @@ public static function check()
* $email = Auth::user()->email; * $email = Auth::user()->email;
* </code> * </code>
* *
* @return object * @return object|null
*/ */
public static function user() public static function user()
{ {
if ( ! is_null(static::$user)) return static::$user; if ( ! is_null(static::$user)) return static::$user;
$id = IoC::core('session')->get(Auth::user_key); $id = Session::get(Auth::user_key);
static::$user = call_user_func(Config::get('auth.user'), $id); // To retrieve the user, we'll first attempt to use the "user" Closure
// defined in the auth configuration file, passing in the ID. The user
// Closure gives the developer a ton of freedom surrounding how the
// user is actually retrieved.
$config = Config::get('auth');
// If the user was not found in the database, but a "remember me" cookie static::$user = call_user_func($config['user'], $id);
// exists, we will attempt to recall the user based on the cookie value.
// Since all cookies contain a fingerprint hash verifying that the have // If the user wasn't found in the database but a "remember me" cookie
// not been modified on the client, we should be able to trust it. // exists, we'll attempt to recall the user based on the cookie value.
// Since all cookies contain a fingerprint hash verifying that they
// haven't changed, we can trust it.
$recaller = Cookie::get(Auth::remember_key); $recaller = Cookie::get(Auth::remember_key);
if (is_null(static::$user) and ! is_null($recaller)) if (is_null(static::$user) and ! is_null($recaller))
@ -94,13 +94,17 @@ public static function user()
*/ */
protected static function recall($recaller) protected static function recall($recaller)
{ {
// When the "remember me" cookie is stored, it is encrypted and contains the // When the remember me cookie is stored, it is encrypted and contains
// user's ID and a long, random string. The ID and string are separated by // the user's ID and a long, random string. The segments are separated
// a pipe character. Since we exploded the decrypted string, we can just // by a pipe character so we'll explode on that.
// pass the first item in the array to the user Closure.
$recaller = explode('|', Crypter::decrypt($recaller)); $recaller = explode('|', Crypter::decrypt($recaller));
if ( ! is_null($user = call_user_func(Config::get('auth.user'), $recaller[0]))) // We'll pass the ID that was stored in the cookie into the same user
// Closure that is used by the "user" method. If the method returns
// a user, we will log them into the application.
$user = call_user_func(Config::get('auth.user'), $recaller[0]);
if ( ! is_null($user))
{ {
static::login($user); static::login($user);
@ -111,12 +115,13 @@ protected static function recall($recaller)
/** /**
* Attempt to log a user into the application. * Attempt to log a user into the application.
* *
* If the credentials are valid, the user will be logged into the application * <code>
* and their user ID will be stored in the session via the "login" method. * // Attempt to log a user into the application
* $success = Auth::attempt('username', 'password');
* *
* The user may also be "remembered", which will keep the user logged into the * // Attempt to login a user and set the "remember me" cookie
* application for one year or until they logout. The user is remembered via * Auth::attempt('username', 'password', true);
* an encrypted cookie. * </code>
* *
* @param string $username * @param string $username
* @param string $password * @param string $password
@ -127,32 +132,35 @@ public static function attempt($username, $password = null, $remember = false)
{ {
$config = Config::get('auth'); $config = Config::get('auth');
$user = call_user_func($config['attempt'], $username, $password, $config); // When attempting to login the user, we will call the "attempt" closure
// from the configuration file. This gives the developer the freedom to
// authenticate based on the needs of their application.
//
// All of the password hashing and checking and left totally up to the
// developer, as this gives them the freedom to use any hashing scheme
// or authentication provider they wish.
$user = call_user_func($config['attempt'], $username, $password);
if ( ! is_null($user)) // If the user credentials were authenticated by the closure, we will
{ // log the user into the application, which will store their user ID
static::login($user, $remember); // in the session for subsequent requests.
if (is_null($user)) return false;
return true; static::login($user, $remember);
}
return false; return true;
} }
/** /**
* Log a user into the application. * Log a user into the application.
* *
* An object representing the user or an integer user ID may be given to the method.
* If an object is given, the object must have an "id" property containing the user
* ID as it is stored in the database.
*
* <code> * <code>
* // Login a user by passing a user object
* Auth::login($user);
*
* // Login the user with an ID of 15 * // Login the user with an ID of 15
* Auth::login(15); * Auth::login(15);
* *
* // Login a user by passing a user object
* Auth::login($user);
*
* // Login a user and set a "remember me" cookie * // Login a user and set a "remember me" cookie
* Auth::login($user, true); * Auth::login($user, true);
* </code> * </code>
@ -167,11 +175,11 @@ public static function login($user, $remember = false)
if ($remember) static::remember($id); if ($remember) static::remember($id);
IoC::core('session')->put(Auth::user_key, $id); Session::put(Auth::user_key, $id);
} }
/** /**
* Set a cookie so that users are "remembered" and don't need to login. * Set a cookie so that the user is "remembered".
* *
* @param string $id * @param string $id
* @return void * @return void
@ -183,7 +191,7 @@ protected static function remember($id)
// This method assumes the "remember me" cookie should have the same // This method assumes the "remember me" cookie should have the same
// configuration as the session cookie. Since this cookie, like the // configuration as the session cookie. Since this cookie, like the
// session cookie, should be kept very secure, it's probably safe // session cookie, should be kept very secure, it's probably safe
// to assume the settings are the same. // to assume the settings are the same for this cookie.
$config = Config::get('session'); $config = Config::get('session');
extract($config, EXTR_SKIP); extract($config, EXTR_SKIP);
@ -194,14 +202,13 @@ protected static function remember($id)
/** /**
* Log the current user out of the application. * Log the current user out of the application.
* *
* The "logout" closure in the authenciation configuration file will be
* called. All authentication cookies will be deleted and the user ID
* will be removed from the session.
*
* @return void * @return void
*/ */
public static function logout() public static function logout()
{ {
// We will call the "logout" closure first, which gives the developer
// the chance to do any clean-up or before the user is logged out of
// the application. No action is taken by default.
call_user_func(Config::get('auth.logout'), static::user()); call_user_func(Config::get('auth.logout'), static::user());
static::$user = null; static::$user = null;
@ -213,11 +220,9 @@ public static function logout()
// When forgetting the cookie, we need to also pass in the path and // When forgetting the cookie, we need to also pass in the path and
// domain that would have been used when the cookie was originally // domain that would have been used when the cookie was originally
// set by the framework, otherwise it will not be deleted. // set by the framework, otherwise it will not be deleted.
Cookie::forget(Auth::user_key, $path, $domain, $secure);
Cookie::forget(Auth::remember_key, $path, $domain, $secure); Cookie::forget(Auth::remember_key, $path, $domain, $secure);
IoC::core('session')->forget(Auth::user_key); Session::forget(Auth::user_key);
} }
} }

View File

@ -1,4 +1,4 @@
<?php namespace Laravel; <?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
class Autoloader { class Autoloader {
@ -10,176 +10,143 @@ class Autoloader {
public static $mappings = array(); public static $mappings = array();
/** /**
* The PSR-0 compliant libraries registered with the loader. * All of the class aliases registered with the auto-loader.
* *
* @var array * @var array
*/ */
public static $libraries = array(); public static $aliases = array();
/** /**
* The paths to be searched by the loader. * The directories that use the PSR-0 naming convention.
* *
* @var array * @var array
*/ */
protected static $paths = array(MODEL_PATH, LIBRARY_PATH); public static $psr = array();
/** /**
* Load the file corresponding to a given class. * Load the file corresponding to a given class.
* *
* This method is registerd in the core bootstrap file as an SPL Autoloader. * This method is registerd in the bootstrap file as an SPL auto-loader.
* *
* @param string $class * @param string $class
* @return void * @return void
*/ */
public static function load($class) public static function load($class)
{ {
if (isset(Config::$items['application']['aliases'][$class])) // First, we will check to see if the class has been aliased. If it has,
// we will register the alias, which may cause the auto-loader to be
// called again for the "real" class name.
if (isset(static::$aliases[$class]))
{ {
return class_alias(Config::$items['application']['aliases'][$class], $class); class_alias(static::$aliases[$class], $class);
} }
if ( ! is_null($path = static::find($class))) // All classes in Laravel are staticly mapped. There is no crazy search
// routine that digs through directories. It's just a simple array of
// class to file path maps for ultra-fast file loading.
elseif (isset(static::$mappings[$class]))
{ {
require $path; require static::$mappings[$class];
}
}
/**
* Determine the file path associated with a given class name.
*
* @param string $class
* @return string
*/
protected static function find($class)
{
// First we will look for the class in the hard-coded class mappings, since
// this is the fastest way to resolve a class name to its associated file.
// This saves us from having to search through the file system manually.
if (isset(static::$mappings[$class]))
{
return static::$mappings[$class];
} }
// If the library has been registered as a PSR-0 compliant library, we will // If the class is namespaced to an existing bundle and the bundle has
// load the library according to the PSR-0 naming standards, which state that // not been started, we will start the bundle and attempt to load the
// namespaces and underscores indicate the directory hierarchy of the class. // class file again. If that fails, an error will be thrown by PHP.
if (in_array(static::library($class), static::$libraries)) //
// This allows bundle classes to be loaded by the auto-loader before
// their class mappings have actually been registered; however, it
// is up to the bundle developer to namespace their classes to
// match the name of their bundle.
elseif (($slash = strpos($class, '\\')) !== false)
{ {
return LIBRARY_PATH.str_replace(array('\\', '_'), '/', $class).EXT; $bundle = substr($class, 0, $slash);
}
// Next we will search through the common Laravel paths for the class file. // It's very important that we make sure the bundle has not been
// The Laravel libraries and models directories will be searched according // started here. If we don't, we'll end up in an infinite loop
// to the Laravel class naming standards. // attempting to load a bundle's class.
$file = strtolower(str_replace('\\', '/', $class)); if (Bundle::exists($bundle) and ! Bundle::started($bundle))
foreach (static::$paths as $path)
{
if (file_exists($path = $path.$file.EXT))
{ {
return $path; Bundle::start($bundle);
static::load($class);
} }
} }
// Since not all controllers will be resolved by the controller resolver, static::load_psr($class);
// we will do a quick check in the controller directory for the class.
// For instance, since base controllers would not be resolved by the
// controller class, we will need to resolve them here.
if (file_exists($path = static::controller($class)))
{
return $path;
}
} }
/** /**
* Extract the "library" name from the given class. * Attempt to resolve a class using the PSR-0 standard.
*
* The library name is essentially the namespace, or the string that preceeds
* the first PSR-0 separator. PSR-0 states that namespaces or undescores may
* be used to indicate the directory structure in which the file resides.
* *
* @param string $class * @param string $class
* @return string * @return void
*/ */
protected static function library($class) protected static function load_psr($class)
{ {
if (($separator = strpos($class, '\\')) !== false) // The PSR-0 standard indicates that class namespace slashes or
{ // underscores should be used to indicate the directory tree in
return substr($class, 0, $separator); // which the class resides.
} $file = str_replace(array('\\', '_'), '/', $class);
elseif (($separator = strpos($class, '_')) !== false)
{
return substr($class, 0, $separator);
}
}
/** // Once we have formatted the class name, we will simply spin
* Translate a given controller class name into the corresponding file name. // through the registered PSR-0 directories and attempt to
* // locate and load the class into the script.
* The controller suffix will be removed, and the underscores will be translated foreach (static::$psr as $directory)
* into directory slashes. Of course, the entire class name will be converted to {
* lower case as well. if (file_exists($path = $directory.strtolower($file).EXT))
* {
* <code> return require $path;
* // Returns "user/profile"... }
* $file = static::controller('User_Profile_Controller'); elseif (file_exists($path = $directory.$file.EXT))
* </code> {
* return require $path;
* @param string $class }
* @return string }
*/
protected static function controller($class)
{
$controller = str_replace(array('_', '_Controller'), array('/', ''), $class);
return CONTROLLER_PATH.strtolower($controller).EXT;
} }
/** /**
* Register an array of class to path mappings. * Register an array of class to path mappings.
* *
* The mappings will be used to resolve file paths from class names when
* a class is lazy loaded through the Autoloader, providing a faster way
* of resolving file paths than the typical file_exists method.
*
* <code> * <code>
* // Register a class mapping with the Autoloader * // Register a class mapping with the Autoloader
* Autoloader::maps(array('User' => MODEL_PATH.'user'.EXT)); * Autoloader::map(array('User' => APP_PATH.'models/user.php'));
* </code> * </code>
* *
* @param array $mappings * @param array $mappings
* @return void * @return void
*/ */
public static function maps($mappings) public static function map($mappings)
{ {
foreach ($mappings as $class => $path) static::$mappings = array_merge(static::$mappings, $mappings);
{
static::$mappings[$class] = $path;
}
} }
/** /**
* Register PSR-0 libraries with the Autoloader. * Register a class alias with the auto-loader.
* *
* The library names given to this method should match directories within * @param string $class
* the application libraries directory. This method provides an easy way * @param string $alias
* to indicate that some libraries should be loaded using the PSR-0
* naming conventions instead of the Laravel conventions.
*
* <code>
* // Register the "Assetic" library with the Autoloader
* Autoloader::libraries('Assetic');
*
* // Register several libraries with the Autoloader
* Autoloader::libraries(array('Assetic', 'Twig'));
* </code>
*
* @param array $libraries
* @return void * @return void
*/ */
public static function libraries($libraries) public static function alias($class, $alias)
{ {
static::$libraries = array_merge(static::$libraries, (array) $libraries); static::$aliases[$alias] = $class;
}
/**
* Register directories to be searched as a PSR-0 library.
*
* @param string|array $directory
* @return void
*/
public static function psr($directory)
{
$directories = array_map(function($directory)
{
return rtrim($directory, '/').'/';
}, (array) $directory);
static::$psr = array_unique(array_merge(static::$psr, $directories));
} }
} }

View File

@ -1,49 +0,0 @@
<?php namespace Laravel;
class Benchmark {
/**
* All of the benchmark starting times.
*
* @var array
*/
protected static $marks = array();
/**
* Start a benchmark starting time.
*
* @param string $name
* @return void
*/
public static function start($name)
{
static::$marks[$name] = microtime(true);
}
/**
* Get the elapsed time in milliseconds since starting a benchmark.
*
* @param string $name
* @return float
*/
public static function check($name)
{
if (array_key_exists($name, static::$marks))
{
return (float) number_format((microtime(true) - static::$marks[$name]) * 1000, 2);
}
return (float) 0.0;
}
/**
* Get the total memory usage in megabytes.
*
* @return float
*/
public static function memory()
{
return (float) number_format(memory_get_usage() / 1024 / 1024, 2);
}
}

View File

@ -1,4 +1,4 @@
<?php namespace Laravel; <?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
class Blade { class Blade {
@ -49,10 +49,6 @@ public static function compile_string($value)
/** /**
* Rewrites Blade echo statements into PHP echo statements. * Rewrites Blade echo statements into PHP echo statements.
* *
* Blade echo statements are simply PHP statement enclosed within double curly
* braces. For example, {{$content}} will simply echo out the content variable
* to the output buffer.
*
* @param string $value * @param string $value
* @return string * @return string
*/ */
@ -64,10 +60,6 @@ protected static function compile_echos($value)
/** /**
* Rewrites Blade structure openings into PHP structure openings. * Rewrites Blade structure openings into PHP structure openings.
* *
* By "structures", we mean the if, elseif, foreach, for, and while statements.
* All of these structures essentially have the same format, and can be lumped
* into a single regular expression.
*
* @param string $value * @param string $value
* @return string * @return string
*/ */

294
laravel/bundle.php Normal file
View File

@ -0,0 +1,294 @@
<?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
class Bundle {
/**
* All of the application's bundles.
*
* @var array
*/
protected static $bundles;
/**
* A cache of the parsed bundle elements.
*
* @var array
*/
protected static $elements = array();
/**
* All of the bundles that have been started.
*
* @var array
*/
protected static $started = array();
/**
* Load a bundle by running it's start-up script.
*
* If the bundle has already been started, no action will be taken.
*
* @param string $bundle
* @return void
*/
public static function start($bundle)
{
if (static::started($bundle)) return;
if ($bundle !== DEFAULT_BUNDLE and ! static::exists($bundle))
{
throw new \Exception("Bundle [$bundle] has not been installed.");
}
// 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.
if (file_exists($path = static::path($bundle).'bundle'.EXT))
{
require $path;
}
// Each bundle may also have a "routes" file which is responsible for
// registering the bundle's routes. This is kept separate from the
// start script for reverse routing efficiency purposes.
static::routes($bundle);
static::$started[] = strtolower($bundle);
}
/**
* Load the "routes" file for a given bundle.
*
* @param string $bundle
* @return void
*/
public static function routes($bundle)
{
if (static::started($bundle)) return;
if (file_exists($path = static::path($bundle).'routes'.EXT))
{
require $path;
}
}
/**
* Determine if the given bundle is "routable".
*
* A bundle is considered routable if it has a controller directory or a routes file.
*
* @param string $bundle
* @return bool
*/
public static function routable($bundle)
{
$path = static::path($bundle);
return is_dir($path.'controllers/') or file_exists($path.'routes'.EXT);
}
/**
* Deteremine if a bundle exists within the bundles directory.
*
* @param string $bundle
* @return bool
*/
public static function exists($bundle)
{
return in_array(strtolower($bundle), static::all());
}
/**
* Determine if a given bundle has been started for the request.
*
* @param string $bundle
* @return void
*/
public static function started($bundle)
{
return in_array(strtolower($bundle), static::$started);
}
/**
* Get the identifier prefix for the bundle.
*
* @param string $bundle
* @return string
*/
public static function prefix($bundle)
{
return ($bundle !== DEFAULT_BUNDLE) ? "{$bundle}::" : '';
}
/**
* Get the class prefix for a given bundle.
*
* @param string $bundle
* @return string
*/
public static function class_prefix($bundle)
{
return ($bundle !== DEFAULT_BUNDLE) ? Str::classify($bundle).'_' : '';
}
/**
* Return the root bundle path for a given bundle.
*
* <code>
* // Returns the bundle path for the "admin" bundle
* $path = Bundle::path('admin');
*
* // Returns the APP_PATH constant as the default bundle
* $path = Bundle::path('application');
* </code>
*
* @param string $bundle
* @return string
*/
public static function path($bundle)
{
return ($bundle != DEFAULT_BUNDLE) ? BUNDLE_PATH.strtolower($bundle).DS : APP_PATH;
}
/**
* Return the root asset path for the given bundle.
*
* @param string $bundle
* @return string
*/
public static function assets($bundle)
{
return ($bundle != DEFAULT_BUNDLE) ? PUBLIC_PATH."bundles/{$bundle}/" : PUBLIC_PATH;
}
/**
* Get the bundle name from a given identifier.
*
* <code>
* // Returns "admin" as the bundle name for the identifier
* $bundle = Bundle::name('admin::home.index');
* </code>
*
* @param string $identifier
* @return string
*/
public static function name($identifier)
{
list($bundle, $element) = static::parse($identifier);
return $bundle;
}
/**
* Get the element name from a given identifier.
*
* <code>
* // Returns "home.index" as the element name for the identifier
* $bundle = Bundle::bundle('admin::home.index');
* </code>
*
* @param string $identifier
* @return string
*/
public static function element($identifier)
{
list($bundle, $element) = static::parse($identifier);
return $element;
}
/**
* Reconstruct an identifier from a given bundle and element.
*
* <code>
* // Returns "admin::home.index"
* $identifier = Bundle::identifier('admin', 'home.index');
*
* // Returns "home.index"
* $identifier = Bundle::identifier('application', 'home.index');
* </code>
*
* @param string $bundle
* @param string $element
* @return string
*/
public static function identifier($bundle, $element)
{
return (is_null($bundle) or $bundle == DEFAULT_BUNDLE) ? $element : $bundle.'::'.$element;
}
/**
* Return the bundle name if it exists, else return the default bundle.
*
* @param string $bundle
* @return string
*/
public static function resolve($bundle)
{
return (static::exists($bundle)) ? $bundle : DEFAULT_BUNDLE;
}
/**
* Parse a element identifier and return the bundle name and element.
*
* <code>
* // Returns array(null, 'admin.user')
* $element = Bundle::parse('admin.user');
*
* // Parses "admin::user" and returns array('admin', 'user')
* $element = Bundle::parse('admin::user');
* </code>
*
* @param string $identifier
* @return array
*/
public static function parse($identifier)
{
// The parsed elements are cached so we don't have to reparse them on each
// subsequent request for the parsed element. So, if we've already parsed
// the given element, we'll just return the cached copy.
if (isset(static::$elements[$identifier]))
{
return static::$elements[$identifier];
}
if (strpos($identifier, '::') !== false)
{
$element = explode('::', strtolower($identifier));
}
// If no bundle is in the identifier, we will insert the default bundle
// since classes like Config and Lang organize their items by bundle.
// The "application" folder essentially behaves as a bundle.
else
{
$element = array(DEFAULT_BUNDLE, strtolower($identifier));
}
return static::$elements[$identifier] = $element;
}
/**
* Detect all of the existing bundles in the application.
*
* The names of the bundles are cached so this operation will be only be
* performed once and then the same array will be returned on each later
* request for the bundle names.
*
* @return array
*/
public static function all()
{
if (is_array(static::$bundles)) return static::$bundles;
$bundles = array();
foreach (array_filter(glob(BUNDLE_PATH.'*'), 'is_dir') as $bundle)
{
$bundles[] = basename($bundle);
}
return static::$bundles = $bundles;
}
}

View File

@ -1,23 +1,18 @@
<?php namespace Laravel\Cache; <?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
use Laravel\Redis; class Cache {
use Laravel\Config;
use Laravel\Memcached;
class Manager {
/** /**
* All of the active cache drivers. * All of the active cache drivers.
* *
* @var array * @var array
*/ */
protected static $drivers = array(); public static $drivers = array();
/** /**
* Get a cache driver instance. * Get a cache driver instance.
* *
* If no driver name is specified, the default cache driver will * If no driver name is specified, the default will be returned.
* be returned as defined in the cache configuration file.
* *
* <code> * <code>
* // Get the default cache driver instance * // Get the default cache driver instance
@ -34,9 +29,9 @@ public static function driver($driver = null)
{ {
if (is_null($driver)) $driver = Config::get('cache.driver'); if (is_null($driver)) $driver = Config::get('cache.driver');
if ( ! array_key_exists($driver, static::$drivers)) if ( ! isset(static::$drivers[$driver]))
{ {
return static::$drivers[$driver] = static::factory($driver); static::$drivers[$driver] = static::factory($driver);
} }
return static::$drivers[$driver]; return static::$drivers[$driver];
@ -53,33 +48,33 @@ protected static function factory($driver)
switch ($driver) switch ($driver)
{ {
case 'apc': case 'apc':
return new Drivers\APC(Config::get('cache.key')); return new Cache\Drivers\APC(Config::get('cache.key'));
case 'file': case 'file':
return new Drivers\File(CACHE_PATH); return new Cache\Drivers\File(CACHE_PATH);
case 'memcached': case 'memcached':
return new Drivers\Memcached(Memcached::instance(), Config::get('cache.key')); return new Cache\Drivers\Memcached(Memcached::connection(), Config::get('cache.key'));
case 'redis': case 'redis':
return new Drivers\Redis(Redis::db()); return new Cache\Drivers\Redis(Redis::db());
case 'database':
return new Cache\Drivers\Database(Config::get('cache.key'));
default: default:
throw new \DomainException("Cache driver {$driver} is not supported."); throw new \Exception("Cache driver {$driver} is not supported.");
} }
} }
/** /**
* Pass all other methods to the default cache driver. * Magic Method for calling the methods on the default cache driver.
*
* Passing method calls to the driver instance provides a convenient API
* for the developer when always using the default cache driver.
* *
* <code> * <code>
* // Call the "get" method on the default driver * // Call the "get" method on the default cache driver
* $name = Cache::get('name'); * $name = Cache::get('name');
* *
* // Call the "put" method on the default driver * // Call the "put" method on the default cache driver
* Cache::put('name', 'Taylor', 15); * Cache::put('name', 'Taylor', 15);
* </code> * </code>
*/ */

113
laravel/cache/drivers/database.php vendored Normal file
View File

@ -0,0 +1,113 @@
<?php namespace Laravel\Cache\Drivers;
use Laravel\Config;
use Laravel\Database as DB;
use Laravel\Database\Connection;
class Database extends Driver {
/**
* The cache key from the cache configuration file.
*
* @var string
*/
protected $key;
/**
* Create a new database cache driver instance.
*
* @param string $key
* @return void
*/
public function __construct($key)
{
$this->key = $key;
}
/**
* Determine if an item exists in the cache.
*
* @param string $key
* @return bool
*/
public function has($key)
{
return ( ! is_null($this->get($key)));
}
/**
* Retrieve an item from the cache driver.
*
* @param string $key
* @return mixed
*/
protected function retrieve($key)
{
$cache = $this->table()->where('key', '=', $this->key.$key)->first();
if ( ! is_null($cache))
{
if (time() >= $cache->expiration) return $this->forget($key);
return unserialize($cache->value);
}
}
/**
* Write an item to the cache for a given number of minutes.
*
* <code>
* // Put an item in the cache for 15 minutes
* Cache::put('name', 'Taylor', 15);
* </code>
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$key = $this->key.$key;
$value = serialize($value);
$expiration = $this->expiration($minutes);
// To update the value, we'll first attempt an insert against the
// database and if we catch an exception, we'll assume that the
// primary key already exists in the table and update.
try
{
$this->table()->insert(compact('key', 'value', 'expiration'));
}
catch (\Exception $e)
{
$this->table()->where('key', '=', $key)->update(compact('value', 'expiration'));
}
}
/**
* Delete an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
$this->table()->where('key', '=', $this->key.$key)->delete();
}
/**
* Get a query builder for the database table.
*
* @return Query
*/
protected function table()
{
$connection = DB::connection(Config::get('cache.database.connection'));
return $connection->table(Config::get('cache.database.table'));
}
}

View File

@ -28,9 +28,7 @@ abstract public function has($key);
*/ */
public function get($key, $default = null) public function get($key, $default = null)
{ {
if ( ! is_null($item = $this->retrieve($key))) return $item; return ( ! is_null($item = $this->retrieve($key))) ? $item : value($default);
return ($default instanceof Closure) ? call_user_func($default) : $default;
} }
/** /**
@ -57,8 +55,7 @@ abstract protected function retrieve($key);
abstract public function put($key, $value, $minutes); abstract public function put($key, $value, $minutes);
/** /**
* Get an item from the cache. If the item doesn't exist in the * Get an item from the cache, or cache and return the default value.
* cache, store the default value in the cache and return it.
* *
* <code> * <code>
* // Get an item from the cache, or cache a value for 15 minutes * // Get an item from the cache, or cache a value for 15 minutes
@ -77,9 +74,7 @@ public function remember($key, $default, $minutes)
{ {
if ( ! is_null($item = $this->get($key, null))) return $item; if ( ! is_null($item = $this->get($key, null))) return $item;
$default = ($default instanceof Closure) ? call_user_func($default) : $default; $this->put($key, value($default), $minutes);
$this->put($key, $default, $minutes);
return $default; return $default;
} }
@ -92,4 +87,15 @@ public function remember($key, $default, $minutes)
*/ */
abstract public function forget($key); abstract public function forget($key);
/**
* Get the expiration time as a UNIX timestamp.
*
* @param int $minutes
* @return int
*/
protected function expiration($minutes)
{
return time() + ($minutes * 60);
}
} }

View File

@ -68,7 +68,7 @@ protected function retrieve($key)
*/ */
public function put($key, $value, $minutes) public function put($key, $value, $minutes)
{ {
$value = (time() + ($minutes * 60)).serialize($value); $value = $this->expiration($minutes).serialize($value);
file_put_contents($this->path.$key, $value, LOCK_EX); file_put_contents($this->path.$key, $value, LOCK_EX);
} }
@ -81,10 +81,7 @@ public function put($key, $value, $minutes)
*/ */
public function forget($key) public function forget($key)
{ {
if (file_exists($this->path.$key)) if (file_exists($this->path.$key)) @unlink($this->path.$key);
{
@unlink($this->path.$key);
}
} }
} }

62
laravel/cli/artisan.php Normal file
View File

@ -0,0 +1,62 @@
<?php namespace Laravel\CLI; defined('APP_PATH') or die('No direct script access.');
use Laravel\IoC;
use Laravel\Bundle;
use Laravel\Database as DB;
/**
* Fire up the default bundle. This will ensure any dependencies that
* need to be registered in the IoC container are registered and that
* the auto-loader mappings are registered.
*/
Bundle::start(DEFAULT_BUNDLE);
/**
* We will register all of the Laravel provided tasks inside the IoC
* container so they can be resolved by the task class. This allows
* us to seamlessly add tasks to the CLI so that the Task class
* doesn't have to worry about how to resolve core tasks.
*/
/**
* The bundle task is responsible for the installation of bundles
* and their dependencies. It utilizes the bundles API to get the
* meta-data for the available bundles.
*/
IoC::register('task: bundle', function()
{
return new Tasks\Bundle\Bundler;
});
/**
* The migrate task is responsible for running database migrations
* as well as migration rollbacks. We will also create an instance
* of the migration resolver and database classes, which are used
* to perform various support functions for the migrator.
*/
IoC::register('task: migrate', function()
{
$database = new Tasks\Migrate\Database;
$resolver = new Tasks\Migrate\Resolver($database);
return new Tasks\Migrate\Migrator($resolver, $database);
});
/**
* We will wrap the command execution in a try / catch block and
* simply write out any exception messages we receive to the CLI
* for the developer. Note that this only writes out messages
* for the CLI exceptions. All others will be not be caught
* and will be totally dumped out to the CLI.
*/
try
{
Command::run(array_slice($_SERVER['argv'], 1));
}
catch (\Exception $e)
{
echo $e->getMessage();
}
echo PHP_EOL;

109
laravel/cli/command.php Normal file
View File

@ -0,0 +1,109 @@
<?php namespace Laravel\CLI;
use Laravel\IoC;
use Laravel\Str;
use Laravel\Bundle;
class Command {
/**
* Run a CLI task with the given arguments.
*
* @param array $arguments
* @return void
*/
public static function run($arguments = array())
{
if ( ! isset($arguments[0]))
{
throw new \Exception("Whoops! You forgot to provide the task name.");
}
list($bundle, $task, $method) = static::parse($arguments[0]);
// If the task exists within a bundle, we will start the bundle so that
// any dependencies can be registered in the application IoC container.
// If the task is registered in the container, it will be resolved
// via the container instead of by this class.
if (Bundle::exists($bundle)) Bundle::start($bundle);
if (is_null($task = static::resolve($bundle, $task)))
{
throw new \Exception("Sorry, I can't find that task.");
}
$task->$method(array_slice($arguments, 1));
}
/**
* Parse the task name to extract the bundle, task, and method.
*
* @param string $task
* @return array
*/
protected static function parse($task)
{
list($bundle, $task) = Bundle::parse($task);
// Extract the task method from the task string. Methods are called
// on tasks by separating the task and method with a single colon.
// If no task is specified, "run" is used as the default method.
if (str_contains($task, ':'))
{
list($task, $method) = explode(':', $task);
}
else
{
$method = 'run';
}
return array($bundle, $task, $method);
}
/**
* Resolve an instance of the given task name.
*
* @param string $bundle
* @param string $task
* @return object
*/
public static function resolve($bundle, $task)
{
$identifier = Bundle::identifier($bundle, $task);
// First we'll check to see if the task has been registered in
// the application IoC container. This allows dependencies to
// be injected into tasks for more testability.
if (IoC::registered("task: {$identifier}"))
{
return IoC::resolve("task: {$identifier}");
}
// If the task file exists, we'll format the bundle and task
// name into a task class name and resolve an instance of
// the so that the requested method may be executed.
if (file_exists($path = Bundle::path($bundle).'tasks/'.$task.EXT))
{
require $path;
$task = static::format($bundle, $task);
return new $task;
}
}
/**
* Format a bundle and task into a task class name.
*
* @param string $bundle
* @param string $task
* @return string
*/
protected static function format($bundle, $task)
{
$prefix = Bundle::class_prefix($bundle);
return '\\'.$prefix.Str::clasify($task).'_Task';
}
}

View File

@ -0,0 +1,127 @@
<?php namespace Laravel\CLI\Tasks\Bundle; defined('APP_PATH') or die('No direct script access.');
use Laravel\IoC;
use Laravel\Bundle;
use Laravel\CLI\Tasks\Task;
IoC::singleton('bundle.repository', function()
{
return new Repository;
});
IoC::singleton('bundle.publisher', function()
{
return new Publisher;
});
IoC::singleton('bundle.provider: github', function()
{
return new Providers\Github;
});
class Bundler extends Task {
/**
* Install the given bundles into the application.
*
* @param array $bundles
* @return void
*/
public function install($bundles)
{
$publisher = IoC::resolve('bundle.publisher');
foreach ($this->get($bundles) as $bundle)
{
if (is_dir(BUNDLE_PATH.$bundle['name']))
{
echo "Bundle {$bundle['name']} is already installed.";
continue;
}
// Once we have the bundle information, we can resolve an instance
// of a provider and install the bundle into the application and
// all of its registered dependencies as well.
//
// Each bundle provider implements the Provider interface and
// is repsonsible for retrieving the bundle source from its
// hosting party and installing it into the application.
$provider = "bundle.provider: {$bundle['provider']}";
IoC::resolve($provider)->install($bundle);
$publisher->publish($bundle);
}
}
/**
* Publish bundle assets to the public directory.
*
* @param array $bundles
* @return void
*/
public function publish($bundles)
{
// If no bundles are passed to the command, we'll just gather all
// of the installed bundle names and publish the assets for each
// for each one of the bundles to the public directory.
if (count($bundles) == 0) $bundles = Bundle::all();
$publisher = IoC::resolve('bundle.publisher');
foreach ($bundles as $bundle)
{
$publisher->publish($bundle);
}
}
/**
* Gather all of the bundles from the bundle repository.
*
* @param array $bundles
* @return array
*/
protected function get($bundles)
{
$responses = array();
$repository = IoC::resolve('bundle.repository');
// This method is primarily responsible for gathering the data
// for all bundles that need to be installed. This allows us
// to verify the existence of the bundle before even getting
// started on the actual installation process.
foreach ($bundles as $bundle)
{
// 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. We'll verify that the bundle
// exists and the API is responding for each bundle.
$response = $repository->get($bundle);
if ( ! $response)
{
throw new \Exception("The bundle API is not responding.");
}
if ($response['status'] == 'not-found')
{
throw new \Exception("There is not a bundle named [$bundle].");
}
// 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.
$bundle = $response['bundle'];
$responses[] = $bundle;
$responses = array_merge($responses, $this->get($bundle['dependencies']));
}
return $responses;
}
}

View File

@ -0,0 +1,27 @@
<?php namespace Laravel\CLI\Tasks\Bundle\Providers;
class Github implements Provider {
/**
* Install the given bundle into the application.
*
* @param string $bundle
* @return void
*/
public function install($bundle)
{
$repository = "git://github.com/{$bundle['location']}.git";
// We need to just extract the basename of the bundle path when
// adding the submodule. Of course, we can't add a submodule to
// a location outside of the Git repository, so we don't need
// the full bundle path. We'll just take the basename in case
// the bundle directory has been renamed.
$path = basename(BUNDLE_PATH).'/';
passthru('git submodule add '.$repository.' '.$path.$bundle['name']);
passthru('git submodule update');
}
}

View File

@ -0,0 +1,13 @@
<?php namespace Laravel\CLI\Tasks\Bundle\Providers;
interface Provider {
/**
* Install the given bundle into the application.
*
* @param string $bundle
* @return void
*/
public function install($bundle);
}

View File

@ -0,0 +1,90 @@
<?php namespace Laravel\CLI\Tasks\Bundle;
use Laravel\Bundle;
use FilesystemIterator;
class Publisher {
/**
* Publish a bundle's assets to the public directory.
*
* @param string $bundle
* @return void
*/
public function publish($bundle)
{
$this->move($bundle, $this->from($bundle), $this->to($bundle));
echo "Assets published for bundle [$bundle].".PHP_EOL;
}
/**
* Copy the contents of a bundle's assets to the public folder.
*
* @param string $bundle
* @param string $source
* @param string $destination
* @return void
*/
protected function move($bundle, $source, $destination)
{
if ( ! is_dir($source)) return;
// First we need to create the destination directory if it doesn't
// already exists. This directory hosts all of the assets we copy
// from the installed bundle's source directory.
if ( ! is_dir($destination))
{
mkdir($destination);
}
$items = new FilesystemIterator($source, FilesystemIterator::SKIP_DOTS);
foreach ($items as $item)
{
// If the file system item is a directory, we will recurse the
// function, passing in the item directory. To get the proper
// destination path, we'll replace the root bundle asset
// directory with the root public asset directory.
if ($item->isDir())
{
$path = $item->getRealPath();
$recurse = str_replace($this->from($bundle), $this->to($bundle), $path);
$this->move($bundle, $path, $recurse);
}
// If the file system item is an actual file, we can copy the
// file from the bundle asset directory to the public asset
// directory. The "copy" method will overwrite any existing
// files with the same name.
else
{
copy($item->getRealPath(), $destination.DS.$item->getBasename());
}
}
}
/**
* Get the "to" location of the bundle's assets.
*
* @param string $bundle
* @return string
*/
protected function to($bundle)
{
return PUBLIC_PATH.'bundles'.DS.$bundle.DS;
}
/**
* Get the "from" location of the bundle's assets.
*
* @param string $bundle
* @return string
*/
protected function from($bundle)
{
return Bundle::path($bundle).'public';
}
}

View File

@ -0,0 +1,29 @@
<?php namespace Laravel\CLI\Tasks\Bundle;
class Repository {
/**
* The root of the Laravel bundle API.
*
* @var string
*/
protected $api = 'http://bundles.laravel.com/api/';
/**
* Get the decoded JSON information for a bundle.
*
* @param string|int $bundle
* @return array
*/
public function get($bundle)
{
// The Bundle API will return a JSON string that we can decode and
// pass back to the consumer. The decoded array will contain info
// regarding the bundle's provider and location, as well as all
// of the bundle's dependencies.
$bundle = @file_get_contents($this->api.$bundle);
return json_decode($bundle, true);
}
}

View File

@ -0,0 +1,83 @@
<?php namespace Laravel\CLI\Tasks\Migrate;
use Laravel\Database as DB;
class Database {
/**
* Log a migration in the migration table.
*
* @param string $bundle
* @param string $name
* @param int $batch
* @return void
*/
public function log($bundle, $name, $batch)
{
$this->table()->insert(compact('bundle', 'name', 'batch'));
}
/**
* Delete a row from the migration table.
*
* @param string $bundle
* @param string $name
* @return void
*/
public function delete($bundle, $name)
{
$this->table()->where_bundle_and_name($bundle, $name)->delete();
}
/**
* Return an array of the last batch of migrations.
*
* @return array
*/
public function last()
{
$table = $this->table();
// First we need to grab the last batch ID from the migration table,
// as this will allow us to grab the lastest batch of migrations
// that need to be run for a rollback command.
$id = $this->batch();
// Once we have the batch ID, we will pull all of the rows for that
// batch. Then we can feed the results into the resolve method to
// get the migration instances for the command.
return $table->where_batch($id)->order_by('name', 'desc')->get();
}
/**
* Get all of the migrations that have run for a bundle.
*
* @param string $bundle
* @return array
*/
public function ran($bundle)
{
return $this->table()->where_bundle($bundle)->lists('name');
}
/**
* Get the maximum batch ID from the migration table.
*
* @return int
*/
public function batch()
{
return $this->table()->max('batch');
}
/**
* Get a database query instance for the migration table.
*
* @return Query
*/
protected function table()
{
return DB::connection()->table('laravel_migrations');
}
}

View File

@ -0,0 +1,235 @@
<?php namespace Laravel\CLI\Tasks\Migrate;
use Laravel\Str;
use Laravel\File;
use Laravel\Bundle;
use Laravel\CLI\Tasks\Task;
use Laravel\Database\Schema;
class Migrator extends Task {
/**
* The migration resolver instance.
*
* @var Resolver
*/
protected $resolver;
/**
* The migration database instance.
*
* @var Database
*/
protected $database;
/**
* Create a new instance of the Migrator CLI task.
*
* @param Resolver $resolver
* @param Database $database
* @return void
*/
public function __construct(Resolver $resolver, Database $database)
{
$this->resolver = $resolver;
$this->database = $database;
}
/**
* Run a database migration command.
*
* @param array $arguments
* @return void
*/
public function run($arguments = array())
{
// If no arguments were passed to the task, we will just migrate
// to the latest version across all bundles. Otherwise, we will
// parse the arguments to determine the bundle for which the
// database migrations should be run.
if (count($arguments) == 0)
{
$this->migrate();
}
else
{
$this->migrate(array_get($arguments, 0));
}
}
/**
* Run the outstanding migrations for a given bundle.
*
* @param string $bundle
* @param int $version
* @return void
*/
public function migrate($bundle = null, $version = null)
{
$migrations = $this->resolver->outstanding($bundle);
if (count($migrations) == 0)
{
echo "No outstanding migrations.";
return;
}
// We need to grab the latest batch ID and increment it
// by one. This allows us to group the migrations such
// that we can easily determine which migrations need
// to be rolled back for a given command.
$batch = $this->database->batch() + 1;
foreach ($migrations as $migration)
{
$migration['migration']->up();
echo 'Migrated: '.$this->display($migration).PHP_EOL;
// After running a migration, we log its execution in the
// migration table so that we can easily determine which
// migrations we will need to reverse on a rollback.
$this->database->log($migration['bundle'], $migration['name'], $batch);
}
}
/**
* Rollback the latest migration command.
*
* @param array $arguments
* @return bool
*/
public function rollback($arguments = array())
{
$migrations = $this->resolver->last();
if (count($migrations) == 0)
{
echo "Nothing to rollback.";
return false;
}
// The "last" method on the resolver returns an array of migrations,
// 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)
{
$migration['migration']->down();
echo 'Rolled back: '.$this->display($migration).PHP_EOL;
// By only removing the migration after it has successfully rolled back,
// we can re-run the rollback command in the event of any errors with
// the migration. When we re-run, only the migrations that have not
// been rolled-back for the batch will still be in the database.
$this->database->delete($migration['bundle'], $migration['name']);
}
return true;
}
/**
* Rollback all of the executed migrations.
*
* @param array $arguments
* @return void
*/
public function reset($arguments = array())
{
while ($this->rollback()) {};
}
/**
* Install the database tables used by the migration system.
*
* @return void
*/
public function install()
{
Schema::table('laravel_migrations', function($table)
{
$table->create();
// Migrations can be run for a specific bundle, so we'll use
// the bundle name and string migration name as an unique ID
// for the migrations, allowing us to easily identify which
// migrations have been run for each bundle.
$table->string('bundle');
$table->string('name');
// When running a migration command, we will store a batch
// ID with each of the rows on the table. This will allow
// us to grab all of the migrations that were run for the
// last command when performing rollbacks.
$table->integer('batch');
$table->primary(array('bundle', 'name'));
});
echo "Migration table created successfully.";
}
/**
* Generate a new migration file.
*
* @param array $arguments
* @return void
*/
public function make($arguments = array())
{
if (count($arguments) == 0)
{
throw new \Exception("I need to know what to name the migration.");
}
list($bundle, $migration) = Bundle::parse($arguments[0]);
// The migration path is prefixed with the UNIX timestamp, which
// is a better way of ordering migrations than a simple integer
// incrementation, since developers may start working on the
// next migration at the same time unknowingly.
$date = date('Y_m_d').'_'.time();
$path = Bundle::path($bundle).'migrations/'.$date.'_'.$migration.EXT;
File::put($path, $this->stub($bundle, $migration));
echo "Great! New migration created!";
}
/**
* Get the stub migration with the proper class name.
*
* @param string $bundle
* @param string $migration
* @return string
*/
protected function stub($bundle, $migration)
{
$stub = File::get(SYS_PATH.'cli/tasks/migrate/stub'.EXT);
// The class name is formatted simialrly to tasks and controllers,
// where the bundle name is prefixed to the class if it is not in
// the default bundle. However, unlike tasks, there is nothing
// appended to the class name since they're already unique.
$class = Bundle::class_prefix($bundle).Str::classify($migration);
return str_replace('{{class}}', $class, $stub);
}
/**
* Get the migration bundle and name for display.
*
* @param array $migration
* @return string
*/
protected function display($migration)
{
return $migration['bundle'].'/'.$migration['name'];
}
}

View File

@ -0,0 +1,159 @@
<?php namespace Laravel\CLI\Tasks\Migrate;
use Laravel\Bundle;
class Resolver {
/**
* The migration database instance.
*
* @var Database
*/
protected $database;
/**
* Create a new instance of the migration resolver.
*
* @param Database $datbase
* @return void
*/
public function __construct(Database $database)
{
$this->database = $database;
}
/**
* Resolve all of the outstanding migrations for a bundle.
*
* @param string $bundle
* @return array
*/
public function outstanding($bundle = null)
{
$migrations = array();
// If no bundle was given to the command, we'll grab every bundle for
// the application, including the "application" bundle, which is not
// returned by "all" method on the Bundle class.
if (is_null($bundle))
{
$bundles = array_merge(Bundle::all(), array('application'));
}
else
{
$bundles = array($bundle);
}
foreach ($bundles as $bundle)
{
// First we need to grab all of the migrations that have already
// run for this bundle, as well as all of the migration files
// for the bundle. Once we have these, we can determine which
// migrations are still outstanding.
$ran = $this->database->ran($bundle);
$files = $this->migrations($bundle);
// To find outstanding migrations, we will simply iterate over
// the migration files and add the files that do not exist in
// the array of ran migrations to the outstanding array.
foreach ($files as $key => $name)
{
if ( ! in_array($name, $ran))
{
$migrations[] = compact('bundle', 'name');
}
}
}
return $this->resolve($migrations);
}
/**
* Resolve an array of the last batch of migrations.
*
* @return array
*/
public function last()
{
return $this->resolve($this->database->last());
}
/**
* Resolve an array of migration instances.
*
* @param array $migrations
* @return array
*/
protected function resolve($migrations)
{
$instances = array();
foreach ($migrations as $migration)
{
$migration = (array) $migration;
// The migration array contains the bundle name, so we will get the
// path to the bundle's migrations and resolve an instance of the
// migration using the name.
$bundle = $migration['bundle'];
$path = Bundle::path($bundle).'migrations/';
// Migrations are not resolved through the auto-loader, so we will
// manually instantiate the migration class instances for each of
// the migration names we're given.
$name = $migration['name'];
require_once $path.$name.EXT;
// Since the migration name will begin with the numeric ID, we'll
// slice off the ID so we are left with the migration class name.
// The IDs are for sorting when resolving outstanding migrations.
//
// Migrations that exist within bundles other than the default
// will be prefixed with the bundle name to avoid any possible
// naming collisions with other bundle's migrations.
$prefix = Bundle::class_prefix($bundle);
$class = $prefix.substr($name, 22);
$migration = new $class;
// When adding to the array of instances, we will actually
// add the migration instance, the bundle, and the name.
// This allows the migrator to log the bundle and name
// when the migration is executed.
$instances[] = compact('bundle', 'name', 'migration');
}
return $instances;
}
/**
* Grab all of the migration filenames for a bundle.
*
* @param string $bundle
* @return array
*/
protected function migrations($bundle)
{
$files = glob(Bundle::path($bundle).'migrations/*_*'.EXT);
// Once we have the array of files in the migration directory,
// we'll take the basename of the file and remove the PHP file
// extension, which isn't needed.
foreach ($files as &$file)
{
$file = str_replace(EXT, '', basename($file));
}
// We'll also sort the files so that the earlier migrations
// will be at the front of the array and will be resolved
// first by this class' resolve method.
sort($files);
return $files;
}
}

View File

@ -0,0 +1,25 @@
<?php
class {{class}} {
/**
* Make changes to the database.
*
* @return void
*/
public function up()
{
//
}
/**
* Revert the changes to the database.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,3 @@
<?php namespace Laravel\CLI\Tasks;
abstract class Task {}

View File

@ -1,22 +1,24 @@
<?php namespace Laravel; use Closure; <?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
use Closure;
class Config { class Config {
/** /**
* All of the loaded configuration items. * All of the loaded configuration items.
* *
* The configuration arrays are keyed by their owning file name. * The configuration arrays are keyed by their owning bundle and file.
* *
* @var array * @var array
*/ */
public static $items = array(); public static $items = array();
/** /**
* The paths to the configuration files. * A cache of the loaded configuration items.
* *
* @var array * @var array
*/ */
public static $paths = array(SYS_CONFIG_PATH, CONFIG_PATH, ENV_CONFIG_PATH); protected static $cache = array();
/** /**
* Determine if a configuration item or file exists. * Determine if a configuration item or file exists.
@ -25,7 +27,7 @@ class Config {
* // Determine if the "session" configuration file exists * // Determine if the "session" configuration file exists
* $exists = Config::has('session'); * $exists = Config::has('session');
* *
* // Determine if the "timezone" option exists in the "application" configuration * // Determine if the "timezone" option exists in the configuration
* $exists = Config::has('application.timezone'); * $exists = Config::has('application.timezone');
* </code> * </code>
* *
@ -46,29 +48,38 @@ public static function has($key)
* // Get the "session" configuration array * // Get the "session" configuration array
* $session = Config::get('session'); * $session = Config::get('session');
* *
* // Get a configuration item from a bundle's configuration file
* $name = Config::get('admin::names.first');
*
* // Get the "timezone" option from the "application" configuration file * // Get the "timezone" option from the "application" configuration file
* $timezone = Config::get('application.timezone'); * $timezone = Config::get('application.timezone');
* </code> * </code>
* *
* @param string $key * @param string $key
* @param string $default
* @return array * @return array
*/ */
public static function get($key, $default = null) public static function get($key)
{ {
list($file, $key) = static::parse($key); // First, we'll check the keyed cache of configuration items, as this will
// be the fastest method of retrieving the configuration option. After an
if ( ! static::load($file)) // item is retrieved, it is always stored in the cache by its key.
if (array_key_exists($key, static::$cache))
{ {
return ($default instanceof Closure) ? call_user_func($default) : $default; return static::$cache[$key];
} }
$items = static::$items[$file]; list($bundle, $file, $item) = static::parse($key);
if ( ! static::load($bundle, $file)) return;
$items = static::$items[$bundle][$file];
// If a specific configuration item was not requested, the key will be null, // If a specific configuration item was not requested, the key will be null,
// meaning we need to return the entire array of configuration item from the // meaning we need to return the entire array of configuration item from the
// requested configuration file. Otherwise we can return the item. // requested configuration file. Otherwise we can return the item.
return (is_null($key)) ? $items : Arr::get($items, $key, $default); $value = (is_null($item)) ? $items : array_get($items, $item);
return static::$cache[$key] = $value;
} }
/** /**
@ -78,6 +89,9 @@ public static function get($key, $default = null)
* // Set the "session" configuration array * // Set the "session" configuration array
* Config::set('session', $array); * Config::set('session', $array);
* *
* // Set a configuration option that belongs by a bundle
* Config::set('admin::names.first', 'Taylor');
*
* // Set the "timezone" option in the "application" configuration file * // Set the "timezone" option in the "application" configuration file
* Config::set('application.timezone', 'UTC'); * Config::set('application.timezone', 'UTC');
* </code> * </code>
@ -88,61 +102,53 @@ public static function get($key, $default = null)
*/ */
public static function set($key, $value) public static function set($key, $value)
{ {
list($file, $key) = static::parse($key); static::$cache[$key] = $value;
static::load($file);
if (is_null($key))
{
Arr::set(static::$items, $file, $value);
}
else
{
Arr::set(static::$items[$file], $key, $value);
}
} }
/** /**
* Parse a configuration key and return its file and key segments. * Parse a key and return its bundle, file, and key segments.
* *
* The first segment of a configuration key represents the configuration * Configuration items are named using the {bundle}::{file}.{item} convention.
* file, while the remaining segments represent an item within that file.
* If no item segment is present, null will be returned for the item value
* indicating that the entire configuration array should be returned.
* *
* @param string $key * @param string $key
* @return array * @return array
*/ */
protected static function parse($key) protected static function parse($key)
{ {
$segments = explode('.', $key); $bundle = Bundle::name($key);
$segments = explode('.', Bundle::element($key));
// If there are not at least two segments in the array, it means that the
// developer is requesting the entire configuration array to be returned.
// If that is the case, we'll make the item field of the array "null".
if (count($segments) >= 2) if (count($segments) >= 2)
{ {
return array($segments[0], implode('.', array_slice($segments, 1))); return array($bundle, $segments[0], implode('.', array_slice($segments, 1)));
} }
else else
{ {
return array($segments[0], null); return array($bundle, $segments[0], null);
} }
} }
/** /**
* Load all of the configuration items from a configuration file. * Load all of the configuration items from a configuration file.
* *
* @param string $bundle
* @param string $file * @param string $file
* @return bool * @return bool
*/ */
public static function load($file) public static function load($bundle, $file)
{ {
if (isset(static::$items[$file])) return true; if (isset(static::$items[$bundle][$file])) return true;
$config = array(); $config = array();
// Configuration files cascade. Typically, the system configuration array is // Configuration files cascade. Typically, the bundle configuration array is
// loaded first, followed by the application array, providing the convenient // loaded first, followed by the environment array, providing the convenient
// cascading of configuration options from system to application. // cascading of configuration options across environments.
foreach (static::$paths as $directory) foreach (static::paths($bundle) as $directory)
{ {
if ($directory !== '' and file_exists($path = $directory.$file.EXT)) if ($directory !== '' and file_exists($path = $directory.$file.EXT))
{ {
@ -150,9 +156,37 @@ public static function load($file)
} }
} }
if (count($config) > 0) static::$items[$file] = $config; if (count($config) > 0)
{
static::$items[$bundle][$file] = $config;
}
return isset(static::$items[$file]); return isset(static::$items[$bundle][$file]);
}
/**
* Get the array of configuration paths that should be searched for a bundle.
*
* @param string $bundle
* @return array
*/
protected static function paths($bundle)
{
$paths[] = Bundle::path($bundle).'config/';
// Configuration files can be made specific for a given environment. If an
// environment has been set, we will merge the environment configuration
// in last, so that it overrides all other options.
//
// This allows the developer to quickly and easily create configurations
// for various scenarios, such as local development and production,
// without constantly changing configuration files.
if (isset($_SERVER['LARAVEL_ENV']))
{
$paths[] = $paths[count($paths) - 1].$_SERVER['LARAVEL_ENV'].'/';
}
return $paths;
} }
} }

View File

@ -1,73 +0,0 @@
<?php
return array(
'/æ|ǽ/' => 'ae',
'/œ/' => 'oe',
'/À|Á|Â|Ã|Ä|Å|Ǻ|Ā|Ă|Ą|Ǎ|А/' => 'A',
'/à|á|â|ã|ä|å|ǻ|ā|ă|ą|ǎ|ª|а/' => 'a',
'/Б/' => 'B',
'/б/' => 'b',
'/Ç|Ć|Ĉ|Ċ|Č|Ц/' => 'C',
'/ç|ć|ĉ|ċ|č|ц/' => 'c',
'/Ð|Ď|Đ|Д/' => 'Dj',
'/ð|ď|đ|д/' => 'dj',
'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě|Е|Ё|Э/' => 'E',
'/è|é|ê|ë|ē|ĕ|ė|ę|ě|е|ё|э/' => 'e',
'/Ф/' => 'F',
'/ƒ|ф/' => 'f',
'/Ĝ|Ğ|Ġ|Ģ|Г/' => 'G',
'/ĝ|ğ|ġ|ģ|г/' => 'g',
'/Ĥ|Ħ|Х/' => 'H',
'/ĥ|ħ|х/' => 'h',
'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ|И/' => 'I',
'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı|и/' => 'i',
'/Ĵ|Й/' => 'J',
'/ĵ|й/' => 'j',
'/Ķ|К/' => 'K',
'/ķ|к/' => 'k',
'/Ĺ|Ļ|Ľ|Ŀ|Ł|Л/' => 'L',
'/ĺ|ļ|ľ|ŀ|ł|л/' => 'l',
'/М/' => 'M',
'/м/' => 'm',
'/Ñ|Ń|Ņ|Ň|Н/' => 'N',
'/ñ|ń|ņ|ň|ʼn|н/' => 'n',
'/Ö|Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ|О/' => 'O',
'/ö|ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º|о/' => 'o',
'/П/' => 'P',
'/п/' => 'p',
'/Ŕ|Ŗ|Ř|Р/' => 'R',
'/ŕ|ŗ|ř|р/' => 'r',
'/Ś|Ŝ|Ş|Š|С/' => 'S',
'/ś|ŝ|ş|š|ſ|с/' => 's',
'/Ţ|Ť|Ŧ|Т/' => 'T',
'/ţ|ť|ŧ|т/' => 't',
'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ|У/' => 'U',
'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ|у/' => 'u',
'/В/' => 'V',
'/в/' => 'v',
'/Ý|Ÿ|Ŷ|Ы/' => 'Y',
'/ý|ÿ|ŷ|ы/' => 'y',
'/Ŵ/' => 'W',
'/ŵ/' => 'w',
'/Ź|Ż|Ž|З/' => 'Z',
'/ź|ż|ž|з/' => 'z',
'/Æ|Ǽ/' => 'AE',
'/ß/'=> 'ss',
'/IJ/' => 'IJ',
'/ij/' => 'ij',
'/Œ/' => 'OE',
'/Ч/' => 'Ch',
'/ч/' => 'ch',
'/Ю/' => 'Ju',
'/ю/' => 'ju',
'/Я/' => 'Ja',
'/я/' => 'ja',
'/Ш/' => 'Sh',
'/ш/' => 'sh',
'/Щ/' => 'Shch',
'/щ/' => 'shch',
'/Ж/' => 'Zh',
'/ж/' => 'zh',
);

View File

@ -1,8 +1,10 @@
<?php namespace Laravel; use Closure; <?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
if (trim(Config::$items['application']['key']) === '') use Closure;
if (trim(Config::get('application.key')) === '')
{ {
throw new \LogicException('The cookie class may not be used without an application key.'); throw new \Exception('The cookie class may not be used without an application key.');
} }
class Cookie { class Cookie {
@ -21,59 +23,54 @@ public static function has($name)
/** /**
* Get the value of a cookie. * Get the value of a cookie.
* *
* <code>
* // Get the value of the "favorite" cookie
* $favorite = Cookie::get('favorite');
*
* // Get the value of a cookie or return a default value if it doesn't exist
* $favorite = Cookie::get('framework', 'Laravel');
* </code>
*
* @param string $name * @param string $name
* @param mixed $default * @param mixed $default
* @return string * @return string
*/ */
public static function get($name, $default = null) public static function get($name, $default = null)
{ {
$value = Arr::get($_COOKIE, $name); $value = array_get($_COOKIE, $name);
if ( ! is_null($value)) if ( ! is_null($value) and isset($value[40]) and $value[40] == '~')
{ {
// All Laravel managed cookies are "signed" with a fingerprint hash. // The hash signature and the cookie value are separated by a tilde
// The hash serves to verify that the contents of the cookie have not // character for convenience. To separate the hash and the contents
// been modified by the user. We can verify the integrity of the cookie // we can simply expode on that character.
// by extracting the value and re-hashing it, then comparing that hash //
// against the hash stored in the cookie. // By re-feeding the cookie value into the "sign" method, we should
if (isset($value[40]) and $value[40] === '~') // be able to generate a hash that matches the one taken out of the
{ // cookie. If they don't match, the cookie value has been changed.
list($hash, $value) = explode('~', $value, 2); list($hash, $value) = explode('~', $value, 2);
if (static::hash($name, $value) === $hash) if (static::hash($name, $value) === $hash)
{ {
return $value; return $value;
}
} }
} }
return ($default instanceof Closure) ? call_user_func($default) : $default; return value($default);
} }
/** /**
* Set a "permanent" cookie. The cookie will last for one year. * Set the value of a cookie.
* *
* @param string $name * If the response headers have already been sent, the cookie will not be set.
* @param string $value
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $http_only
* @return bool
*/
public static function forever($name, $value, $path = '/', $domain = null, $secure = false, $http_only = false)
{
return static::put($name, $value, 525600, $path, $domain, $secure, $http_only);
}
/**
* Set the value of a cookie.
* *
* If a negative number of minutes is specified, the cookie will be deleted. * <code>
* // Set the value of the "favorite" cookie
* Cookie::put('favorite', 'Laravel');
* *
* This method's signature is very similar to the PHP setcookie method. * // Set the value of the "favorite" cookie for twenty minutes
* However, you simply need to pass the number of minutes for which you * Cookie::put('favorite', 'Laravel', 20);
* wish the cookie to be valid. No funky time calculation is required. * </code>
* *
* @param string $name * @param string $name
* @param string $value * @param string $value
@ -81,39 +78,51 @@ public static function forever($name, $value, $path = '/', $domain = null, $secu
* @param string $path * @param string $path
* @param string $domain * @param string $domain
* @param bool $secure * @param bool $secure
* @param bool $http_only
* @return bool * @return bool
*/ */
public static function put($name, $value, $minutes = 0, $path = '/', $domain = null, $secure = false, $http_only = false) public static function put($name, $value, $minutes = 0, $path = '/', $domain = null, $secure = false)
{ {
if (headers_sent()) return false; if (headers_sent()) return false;
$time = ($minutes !== 0) ? time() + ($minutes * 60) : 0; $time = ($minutes !== 0) ? time() + ($minutes * 60) : 0;
$value = static::hash($name, $value).'~'.$value; return setcookie($name, static::sign($name, $value), $time, $path, $domain, $secure);
if ($minutes < 0)
{
unset($_COOKIE[$name]);
}
else
{
$_COOKIE[$name] = $value;
}
return setcookie($name, $value, $time, $path, $domain, $secure, $http_only);
} }
/** /**
* Generate a cookie hash. * Set a "permanent" cookie. The cookie will last for one year.
* *
* Cookie salts are used to verify that the contents of the cookie have not * <code>
* been modified by the user, since they serve as a fingerprint of the cookie * // Set a cookie that should last one year
* contents. The application key is used to salt the salts. * Cookie::forever('favorite', 'Blue');
* </code>
* *
* When the cookie is read using the "get" method, the value will be extracted * @param string $name
* from the cookie and hashed, if the hash in the cookie and the hashed value * @param string $value
* do not match, we know the cookie has been changed on the client. * @param string $path
* @param string $domain
* @param bool $secure
* @return bool
*/
public static function forever($name, $value, $path = '/', $domain = null, $secure = false)
{
return static::put($name, $value, 525600, $path, $domain, $secure);
}
/**
* Generate a cookie signature based on the contents.
*
* @param string $name
* @param string $value
* @return string
*/
protected static function sign($name, $value)
{
return static::hash($name, $value).'~'.$value;
}
/**
* Generate a cookie hash based on the contents.
* *
* @param string $name * @param string $name
* @param string $value * @param string $value
@ -121,7 +130,7 @@ public static function put($name, $value, $minutes = 0, $path = '/', $domain = n
*/ */
protected static function hash($name, $value) protected static function hash($name, $value)
{ {
return sha1($name.$value.Config::$items['application']['key']); return sha1($name.$value.Config::get('application.key'));
} }
/** /**
@ -131,12 +140,11 @@ protected static function hash($name, $value)
* @param string $path * @param string $path
* @param string $domain * @param string $domain
* @param bool $secure * @param bool $secure
* @param bool $http_only
* @return bool * @return bool
*/ */
public static function forget($name, $path = '/', $domain = null, $secure = false, $http_only = false) public static function forget($name, $path = '/', $domain = null, $secure = false)
{ {
return static::put($name, null, -2000, $path, $domain, $secure, $http_only); return static::put($name, null, -2000, $path, $domain, $secure);
} }
} }

View File

@ -9,59 +9,22 @@
define('EXT', '.php'); define('EXT', '.php');
define('CRLF', "\r\n"); define('CRLF', "\r\n");
define('BLADE_EXT', '.blade.php'); define('BLADE_EXT', '.blade.php');
define('APP_PATH', realpath($application).'/'); define('CACHE_PATH', STORAGE_PATH.'cache'.DS);
define('PUBLIC_PATH', realpath($public).'/'); define('DATABASE_PATH', STORAGE_PATH.'database'.DS);
define('SYS_PATH', realpath($laravel).'/'); define('SESSION_PATH', STORAGE_PATH.'sessions'.DS);
define('STORAGE_PATH', APP_PATH.'storage/'); define('DEFAULT_BUNDLE', 'application');
define('CACHE_PATH', STORAGE_PATH.'cache/'); define('MB_STRING', (int) function_exists('mb_get_info'));
define('CONFIG_PATH', APP_PATH.'config/');
define('CONTROLLER_PATH', APP_PATH.'controllers/');
define('DATABASE_PATH', STORAGE_PATH.'database/');
define('LANG_PATH', APP_PATH.'language/');
define('LIBRARY_PATH', APP_PATH.'libraries/');
define('MODEL_PATH', APP_PATH.'models/');
define('ROUTE_PATH', APP_PATH.'routes/');
define('SESSION_PATH', STORAGE_PATH.'sessions/');
define('SYS_CONFIG_PATH', SYS_PATH.'config/');
define('VIEW_PATH', APP_PATH.'views/');
/**
* Define the Laravel environment configuration path. This path is used
* by the configuration class to load configuration options specific for
* the server environment, allowing the developer to conveniently change
* configuration options based on the application environment.
*
*/
$environment = '';
if (isset($_SERVER['LARAVEL_ENV']))
{
$environment = CONFIG_PATH.$_SERVER['LARAVEL_ENV'].'/';
}
define('ENV_CONFIG_PATH', $environment);
unset($application, $public, $laravel, $environment);
/** /**
* Require all of the classes that can't be loaded by the auto-loader. * Require all of the classes that can't be loaded by the auto-loader.
* These are typically classes that the auto-loader itself relies upon * These are typically classes that the auto-loader itself relies upon
* to load classes, such as the array and configuration classes. * to load classes, such as the array and configuration classes.
*/ */
require SYS_PATH.'arr'.EXT; require SYS_PATH.'bundle'.EXT;
require SYS_PATH.'config'.EXT; require SYS_PATH.'config'.EXT;
require SYS_PATH.'facades'.EXT; require SYS_PATH.'helpers'.EXT;
require SYS_PATH.'autoloader'.EXT; require SYS_PATH.'autoloader'.EXT;
/**
* Load a few of the core configuration files that are loaded for every
* request to the application. It is quicker to load them manually each
* request rather than parse the keys for every request.
*/
Config::load('application');
Config::load('session');
Config::load('error');
/** /**
* Register the Autoloader's "load" method on the auto-loader stack. * Register the Autoloader's "load" method on the auto-loader stack.
* This method provides the lazy-loading of all class files, as well * This method provides the lazy-loading of all class files, as well
@ -75,22 +38,27 @@
* More mappings can also be registered by the developer as needed. * More mappings can also be registered by the developer as needed.
*/ */
Autoloader::$mappings = array( Autoloader::$mappings = array(
'Laravel\\Arr' => SYS_PATH.'arr'.EXT,
'Laravel\\Asset' => SYS_PATH.'asset'.EXT,
'Laravel\\Auth' => SYS_PATH.'auth'.EXT, 'Laravel\\Auth' => SYS_PATH.'auth'.EXT,
'Laravel\\Asset' => SYS_PATH.'asset'.EXT,
'Laravel\\Benchmark' => SYS_PATH.'benchmark'.EXT, 'Laravel\\Benchmark' => SYS_PATH.'benchmark'.EXT,
'Laravel\\Blade' => SYS_PATH.'blade'.EXT, 'Laravel\\Blade' => SYS_PATH.'blade'.EXT,
'Laravel\\Bundle' => SYS_PATH.'bundle'.EXT,
'Laravel\\Cache' => SYS_PATH.'cache'.EXT,
'Laravel\\Config' => SYS_PATH.'config'.EXT, 'Laravel\\Config' => SYS_PATH.'config'.EXT,
'Laravel\\Cookie' => SYS_PATH.'cookie'.EXT, 'Laravel\\Cookie' => SYS_PATH.'cookie'.EXT,
'Laravel\\Crypter' => SYS_PATH.'crypter'.EXT, 'Laravel\\Crypter' => SYS_PATH.'crypter'.EXT,
'Laravel\\Database' => SYS_PATH.'database'.EXT,
'Laravel\\Error' => SYS_PATH.'error'.EXT,
'Laravel\\Event' => SYS_PATH.'event'.EXT,
'Laravel\\File' => SYS_PATH.'file'.EXT, 'Laravel\\File' => SYS_PATH.'file'.EXT,
'Laravel\\Fluent' => SYS_PATH.'fluent'.EXT,
'Laravel\\Form' => SYS_PATH.'form'.EXT, 'Laravel\\Form' => SYS_PATH.'form'.EXT,
'Laravel\\Hash' => SYS_PATH.'hash'.EXT, 'Laravel\\Hash' => SYS_PATH.'hash'.EXT,
'Laravel\\HTML' => SYS_PATH.'html'.EXT, 'Laravel\\HTML' => SYS_PATH.'html'.EXT,
'Laravel\\Inflector' => SYS_PATH.'inflector'.EXT,
'Laravel\\Input' => SYS_PATH.'input'.EXT, 'Laravel\\Input' => SYS_PATH.'input'.EXT,
'Laravel\\IoC' => SYS_PATH.'ioc'.EXT, 'Laravel\\IoC' => SYS_PATH.'ioc'.EXT,
'Laravel\\Lang' => SYS_PATH.'lang'.EXT, 'Laravel\\Lang' => SYS_PATH.'lang'.EXT,
'Laravel\\Log' => SYS_PATH.'log'.EXT,
'Laravel\\Memcached' => SYS_PATH.'memcached'.EXT, 'Laravel\\Memcached' => SYS_PATH.'memcached'.EXT,
'Laravel\\Messages' => SYS_PATH.'messages'.EXT, 'Laravel\\Messages' => SYS_PATH.'messages'.EXT,
'Laravel\\Paginator' => SYS_PATH.'paginator'.EXT, 'Laravel\\Paginator' => SYS_PATH.'paginator'.EXT,
@ -99,40 +67,62 @@
'Laravel\\Request' => SYS_PATH.'request'.EXT, 'Laravel\\Request' => SYS_PATH.'request'.EXT,
'Laravel\\Response' => SYS_PATH.'response'.EXT, 'Laravel\\Response' => SYS_PATH.'response'.EXT,
'Laravel\\Section' => SYS_PATH.'section'.EXT, 'Laravel\\Section' => SYS_PATH.'section'.EXT,
'Laravel\\Session' => SYS_PATH.'session'.EXT,
'Laravel\\Str' => SYS_PATH.'str'.EXT, 'Laravel\\Str' => SYS_PATH.'str'.EXT,
'Laravel\\URI' => SYS_PATH.'uri'.EXT, 'Laravel\\URI' => SYS_PATH.'uri'.EXT,
'Laravel\\URL' => SYS_PATH.'url'.EXT, 'Laravel\\URL' => SYS_PATH.'url'.EXT,
'Laravel\\Validator' => SYS_PATH.'validator'.EXT, 'Laravel\\Validator' => SYS_PATH.'validator'.EXT,
'Laravel\\View' => SYS_PATH.'view'.EXT, 'Laravel\\View' => SYS_PATH.'view'.EXT,
'Laravel\\Cache\\Manager' => SYS_PATH.'cache/manager'.EXT,
'Laravel\\Cache\\Drivers\\APC' => SYS_PATH.'cache/drivers/apc'.EXT, 'Laravel\\Cache\\Drivers\\APC' => SYS_PATH.'cache/drivers/apc'.EXT,
'Laravel\\Cache\\Drivers\\Driver' => SYS_PATH.'cache/drivers/driver'.EXT, 'Laravel\\Cache\\Drivers\\Driver' => SYS_PATH.'cache/drivers/driver'.EXT,
'Laravel\\Cache\\Drivers\\File' => SYS_PATH.'cache/drivers/file'.EXT, 'Laravel\\Cache\\Drivers\\File' => SYS_PATH.'cache/drivers/file'.EXT,
'Laravel\\Cache\\Drivers\\Memcached' => SYS_PATH.'cache/drivers/memcached'.EXT, 'Laravel\\Cache\\Drivers\\Memcached' => SYS_PATH.'cache/drivers/memcached'.EXT,
'Laravel\\Cache\\Drivers\\Redis' => SYS_PATH.'cache/drivers/redis'.EXT, 'Laravel\\Cache\\Drivers\\Redis' => SYS_PATH.'cache/drivers/redis'.EXT,
'Laravel\\Cache\\Drivers\\Database' => SYS_PATH.'cache/drivers/database'.EXT,
'Laravel\\CLI\\Command' => SYS_PATH.'cli/command'.EXT,
'Laravel\\CLI\\Tasks\\Task' => SYS_PATH.'cli/tasks/task'.EXT,
'Laravel\\CLI\\Tasks\\Bundle\\Bundler' => SYS_PATH.'cli/tasks/bundle/bundler'.EXT,
'Laravel\\CLI\\Tasks\\Bundle\\Repository' => SYS_PATH.'cli/tasks/bundle/repository'.EXT,
'Laravel\\CLI\\Tasks\\Bundle\\Publisher' => SYS_PATH.'cli/tasks/bundle/publisher'.EXT,
'Laravel\\CLI\\Tasks\\Bundle\\Providers\\Provider' => SYS_PATH.'cli/tasks/bundle/providers/provider'.EXT,
'Laravel\\CLI\\Tasks\\Bundle\\Providers\\Github' => SYS_PATH.'cli/tasks/bundle/providers/github'.EXT,
'Laravel\\CLI\\Tasks\\Migrate\\Migrator' => SYS_PATH.'cli/tasks/migrate/migrator'.EXT,
'Laravel\\CLI\\Tasks\\Migrate\\Resolver' => SYS_PATH.'cli/tasks/migrate/resolver'.EXT,
'Laravel\\CLI\\Tasks\\Migrate\\Database' => SYS_PATH.'cli/tasks/migrate/database'.EXT,
'Laravel\\Database\\Connection' => SYS_PATH.'database/connection'.EXT, 'Laravel\\Database\\Connection' => SYS_PATH.'database/connection'.EXT,
'Laravel\\Database\\Expression' => SYS_PATH.'database/expression'.EXT, 'Laravel\\Database\\Expression' => SYS_PATH.'database/expression'.EXT,
'Laravel\\Database\\Manager' => SYS_PATH.'database/manager'.EXT,
'Laravel\\Database\\Query' => SYS_PATH.'database/query'.EXT, 'Laravel\\Database\\Query' => SYS_PATH.'database/query'.EXT,
'Laravel\\Database\\Schema' => SYS_PATH.'database/schema'.EXT,
'Laravel\\Database\\Grammar' => SYS_PATH.'database/grammar'.EXT,
'Laravel\\Database\\Connectors\\Connector' => SYS_PATH.'database/connectors/connector'.EXT, 'Laravel\\Database\\Connectors\\Connector' => SYS_PATH.'database/connectors/connector'.EXT,
'Laravel\\Database\\Connectors\\MySQL' => SYS_PATH.'database/connectors/mysql'.EXT, 'Laravel\\Database\\Connectors\\MySQL' => SYS_PATH.'database/connectors/mysql'.EXT,
'Laravel\\Database\\Connectors\\Postgres' => SYS_PATH.'database/connectors/postgres'.EXT, 'Laravel\\Database\\Connectors\\Postgres' => SYS_PATH.'database/connectors/postgres'.EXT,
'Laravel\\Database\\Connectors\\SQLite' => SYS_PATH.'database/connectors/sqlite'.EXT, 'Laravel\\Database\\Connectors\\SQLite' => SYS_PATH.'database/connectors/sqlite'.EXT,
'Laravel\\Database\\Eloquent\\Hydrator' => SYS_PATH.'database/eloquent/hydrator'.EXT, 'Laravel\\Database\\Connectors\\SQLServer' => SYS_PATH.'database/connectors/sqlserver'.EXT,
'Laravel\\Database\\Eloquent\\Model' => SYS_PATH.'database/eloquent/model'.EXT, 'Laravel\\Database\\Query\\Grammars\\Grammar' => SYS_PATH.'database/query/grammars/grammar'.EXT,
'Laravel\\Database\\Grammars\\Grammar' => SYS_PATH.'database/grammars/grammar'.EXT, 'Laravel\\Database\\Query\\Grammars\\MySQL' => SYS_PATH.'database/query/grammars/mysql'.EXT,
'Laravel\\Database\\Grammars\\MySQL' => SYS_PATH.'database/grammars/mysql'.EXT, 'Laravel\\Database\\Query\\Grammars\\SQLServer' => SYS_PATH.'database/query/grammars/sqlserver'.EXT,
'Laravel\\Database\\Schema\\Table' => SYS_PATH.'database/schema/table'.EXT,
'Laravel\\Database\\Schema\\Grammars\\Grammar' => SYS_PATH.'database/schema/grammars/grammar'.EXT,
'Laravel\\Database\\Schema\\Grammars\\MySQL' => SYS_PATH.'database/schema/grammars/mysql'.EXT,
'Laravel\\Database\\Schema\\Grammars\\Postgres' => SYS_PATH.'database/schema/grammars/postgres'.EXT,
'Laravel\\Database\\Schema\\Grammars\\SQLServer' => SYS_PATH.'database/schema/grammars/sqlserver'.EXT,
'Laravel\\Database\\Schema\\Grammars\\SQLite' => SYS_PATH.'database/schema/grammars/sqlite'.EXT,
'Laravel\\Routing\\Controller' => SYS_PATH.'routing/controller'.EXT, 'Laravel\\Routing\\Controller' => SYS_PATH.'routing/controller'.EXT,
'Laravel\\Routing\\Filter' => SYS_PATH.'routing/filter'.EXT, 'Laravel\\Routing\\Filter' => SYS_PATH.'routing/filter'.EXT,
'Laravel\\Routing\\Loader' => SYS_PATH.'routing/loader'.EXT, 'Laravel\\Routing\\Filter_Collection' => SYS_PATH.'routing/filter'.EXT,
'Laravel\\Routing\\Route' => SYS_PATH.'routing/route'.EXT, 'Laravel\\Routing\\Route' => SYS_PATH.'routing/route'.EXT,
'Laravel\\Routing\\Router' => SYS_PATH.'routing/router'.EXT, 'Laravel\\Routing\\Router' => SYS_PATH.'routing/router'.EXT,
'Laravel\\Session\\Payload' => SYS_PATH.'session/payload'.EXT, 'Laravel\\Session\\Payload' => SYS_PATH.'session/payload'.EXT,
'Laravel\\Session\\Drivers\\APC' => SYS_PATH.'session/drivers/apc'.EXT, 'Laravel\\Session\\Drivers\\APC' => SYS_PATH.'session/drivers/apc'.EXT,
'Laravel\\Session\\Drivers\\Cookie' => SYS_PATH.'session/drivers/cookie'.EXT, 'Laravel\\Session\\Drivers\\Cookie' => SYS_PATH.'session/drivers/cookie'.EXT,
'Laravel\\Session\\Drivers\\Database' => SYS_PATH.'session/drivers/database'.EXT, 'Laravel\\Session\\Drivers\\Database' => SYS_PATH.'session/drivers/database'.EXT,
'Laravel\\Session\\Drivers\\Driver' => SYS_PATH.'session/drivers/driver'.EXT, 'Laravel\\Session\\Drivers\\Driver' => SYS_PATH.'session/drivers/driver'.EXT,
'Laravel\\Session\\Drivers\\Factory' => SYS_PATH.'session/drivers/factory'.EXT,
'Laravel\\Session\\Drivers\\File' => SYS_PATH.'session/drivers/file'.EXT, 'Laravel\\Session\\Drivers\\File' => SYS_PATH.'session/drivers/file'.EXT,
'Laravel\\Session\\Drivers\\Memcached' => SYS_PATH.'session/drivers/memcached'.EXT, 'Laravel\\Session\\Drivers\\Memcached' => SYS_PATH.'session/drivers/memcached'.EXT,
'Laravel\\Session\\Drivers\\Redis' => SYS_PATH.'session/drivers/redis'.EXT, 'Laravel\\Session\\Drivers\\Redis' => SYS_PATH.'session/drivers/redis'.EXT,
@ -140,16 +130,9 @@
); );
/** /**
* Register the default timezone for the application. This will be * Register all of the core class aliases. These aliases provide a
* the default timezone used by all date / timezone functions in * convenient way of working with the Laravel core classes without
* the entire application. * having to worry about the namespacing. The developer is also
* free to remove aliases when they extend core classes.
*/ */
date_default_timezone_set(Config::$items['application']['timezone']); Autoloader::$aliases = Config::get('application.aliases');
/**
* Define a few global, convenient functions. These functions
* provide short-cuts for things like the retrieval of language
* lines and HTML::entities. They just make our lives as devs a
* little sweeter and more enjoyable.
*/
require SYS_PATH.'helpers'.EXT;

View File

@ -1,8 +1,8 @@
<?php namespace Laravel; <?php namespace Laravel; defined('APP_PATH') or die('No direct script access.');
if (trim(Config::$items['application']['key']) === '') if (trim(Config::get('application.key')) === '')
{ {
throw new \LogicException('The encryption class may not be used without an application key.'); throw new \Exception('The Crypter class may not be used without an application key.');
} }
class Crypter { class Crypter {
@ -12,28 +12,19 @@ class Crypter {
* *
* @var string * @var string
*/ */
protected static $cipher = MCRYPT_RIJNDAEL_256; public static $cipher = MCRYPT_RIJNDAEL_256;
/** /**
* The encryption mode. * The encryption mode.
* *
* @var string * @var string
*/ */
protected static $mode = MCRYPT_MODE_CBC; public static $mode = MCRYPT_MODE_CBC;
/** /**
* Encrypt a string using Mcrypt. * Encrypt a string using Mcrypt.
* *
* The given string will be encrypted using AES-256 encryption for a high * The string will be encrypted using the AES-256 scheme and will be base64 encoded.
* degree of security. The returned string will also be base64 encoded.
*
* Mcrypt must be installed on your machine before using this method, and
* an application key must be specified in the application configuration.
*
* <code>
* // Encrypt a string using the Mcrypt PHP extension
* $encrypted = Crypter::encrpt('secret');
* </code>
* *
* @param string $value * @param string $value
* @return string * @return string
@ -50,26 +41,27 @@ public static function encrypt($value)
/** /**
* Decrypt a string using Mcrypt. * Decrypt a string using Mcrypt.
* *
* The given encrypted value must have been encrypted using Laravel and
* the application key specified in the application configuration file.
*
* Mcrypt must be installed on your machine before using this method.
*
* @param string $value * @param string $value
* @return string * @return string
*/ */
public static function decrypt($value) public static function decrypt($value)
{ {
if (($value = base64_decode($value)) === false) $value = base64_decode($value);
{
throw new \InvalidArgumentException('Input value is not valid base64 data.');
}
// To decrypt the value, we first need to extract the input vector and
// the encrypted value. The input vector size varies across different
// encryption ciphers and modes, so we will get the correct size for
// the cipher and mode being used by the class.
$iv = substr($value, 0, static::iv_size()); $iv = substr($value, 0, static::iv_size());
$value = substr($value, static::iv_size()); $value = substr($value, static::iv_size());
return rtrim(mcrypt_decrypt(static::$cipher, static::key(), $value, static::$mode, $iv), "\0"); // Once we have the input vector and the value, we can give them both
// to Mcrypt for decryption. The value is sometimes padded with \0,
// so we will trim all of the padding characters from the string.
$key = static::key();
return rtrim(mcrypt_decrypt(static::$cipher, $key, $value, static::$mode, $iv), "\0");
} }
/** /**
@ -89,7 +81,7 @@ protected static function iv_size()
*/ */
protected static function key() protected static function key()
{ {
return Config::$items['application']['key']; return Config::get('application.key');
} }
} }

View File

@ -1,16 +1,16 @@
<?php namespace Laravel\Database; <?php namespace Laravel;
use Laravel\IoC; use Laravel\Database\Expression;
use Laravel\Config; use Laravel\Database\Connection;
class Manager { class Database {
/** /**
* The established database connections. * The established database connections.
* *
* @var array * @var array
*/ */
protected static $connections = array(); public static $connections = array();
/** /**
* Get a database connection. * Get a database connection.
@ -38,7 +38,7 @@ public static function connection($connection = null)
if (is_null($config)) if (is_null($config))
{ {
throw new \OutOfBoundsException("Connection is not defined for [$connection]."); throw new \Exception("Database connection is not defined for [$connection].");
} }
static::$connections[$connection] = new Connection(static::connect($config), $config); static::$connections[$connection] = new Connection(static::connect($config), $config);
@ -55,26 +55,12 @@ public static function connection($connection = null)
*/ */
protected static function connect($config) protected static function connect($config)
{ {
// We allow the developer to place a "connector" option in the database
// configuration, which should have a Closure value. If the connector
// is present, we will use the Closure to retrieve the PDO connection
// to the database. This allows the flexiblity to connect to database
// systems that are not officially supported by the the framework.
if (isset($config['connector']))
{
return call_user_func($config['connector'], $config);
}
return static::connector($config['driver'])->connect($config); return static::connector($config['driver'])->connect($config);
} }
/** /**
* Create a new database connector instance. * Create a new database connector instance.
* *
* The database connectors are responsible for simply establishing a PDO
* database connection given a configuration. This allows us to easily
* drop in support for new database systems by writing a connector.
*
* @param string $driver * @param string $driver
* @return Connector * @return Connector
*/ */
@ -83,16 +69,19 @@ protected static function connector($driver)
switch ($driver) switch ($driver)
{ {
case 'sqlite': case 'sqlite':
return new Connectors\SQLite(DATABASE_PATH); return new Database\Connectors\SQLite;
case 'mysql': case 'mysql':
return new Connectors\MySQL; return new Database\Connectors\MySQL;
case 'pgsql': case 'pgsql':
return new Connectors\Postgres; return new Database\Connectors\Postgres;
case 'sqlsrv':
return new Database\Connectors\SQLServer;
default: default:
throw new \DomainException("Database driver [$driver] is not supported."); throw new \Exception("Database driver [$driver] is not supported.");
} }
} }
@ -124,7 +113,13 @@ public static function raw($value)
/** /**
* Magic Method for calling methods on the default database connection. * Magic Method for calling methods on the default database connection.
* *
* This provides a convenient API for querying or examining the default database connection. * <code>
* // Get the driver name for the default database connection
* $driver = DB::driver();
*
* // Execute a fluent query on the default database connection
* $users = DB::table('users')->get();
* </code>
*/ */
public static function __callStatic($method, $parameters) public static function __callStatic($method, $parameters)
{ {

View File

@ -9,13 +9,6 @@ class Connection {
*/ */
public $pdo; public $pdo;
/**
* All of the queries that have been executed on the connection.
*
* @var array
*/
public $queries = array();
/** /**
* The connection configuration array. * The connection configuration array.
* *
@ -30,6 +23,13 @@ class Connection {
*/ */
protected $grammar; protected $grammar;
/**
* All of the queries that have been executed on all connections.
*
* @var array
*/
public static $queries = array();
/** /**
* Create a new database connection instance. * Create a new database connection instance.
* *
@ -46,6 +46,14 @@ public function __construct(PDO $pdo, $config)
/** /**
* Begin a fluent query against a table. * Begin a fluent query against a table.
* *
* <code>
* // Start a fluent query against the "users" table
* $query = DB::connection()->table('users');
*
* // Start a fluent query against the "users" table and get all the users
* $users = DB::connection()->table('users')->get();
* </code>
*
* @param string $table * @param string $table
* @return Query * @return Query
*/ */
@ -57,27 +65,22 @@ public function table($table)
/** /**
* Create a new query grammar for the connection. * Create a new query grammar for the connection.
* *
* Query grammars allow support for new database systems to be added quickly * @return Query\Grammars\Grammar
* and easily. Since the responsibility of the query generation is delegated
* to the grammar classes, it is simple to override only the methods with
* SQL syntax that differs from the default implementation.
*
* @return Grammars\Grammar
*/ */
protected function grammar() protected function grammar()
{ {
if (isset($this->grammar)) return $this->grammar; if (isset($this->grammar)) return $this->grammar;
// We allow the developer to hard-code a grammar for the connection. This really
// has no use yet; however, if database systems that can use multiple grammars
// like ODBC are added in the future, this will be needed.
switch (isset($this->config['grammar']) ? $this->config['grammar'] : $this->driver()) switch (isset($this->config['grammar']) ? $this->config['grammar'] : $this->driver())
{ {
case 'mysql': case 'mysql':
return $this->grammar = new Grammars\MySQL; return $this->grammar = new Query\Grammars\MySQL;
case 'sqlsrv':
return $this->grammar = new Query\Grammars\SQLServer;
default: default:
return $this->grammar = new Grammars\Grammar; return $this->grammar = new Query\Grammars\Grammar;
} }
} }
@ -98,9 +101,9 @@ protected function grammar()
*/ */
public function only($sql, $bindings = array()) public function only($sql, $bindings = array())
{ {
$result = (array) $this->first($sql, $bindings); $results = (array) $this->first($sql, $bindings);
return reset($result); return reset($results);
} }
/** /**
@ -127,67 +130,124 @@ public function first($sql, $bindings = array())
} }
/** /**
* Execute a SQL query against the connection. * Execute a SQL query and return an array of StdClass objects.
*
* The method returns the following based on query type:
*
* SELECT -> Array of stdClasses
* UPDATE -> Number of rows affected.
* DELETE -> Number of Rows affected.
* ELSE -> Boolean true / false depending on success.
*
* <code>
* // Execute a query against the database connection
* $users = DB::connection()->query('select * from users');
*
* // Execute a query with bound parameters
* $user = DB::connection()->query('select * from users where id = ?', array($id));
* </code>
* *
* @param string $sql * @param string $sql
* @param array $bindings * @param array $bindings
* @return mixed * @return array
*/ */
public function query($sql, $bindings = array()) public function query($sql, $bindings = array())
{ {
// Since expressions are injected into the query as raw strings, we need list($statement, $result) = $this->execute($sql, $bindings);
// to remove them from the array of bindings. They are not truly bound
// to the PDO statement as named parameters.
foreach ($bindings as $key => $value)
{
if ($value instanceof Expression) unset($bindings[$key]);
}
$bindings = array_values($bindings); return $statement->fetchAll(PDO::FETCH_CLASS, 'stdClass');
}
/**
* Execute a SQL UPDATE query and return the affected row count.
*
* @param string $sql
* @param array $bindings
* @return int
*/
public function update($sql, $bindings = array())
{
list($statement, $result) = $this->execute($sql, $bindings);
return $statement->rowCount();
}
/**
* Execute a SQL DELETE query and return the affected row count.
*
* @param string $sql
* @param array $bindings
* @return int
*/
public function delete($sql, $bindings = array())
{
list($statement, $result) = $this->execute($sql, $bindings);
return $statement->rowCount();
}
/**
* Execute an SQL query and return the boolean result of the PDO statement.
*
* @param string $sql
* @param array $bindings
* @return bool
*/
public function statement($sql, $bindings = array())
{
list($statement, $result) = $this->execute($sql, $bindings);
return $result;
}
/**
* Execute a SQL query against the connection.
*
* The PDO statement and boolean result will be return in an array.
*
* @param string $sql
* @param array $bindings
* @return array
*/
protected function execute($sql, $bindings = array())
{
// Since expressions are injected into the query as strings, we need to
// remove them from the array of bindings. After we have removed them,
// we'll reset the array so there aren't gaps in the keys.
$bindings = array_values(array_filter($bindings, function($binding)
{
return ! $binding instanceof Expression;
}));
$sql = $this->transform($sql, $bindings); $sql = $this->transform($sql, $bindings);
$this->queries[] = compact('sql', 'bindings'); $statement = $this->pdo->prepare($sql);
return $this->execute($this->pdo->prepare($sql), $bindings); // Every query is timed so that we can log the executinon time along
// with the query SQL and array of bindings. This should be make it
// convenient for the developer to profile the application's query
// performance to diagnose bottlenecks.
$time = microtime(true);
$result = $statement->execute($bindings);
$time = number_format((microtime(true) - $time) * 1000, 2);
// Once we have execute the query, we log the SQL, bindings, and
// execution time in a static array that is accessed by all of
// the connections used by the application. This allows us to
// review all of the executed SQL.
static::$queries[] = compact('sql', 'bindings', 'time');
return array($statement, $result);
} }
/** /**
* Transform an SQL query into an executable query. * Transform an SQL query into an executable query.
* *
* Laravel provides a convenient short-cut when writing raw queries for
* handling cumbersome "where in" statements. This method will transform
* those segments into their full SQL counterparts.
*
* @param string $sql * @param string $sql
* @param array $bindings * @param array $bindings
* @return string * @return string
*/ */
protected function transform($sql, $bindings) protected function transform($sql, $bindings)
{ {
// Laravel provides an easy short-cut notation for writing raw
// WHERE IN statements. If (...) is in the query, it will be
// replaced with the correct number of parameters based on
// the bindings for the query.
if (strpos($sql, '(...)') !== false) if (strpos($sql, '(...)') !== false)
{ {
for ($i = 0; $i < count($bindings); $i++) for ($i = 0; $i < count($bindings); $i++)
{ {
// If the binding is an array, we can assume it is being used to fill // If the binding is an array, we can assume it is being used
// a "where in" condition, so we will replace the next place-holder // to fill a "where in" condition, so we'll replace the next
// in the query with the correct number of parameters based on the // place-holder in the SQL query with the correct number of
// number of elements in this binding. // parameters based on the elements in the binding.
if (is_array($bindings[$i])) if (is_array($bindings[$i]))
{ {
$parameters = implode(', ', array_fill(0, count($bindings[$i]), '?')); $parameters = implode(', ', array_fill(0, count($bindings[$i]), '?'));
@ -200,33 +260,6 @@ protected function transform($sql, $bindings)
return trim($sql); return trim($sql);
} }
/**
* Execute a prepared PDO statement and return the appropriate results.
*
* @param PDOStatement $statement
* @param array $bindings
* @return mixed
*/
protected function execute(PDOStatement $statement, $bindings)
{
$result = $statement->execute($bindings);
$sql = strtoupper($statement->queryString);
if (strpos($sql, 'SELECT') === 0)
{
return $statement->fetchAll(PDO::FETCH_CLASS, 'stdClass');
}
elseif (strpos($sql, 'UPDATE') === 0 or strpos($sql, 'DELETE') === 0)
{
return $statement->rowCount();
}
else
{
return $result;
}
}
/** /**
* Get the driver name for the database connection. * Get the driver name for the database connection.
* *

View File

@ -16,7 +16,7 @@ abstract class Connector {
); );
/** /**
* Establish a PDO database connection for a given database configuration. * Establish a PDO database connection.
* *
* @param array $config * @param array $config
* @return PDO * @return PDO
@ -24,7 +24,7 @@ abstract class Connector {
abstract public function connect($config); abstract public function connect($config);
/** /**
* Get the PDO connection options for a given database configuration. * Get the PDO connection options for the configuration.
* *
* Developer specified options will override the default connection options. * Developer specified options will override the default connection options.
* *

View File

@ -3,7 +3,7 @@
class MySQL extends Connector { class MySQL extends Connector {
/** /**
* Establish a PDO database connection for a given database configuration. * Establish a PDO database connection.
* *
* @param array $config * @param array $config
* @return PDO * @return PDO
@ -15,7 +15,7 @@ public function connect($config)
// Format the initial MySQL PDO connection string. These options are required // Format the initial MySQL PDO connection string. These options are required
// for every MySQL connection that is established. The connection strings // for every MySQL connection that is established. The connection strings
// have the following convention: "mysql:host=hostname;dbname=database" // have the following convention: "mysql:host=hostname;dbname=database"
$dsn = sprintf('%s:host=%s;dbname=%s', $driver, $host, $database); $dsn = "mysql:host={$host};dbname={$database}";
// Check for any optional MySQL PDO options. These options are not required // Check for any optional MySQL PDO options. These options are not required
// to establish a PDO connection; however, may be needed in certain server // to establish a PDO connection; however, may be needed in certain server

View File

@ -3,7 +3,7 @@
class Postgres extends Connector { class Postgres extends Connector {
/** /**
* Establish a PDO database connection for a given database configuration. * Establish a PDO database connection.
* *
* @param array $config * @param array $config
* @return PDO * @return PDO
@ -15,7 +15,7 @@ public function connect($config)
// Format the initial Postgres PDO connection string. These options are required // Format the initial Postgres PDO connection string. These options are required
// for every Postgres connection that is established. The connection strings // for every Postgres connection that is established. The connection strings
// have the following convention: "pgsql:host=hostname;dbname=database" // have the following convention: "pgsql:host=hostname;dbname=database"
$dsn = sprintf('%s:host=%s;dbname=%s', $driver, $host, $database); $dsn = "pgsql:host={$host};dbname={$database}";
// Check for any optional Postgres PDO options. These options are not required // Check for any optional Postgres PDO options. These options are not required
// to establish a PDO connection; however, may be needed in certain server // to establish a PDO connection; however, may be needed in certain server

View File

@ -3,25 +3,7 @@
class SQLite extends Connector { class SQLite extends Connector {
/** /**
* The path to the SQLite databases for the application. * Establish a PDO database connection.
*
* @var string
*/
protected $path;
/**
* Create a new SQLite database connector instance.
*
* @param string $path
* @return void
*/
public function __construct($path)
{
$this->path = $path;
}
/**
* Establish a PDO database connection for a given database configuration.
* *
* @param array $config * @param array $config
* @return PDO * @return PDO
@ -32,27 +14,19 @@ public function connect($config)
// SQLite provides supported for "in-memory" databases, which exist only for the // SQLite provides supported for "in-memory" databases, which exist only for the
// lifetime of the request. Any given in-memory database may only have one PDO // lifetime of the request. Any given in-memory database may only have one PDO
// connection open to it at a time. Generally, these databases are use for // connection open to it at a time. Generally, these databases are used for
// testing and development purposes, not in production scenarios. // testing and development purposes, not in production scenarios.
if ($config['database'] == ':memory:') if ($config['database'] == ':memory:')
{ {
return new PDO('sqlite::memory:', null, null, $options); return new PDO('sqlite::memory:', null, null, $options);
} }
// First, we will check for the database in the default storage directory for the if (file_exists($path = DATABASE_PATH.$config['database'].'.sqlite'))
// application. If we don't find the database there, we will assume the database
// name is actually a full qualified path to the database on disk and attempt
// to load it. If we still can't find it, we'll bail out.
elseif (file_exists($path = $this->path.$config['database'].'.sqlite'))
{ {
return new PDO('sqlite:'.$path, null, null, $options); return new PDO('sqlite:'.$path, null, null, $options);
} }
elseif (file_exists($config['database']))
{
return new PDO('sqlite:'.$config['database'], null, null, $options);
}
throw new \OutOfBoundsException("SQLite database [{$config['database']}] could not be found."); throw new \Exception("SQLite database [{$config['database']}] could not be found.");
} }
} }

View File

@ -0,0 +1,38 @@
<?php namespace Laravel\Database\Connectors; use PDO;
class SQLServer extends Connector {
/**
* The PDO connection options.
*
* @var array
*/
protected $options = array(
PDO::ATTR_CASE => PDO::CASE_LOWER,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
);
/**
* Establish a PDO database connection.
*
* @param array $config
* @return PDO
*/
public function connect($config)
{
extract($config);
// Format the SQL Server connection string. This connection string format can
// also be used to connect to Azure SQL Server databases. The port is defined
// directly after the server name, so we'll create that and then create the
// final DSN string to pass to PDO.
$port = (isset($port)) ? ','.$port : '';
$dsn = "sqlsrv:Server={$host}{$port};Database={$database}";
return new PDO($dsn, $username, $password, $this->options($config));
}
}

View File

@ -1,212 +0,0 @@
<?php namespace Laravel\Database\Eloquent;
class Hydrator {
/**
* Load the array of hydrated models and their eager relationships.
*
* @param Model $eloquent
* @return array
*/
public static function hydrate($eloquent)
{
$results = static::base(get_class($eloquent), $eloquent->query->get());
if (count($results) > 0)
{
foreach ($eloquent->includes as $include)
{
if ( ! method_exists($eloquent, $include))
{
throw new \LogicException("Attempting to eager load [$include], but the relationship is not defined.");
}
static::eagerly($eloquent, $results, $include);
}
}
return $results;
}
/**
* Hydrate the base models for a query.
*
* The resulting model array is keyed by the primary keys of the models.
* This allows the models to easily be matched to their children.
*
* @param string $class
* @param array $results
* @return array
*/
private static function base($class, $results)
{
$models = array();
foreach ($results as $result)
{
$model = new $class;
$model->attributes = (array) $result;
$model->exists = true;
if (isset($model->attributes['id']))
{
$models[$model->id] = $model;
}
else
{
$models[] = $model;
}
}
return $models;
}
/**
* Eagerly load a relationship.
*
* @param object $eloquent
* @param array $parents
* @param string $include
* @return void
*/
private static function eagerly($eloquent, &$parents, $include)
{
// We temporarily spoof the query attributes to allow the query to be fetched without
// any problems, since the belongs_to method actually gets the related attribute.
$first = reset($parents);
$eloquent->attributes = $first->attributes;
$relationship = $eloquent->$include();
$eloquent->attributes = array();
// Reset the WHERE clause and bindings on the query. We'll add our own WHERE clause soon.
// This will allow us to load a range of related models instead of only one.
$relationship->query->reset_where();
// Initialize the relationship attribute on the parents. As expected, "many" relationships
// are initialized to an array and "one" relationships are initialized to null.
foreach ($parents as &$parent)
{
$parent->ignore[$include] = (in_array($eloquent->relating, array('has_many', 'has_and_belongs_to_many'))) ? array() : null;
}
if (in_array($relating = $eloquent->relating, array('has_one', 'has_many', 'belongs_to')))
{
return static::$relating($relationship, $parents, $eloquent->relating_key, $include);
}
else
{
static::has_and_belongs_to_many($relationship, $parents, $eloquent->relating_key, $eloquent->relating_table, $include);
}
}
/**
* Eagerly load a 1:1 relationship.
*
* @param object $relationship
* @param array $parents
* @param string $relating_key
* @param string $relating
* @param string $include
* @return void
*/
private static function has_one($relationship, &$parents, $relating_key, $include)
{
foreach ($relationship->where_in($relating_key, array_keys($parents))->get() as $key => $child)
{
$parents[$child->$relating_key]->ignore[$include] = $child;
}
}
/**
* Eagerly load a 1:* relationship.
*
* @param object $relationship
* @param array $parents
* @param string $relating_key
* @param string $relating
* @param string $include
* @return void
*/
private static function has_many($relationship, &$parents, $relating_key, $include)
{
foreach ($relationship->where_in($relating_key, array_keys($parents))->get() as $key => $child)
{
$parents[$child->$relating_key]->ignore[$include][$child->id] = $child;
}
}
/**
* Eagerly load a 1:1 belonging relationship.
*
* @param object $relationship
* @param array $parents
* @param string $relating_key
* @param string $include
* @return void
*/
private static function belongs_to($relationship, &$parents, $relating_key, $include)
{
$keys = array();
foreach ($parents as &$parent)
{
$keys[] = $parent->$relating_key;
}
$children = $relationship->where_in('id', array_unique($keys))->get();
foreach ($parents as &$parent)
{
if (array_key_exists($parent->$relating_key, $children))
{
$parent->ignore[$include] = $children[$parent->$relating_key];
}
}
}
/**
* Eagerly load a many-to-many relationship.
*
* @param object $relationship
* @param array $parents
* @param string $relating_key
* @param string $relating_table
* @param string $include
*
* @return void
*/
private static function has_and_belongs_to_many($relationship, &$parents, $relating_key, $relating_table, $include)
{
// The model "has and belongs to many" method sets the SELECT clause; however, we need
// to clear it here since we will be adding the foreign key to the select.
$relationship->query->select = null;
$relationship->query->where_in($relating_table.'.'.$relating_key, array_keys($parents));
// The foreign key is added to the select to allow us to easily match the models back to their parents.
// Otherwise, there would be no apparent connection between the models to allow us to match them.
$children = $relationship->query->get(array(Model::table(get_class($relationship)).'.*', $relating_table.'.'.$relating_key));
$class = get_class($relationship);
foreach ($children as $child)
{
$related = new $class;
$related->attributes = (array) $child;
$related->exists = true;
// Remove the foreign key since it was only added to the query to help match the models.
unset($related->attributes[$relating_key]);
$parents[$child->$relating_key]->ignore[$include][$child->id] = $related;
}
}
}

View File

@ -1,538 +0,0 @@
<?php namespace Laravel\Database\Eloquent;
use Laravel\Str;
use Laravel\Inflector;
use Laravel\Paginator;
use Laravel\Database\Manager as DB;
abstract class Model {
/**
* The connection that should be used for the model.
*
* @var string
*/
public static $connection;
/**
* Indicates if the model has creation and update timestamps.
*
* @var bool
*/
public static $timestamps = false;
/**
* The name of the auto-incrementing sequence associated with the model.
*
* @var string
*/
public static $sequence = null;
/**
* The model query instance.
*
* @var Query
*/
public $query;
/**
* Indicates if the model exists in the database.
*
* @var bool
*/
public $exists = false;
/**
* The model's attributes.
*
* Typically, a model has an attribute for each column on the table.
*
* @var array
*/
public $attributes = array();
/**
* The model's dirty attributes.
*
* @var array
*/
public $dirty = array();
/**
* The model's ignored attributes.
*
* Ignored attributes will not be saved to the database, and are
* primarily used to hold relationships.
*
* @var array
*/
public $ignore = array();
/**
* The relationships that should be eagerly loaded.
*
* @var array
*/
public $includes = array();
/**
* The relationship type the model is currently resolving.
*
* @var string
*/
public $relating;
/**
* The foreign key of the "relating" relationship.
*
* @var string
*/
public $relating_key;
/**
* The table name of the model being resolved.
*
* This is used during many-to-many eager loading.
*
* @var string
*/
public $relating_table;
/**
* Create a new Eloquent model instance.
*
* @param array $attributes
* @return void
*/
public function __construct($attributes = array())
{
$this->fill($attributes);
}
/**
* Set the attributes of the model using an array.
*
* @param array $attributes
* @return Model
*/
public function fill($attributes)
{
foreach ($attributes as $key => $value)
{
$this->$key = $value;
}
return $this;
}
/**
* Set the eagerly loaded models on the queryable model.
*
* @return Model
*/
private function _with()
{
$this->includes = func_get_args();
return $this;
}
/**
* Factory for creating queryable Eloquent model instances.
*
* @param string $class
* @return object
*/
public static function query($class)
{
$model = new $class;
// Since this method is only used for instantiating models for querying
// purposes, we will go ahead and set the Query instance on the model.
$model->query = DB::connection(static::$connection)->table(static::table($class));
return $model;
}
/**
* Get the table name for a model.
*
* @param string $class
* @return string
*/
public static function table($class)
{
if (property_exists($class, 'table')) return $class::$table;
return strtolower(Inflector::plural(static::model_name($class)));
}
/**
* Get an Eloquent model name without any namespaces.
*
* @param string|Model $model
* @return string
*/
public static function model_name($model)
{
$class = (is_object($model)) ? get_class($model) : $model;
$segments = array_reverse(explode('\\', $class));
return $segments[0];
}
/**
* Get all of the models from the database.
*
* @return array
*/
public static function all()
{
return Hydrator::hydrate(static::query(get_called_class()));
}
/**
* Get a model by the primary key.
*
* @param int $id
* @return mixed
*/
public static function find($id)
{
return static::query(get_called_class())->where('id', '=', $id)->first();
}
/**
* Get an array of models from the database.
*
* @return array
*/
private function _get()
{
return Hydrator::hydrate($this);
}
/**
* Get the first model result
*
* @return mixed
*/
private function _first()
{
return (count($results = $this->take(1)->_get()) > 0) ? reset($results) : null;
}
/**
* Get paginated model results as a Paginator instance.
*
* @param int $per_page
* @return Paginator
*/
private function _paginate($per_page = null)
{
$total = $this->query->count();
// The number of models to show per page may be specified as a static property
// on the model. The models shown per page may also be overriden for the model
// by passing the number into this method. If the models to show per page is
// not available via either of these avenues, a default number will be shown.
if (is_null($per_page))
{
$per_page = (property_exists(get_class($this), 'per_page')) ? static::$per_page : 20;
}
return Paginator::make($this->for_page(Paginator::page($total, $per_page), $per_page)->get(), $total, $per_page);
}
/**
* Retrieve the query for a 1:1 relationship.
*
* @param string $model
* @param string $foreign_key
* @return mixed
*/
public function has_one($model, $foreign_key = null)
{
$this->relating = __FUNCTION__;
return $this->has_one_or_many($model, $foreign_key);
}
/**
* Retrieve the query for a 1:* relationship.
*
* @param string $model
* @param string $foreign_key
* @return mixed
*/
public function has_many($model, $foreign_key = null)
{
$this->relating = __FUNCTION__;
return $this->has_one_or_many($model, $foreign_key);
}
/**
* Retrieve the query for a 1:1 or 1:* relationship.
*
* The default foreign key for has one and has many relationships is the name
* of the model with an appended _id. For example, the foreign key for a
* User model would be user_id. Photo would be photo_id, etc.
*
* @param string $model
* @param string $foreign_key
* @return mixed
*/
private function has_one_or_many($model, $foreign_key)
{
$this->relating_key = (is_null($foreign_key)) ? strtolower(static::model_name($this)).'_id' : $foreign_key;
return static::query($model)->where($this->relating_key, '=', $this->id);
}
/**
* Retrieve the query for a 1:1 belonging relationship.
*
* The default foreign key for belonging relationships is the name of the
* relationship method name with _id. So, if a model has a "manager" method
* returning a belongs_to relationship, the key would be manager_id.
*
* @param string $model
* @param string $foreign_key
* @return mixed
*/
public function belongs_to($model, $foreign_key = null)
{
$this->relating = __FUNCTION__;
if ( ! is_null($foreign_key))
{
$this->relating_key = $foreign_key;
}
else
{
list(, $caller) = debug_backtrace(false);
$this->relating_key = $caller['function'].'_id';
}
return static::query($model)->where('id', '=', $this->attributes[$this->relating_key]);
}
/**
* Retrieve the query for a *:* relationship.
*
* The default foreign key for many-to-many relations is the name of the model
* with an appended _id. This is the same convention as has_one and has_many.
*
* @param string $model
* @param string $table
* @param string $foreign_key
* @param string $associated_key
* @return mixed
*/
public function has_and_belongs_to_many($model, $table = null, $foreign_key = null, $associated_key = null)
{
$this->relating = __FUNCTION__;
$this->relating_table = (is_null($table)) ? $this->intermediate_table($model) : $table;
// Allowing the overriding of the foreign and associated keys provides
// the flexibility for self-referential many-to-many relationships.
$this->relating_key = (is_null($foreign_key)) ? strtolower(static::model_name($this)).'_id' : $foreign_key;
// The associated key is the foreign key name of the related model.
// If the related model is "Role", the key would be "role_id".
$associated_key = (is_null($associated_key)) ? strtolower(static::model_name($model)).'_id' : $associated_key;
return static::query($model)
->select(array(static::table($model).'.*'))
->join($this->relating_table, static::table($model).'.id', '=', $this->relating_table.'.'.$associated_key)
->where($this->relating_table.'.'.$this->relating_key, '=', $this->id);
}
/**
* Determine the intermediate table name for a given model.
*
* By default, the intermediate table name is the plural names of the models
* arranged alphabetically and concatenated with an underscore.
*
* @param string $model
* @return string
*/
private function intermediate_table($model)
{
$models = array(Inflector::plural(static::model_name($model)), Inflector::plural(static::model_name($this)));
sort($models);
return strtolower($models[0].'_'.$models[1]);
}
/**
* Save the model to the database.
*
* @return bool
*/
public function save()
{
// If the model does not have any dirty attributes, there is no reason
// to save it to the database.
if ($this->exists and count($this->dirty) == 0) return true;
$model = get_class($this);
// Since the model was instantiated using "new", a query instance has not been set.
// Only models being used for querying have their query instances set by default.
$this->query = DB::connection(static::$connection)->table(static::table($model));
if (property_exists($model, 'timestamps') and $model::$timestamps)
{
$this->timestamp();
}
// If the model already exists in the database, we will just update it.
// Otherwise, we will insert the model and set the ID attribute.
if ($this->exists)
{
$success = ($this->query->where_id($this->attributes['id'])->update($this->dirty) === 1);
}
else
{
$success = is_numeric($this->attributes['id'] = $this->query->insert_get_id($this->attributes, static::$sequence));
}
($this->exists = true) and $this->dirty = array();
return $success;
}
/**
* Set the creation and update timestamps on the model.
*
* @return void
*/
protected function timestamp()
{
$this->updated_at = date('Y-m-d H:i:s');
if ( ! $this->exists) $this->created_at = $this->updated_at;
}
/**
* Delete a model from the database.
*
* @param int $id
* @return int
*/
public function delete($id = null)
{
// If the delete method is being called on an existing model, we only want to delete
// that model. If it is being called from an Eloquent query model, it is probably
// the developer's intention to delete more than one model, so we will pass the
// delete statement to the query instance.
if ( ! $this->exists) return $this->query->delete();
$table = static::table(get_class($this));
return DB::connection(static::$connection)->table($table)->delete($this->id);
}
/**
* Magic method for retrieving model attributes.
*/
public function __get($key)
{
if (array_key_exists($key, $this->attributes))
{
return $this->attributes[$key];
}
// Is the requested item a model relationship that has already been loaded?
// All of the loaded relationships are stored in the "ignore" array.
elseif (array_key_exists($key, $this->ignore))
{
return $this->ignore[$key];
}
// Is the requested item a model relationship? If it is, we will dynamically
// load it and return the results of the relationship query.
elseif (method_exists($this, $key))
{
$query = $this->$key();
return $this->ignore[$key] = (in_array($this->relating, array('has_one', 'belongs_to'))) ? $query->first() : $query->get();
}
}
/**
* Magic Method for setting model attributes.
*/
public function __set($key, $value)
{
// If the key is a relationship, add it to the ignored attributes.
// Ignored attributes are not stored in the database.
if (method_exists($this, $key))
{
$this->ignore[$key] = $value;
}
else
{
$this->attributes[$key] = $value;
$this->dirty[$key] = $value;
}
}
/**
* Magic Method for determining if a model attribute is set.
*/
public function __isset($key)
{
return (array_key_exists($key, $this->attributes) or array_key_exists($key, $this->ignore));
}
/**
* Magic Method for unsetting model attributes.
*/
public function __unset($key)
{
unset($this->attributes[$key], $this->ignore[$key], $this->dirty[$key]);
}
/**
* Magic Method for handling dynamic method calls.
*/
public function __call($method, $parameters)
{
// To allow the "with", "get", "first", and "paginate" methods to be called both
// staticly and on an instance, we need to have private, underscored versions
// of the methods and handle them dynamically.
if (in_array($method, array('with', 'get', 'first', 'paginate')))
{
return call_user_func_array(array($this, '_'.$method), $parameters);
}
// All of the aggregate and persistance functions can be passed directly to the query
// instance. For these functions, we can simply return the response of the query.
if (in_array($method, array('insert', 'update', 'increment', 'decrement', 'abs', 'count', 'sum', 'min', 'max', 'avg')))
{
return call_user_func_array(array($this->query, $method), $parameters);
}
// Pass the method to the query instance. This allows the chaining of methods
// from the query builder, providing the same convenient query API as the
// query builder itself.
call_user_func_array(array($this->query, $method), $parameters);
return $this;
}
/**
* Magic Method for handling dynamic static method calls.
*/
public static function __callStatic($method, $parameters)
{
// Just pass the method to a model instance and let the __call method take care of it.
return call_user_func_array(array(static::query(get_called_class()), $method), $parameters);
}
}

View File

@ -0,0 +1,108 @@
<?php namespace Laravel\Database;
abstract class Grammar {
/**
* The keyword identifier for the database system.
*
* @var string
*/
protected $wrapper = '"%s"';
/**
* Wrap a value in keyword identifiers.
*
* @param string $value
* @return string
*/
public function wrap($value)
{
// If the value being wrapped contains a column alias, we need to
// wrap it a little differently as each segment must be wrapped
// and not the entire string. We'll split the value on the "as"
// joiner to extract the column and the alias.
if (strpos(strtolower($value), ' as ') !== false)
{
$segments = explode(' ', $value);
return $this->wrap($segments[0]).' AS '.$this->wrap($segments[2]);
}
// Expressions should be injected into the query as raw strings,
// so we do not want to wrap them in any way. We'll just return
// the string value from the expression to be included.
if ($value instanceof Expression) return $value->get();
// Since columns may be prefixed with their corresponding table
// name so as to not make them ambiguous, we will need to wrap
// the table and the column in keyword identifiers.
foreach (explode('.', $value) as $segment)
{
if ($segment == '*')
{
$wrapped[] = $segment;
}
else
{
$wrapped[] = sprintf($this->wrapper, $segment);
}
}
return implode('.', $wrapped);
}
/**
* Create query parameters from an array of values.
*
* <code>
* Returns "?, ?, ?", which may be used as PDO place-holders
* $parameters = $grammar->parameterize(array(1, 2, 3));
*
* // Returns "?, "Taylor"" since an expression is used
* $parameters = $grammar->parameterize(array(1, DB::raw('Taylor')));
* </code>
*
* @param array $values
* @return string
*/
final public function parameterize($values)
{
return implode(', ', array_map(array($this, 'parameter'), $values));
}
/**
* Get the appropriate query parameter string for a value.
*
* <code>
* // Returns a "?" PDO place-holder
* $value = $grammar->parameter('Taylor Otwell');
*
* // Returns "Taylor Otwell" as the raw value of the expression
* $value = $grammar->parameter(DB::raw('Taylor Otwell'));
* </code>
*
* @param mixed $value
* @return string
*/
final public function parameter($value)
{
return ($value instanceof Expression) ? $value->get() : '?';
}
/**
* Create a comma-delimited list of wrapped column names.
*
* <code>
* // Returns ""Taylor", "Otwell"" when the identifier is quotes
* $columns = $grammar->columnize(array('Taylor', 'Otwell'));
* </code>
*
* @param array $columns
* @return string
*/
final public function columnize($columns)
{
return implode(', ', array_map(array($this, 'wrap'), $columns));
}
}

View File

@ -1,4 +1,10 @@
<?php namespace Laravel\Database; use Laravel\Paginator; <?php namespace Laravel\Database;
use Closure;
use Laravel\Database;
use Laravel\Paginator;
use Laravel\Database\Query\Grammars\Grammar;
use Laravel\Database\Query\Grammars\SQLServer;
class Query { class Query {
@ -12,7 +18,7 @@ class Query {
/** /**
* The query grammar instance. * The query grammar instance.
* *
* @var Grammars\Grammar * @var Query\Grammars\Grammar
*/ */
public $grammar; public $grammar;
@ -58,6 +64,13 @@ class Query {
*/ */
public $wheres; public $wheres;
/**
* The GROUP BY clauses.
*
* @var array
*/
public $groupings;
/** /**
* The ORDER BY clauses. * The ORDER BY clauses.
* *
@ -89,12 +102,12 @@ class Query {
/** /**
* Create a new query instance. * Create a new query instance.
* *
* @param Connection $connection * @param Connection $connection
* @param Grammars\Grammar $grammar * @param Grammar $grammar
* @param string $table * @param string $table
* @return void * @return void
*/ */
public function __construct(Connection $connection, Grammars\Grammar $grammar, $table) public function __construct(Connection $connection, Grammar $grammar, $table)
{ {
$this->from = $table; $this->from = $table;
$this->grammar = $grammar; $this->grammar = $grammar;
@ -156,7 +169,7 @@ public function left_join($table, $column1, $operator, $column2)
} }
/** /**
* Reset the where clause to its initial state. All bindings will be cleared. * Reset the where clause to its initial state.
* *
* @return void * @return void
*/ */
@ -203,8 +216,16 @@ public function raw_or_where($where, $bindings = array())
* @param string $connector * @param string $connector
* @return Query * @return Query
*/ */
public function where($column, $operator, $value, $connector = 'AND') public function where($column, $operator = null, $value = null, $connector = 'AND')
{ {
// If a CLosure is passed into the method, it means a nested where
// clause is being initiated, so we will take a different course
// of action than when the statement is just a simple where.
if ($column instanceof Closure)
{
return $this->where_nested($column, $connector);
}
$type = 'where'; $type = 'where';
$this->wheres[] = compact('type', 'column', 'operator', 'value', 'connector'); $this->wheres[] = compact('type', 'column', 'operator', 'value', 'connector');
@ -222,7 +243,7 @@ public function where($column, $operator, $value, $connector = 'AND')
* @param mixed $value * @param mixed $value
* @return Query * @return Query
*/ */
public function or_where($column, $operator, $value) public function or_where($column, $operator = null, $value = null)
{ {
return $this->where($column, $operator, $value, 'OR'); return $this->where($column, $operator, $value, 'OR');
} }
@ -347,10 +368,35 @@ public function or_where_not_null($column)
} }
/** /**
* Add dynamic where conditions to the query. * Add a nested where condition to the query.
* *
* Dynamic queries are caught by the __call magic method and are parsed here. * @param Closure $callback
* They provide a convenient, expressive API for building simple conditions. * @param string $connector
* @return Query
*/
protected function where_nested($callback, $connector)
{
$type = 'where_nested';
// To handle a nested where statement, we will actually instantiate a
// new Query instance and run the callback over that instance, which
// will allow the developer to have a fresh query to work with.
$query = new Query($this->connection, $this->grammar, $this->from);
// Once the callback has been run on the query, we will store the
// nested query instance on the where clause array so that it's
// passed to the query grammar.
call_user_func($callback, $query);
$this->wheres[] = compact('type', 'query', 'connector');
$this->bindings = array_merge($this->bindings, $query->bindings);
return $this;
}
/**
* Add dynamic where conditions to the query.
* *
* @param string $method * @param string $method
* @param array $parameters * @param array $parameters
@ -358,11 +404,11 @@ public function or_where_not_null($column)
*/ */
private function dynamic_where($method, $parameters) private function dynamic_where($method, $parameters)
{ {
// Strip the "where_" off of the method.
$finder = substr($method, 6); $finder = substr($method, 6);
// Split the column names from the connectors. $flags = PREG_SPLIT_DELIM_CAPTURE;
$segments = preg_split('/(_and_|_or_)/i', $finder, -1, PREG_SPLIT_DELIM_CAPTURE);
$segments = preg_split('/(_and_|_or_)/i', $finder, -1, $flags);
// The connector variable will determine which connector will be // The connector variable will determine which connector will be
// used for the condition. We'll change it as we come across new // used for the condition. We'll change it as we come across new
@ -377,6 +423,13 @@ private function dynamic_where($method, $parameters)
foreach ($segments as $segment) foreach ($segments as $segment)
{ {
// If the segment is not a boolean connector, we can assume it
// it is a column name, and we'll add it to the query as a new
// where clause.
//
// Otherwise, we'll store the connector so that we know how to
// connection the next where clause we find to the query, as
// all connectors should precede a new where clause.
if ($segment != '_and_' and $segment != '_or_') if ($segment != '_and_' and $segment != '_or_')
{ {
$this->where($segment, '=', $parameters[$index], $connector); $this->where($segment, '=', $parameters[$index], $connector);
@ -392,6 +445,18 @@ private function dynamic_where($method, $parameters)
return $this; return $this;
} }
/**
* Add a grouping to the query.
*
* @param string $column
* @return Query
*/
public function group_by($column)
{
$this->groupings[] = $column;
return $this;
}
/** /**
* Add an ordering to the query. * Add an ordering to the query.
* *
@ -430,7 +495,7 @@ public function take($value)
} }
/** /**
* Set the query limit and offset for a given page and item per page count. * Set the query limit and offset for a given page.
* *
* @param int $page * @param int $page
* @param int $per_page * @param int $per_page
@ -461,16 +526,14 @@ public function find($id, $columns = array('*'))
*/ */
public function only($column) public function only($column)
{ {
$this->select(array($column)); $sql = $this->grammar->select($this->select(array($column)));
return $this->connection->only($this->grammar->select($this), $this->bindings); return $this->connection->only($sql, $this->bindings);
} }
/** /**
* Execute the query as a SELECT statement and return the first result. * Execute the query as a SELECT statement and return the first result.
* *
* If a single column is selected from the database, only the value of that column will be returned.
*
* @param array $columns * @param array $columns
* @return mixed * @return mixed
*/ */
@ -478,7 +541,51 @@ public function first($columns = array('*'))
{ {
$columns = (array) $columns; $columns = (array) $columns;
return (count($results = $this->take(1)->get($columns)) > 0) ? $results[0] : null; // Since we only need the first result, we'll go ahead and set the
// limit clause to 1, since this will be much faster than getting
// all of the rows and then only returning the first.
$results = $this->take(1)->get($columns);
return (count($results) > 0) ? $results[0] : null;
}
/**
* Get an array with the values of a given column.
*
* @param string $column
* @param string $key
* @return array
*/
public function lists($column, $key = null)
{
$columns = (is_null($key)) ? array($column) : array($column, $key);
$results = $this->get($columns);
// First we will get the array of values for the requested column.
// Of course, this array will simply have numeric keys. After we
// have this array we will determine if we need to key the array
// by another column from the result set.
$values = array_map(function($row) use ($column)
{
return $row->$column;
}, $results);
// If a key was provided, we will extract an array of keys and
// set the keys on the array of values using the array_combine
// function provided by PHP, which should give us the proper
// array form to return from the method.
if ( ! is_null($key))
{
return array_combine(array_map(function($row) use ($key)
{
return $row->$key;
}, $results), $values);
}
return $values;
} }
/** /**
@ -491,11 +598,25 @@ public function get($columns = array('*'))
{ {
if (is_null($this->selects)) $this->select($columns); if (is_null($this->selects)) $this->select($columns);
$results = $this->connection->query($this->grammar->select($this), $this->bindings); $sql = $this->grammar->select($this);
$results = $this->connection->query($sql, $this->bindings);
// If the query has an offset and we are using the SQL Server grammar,
// we need to spin through the results and remove the "rownum" from
// each of the objects. Unfortunately SQL Server does not have an
// offset keyword, so we have to use row numbers in the query.
if ($this->offset > 0 and $this->grammar instanceof SQLServer)
{
array_walk($results, function($result)
{
unset($result->rownum);
});
}
// Reset the SELECT clause so more queries can be performed using // Reset the SELECT clause so more queries can be performed using
// the same instance. This is helpful for getting aggregates and // the same instance. This is helpful for getting aggregates and
// then getting actual results. // then getting actual results from the query.
$this->selects = null; $this->selects = null;
return $results; return $results;
@ -512,11 +633,13 @@ private function aggregate($aggregator, $column)
{ {
$this->aggregate = compact('aggregator', 'column'); $this->aggregate = compact('aggregator', 'column');
$result = $this->connection->only($this->grammar->select($this), $this->bindings); $sql = $this->grammar->select($this);
$result = $this->connection->only($sql, $this->bindings);
// Reset the aggregate so more queries can be performed using // Reset the aggregate so more queries can be performed using
// the same instance. This is helpful for getting aggregates // the same instance. This is helpful for getting aggregates
// and then getting actual results. // and then getting actual results from the query.
$this->aggregate = null; $this->aggregate = null;
return $result; return $result;
@ -531,17 +654,23 @@ private function aggregate($aggregator, $column)
*/ */
public function paginate($per_page = 20, $columns = array('*')) public function paginate($per_page = 20, $columns = array('*'))
{ {
// Because some database engines may throw errors if we leave // Because some database engines may throw errors if we leave orderings
// orderings on the query when retrieving the total number // on the query when retrieving the total number of records, we will
// of records, we will remove all of the ordreings and put // remove all of the ordreings and put them back on the query after
// them back on the query after we have the count. // we have the count.
list($orderings, $this->orderings) = array($this->orderings, null); list($orderings, $this->orderings) = array($this->orderings, null);
$page = Paginator::page($total = $this->count(), $per_page); $page = Paginator::page($total = $this->count(), $per_page);
$this->orderings = $orderings; $this->orderings = $orderings;
return Paginator::make($this->for_page($page, $per_page)->get($columns), $total, $per_page); // Now we're ready to get the actual pagination results from the
// database table. The "for_page" method provides a convenient
// way to set the limit and offset so we get the correct span
// of results from the table.
$results = $this->for_page($page, $per_page)->get($columns);
return Paginator::make($results, $total, $per_page);
} }
/** /**
@ -559,17 +688,21 @@ public function insert($values)
$bindings = array(); $bindings = array();
// We need to merge the the insert values into the array of the query
// bindings so that they will be bound to the PDO statement when it
// is executed by the database connection.
foreach ($values as $value) foreach ($values as $value)
{ {
$bindings = array_merge($bindings, array_values($value)); $bindings = array_merge($bindings, array_values($value));
} }
return $this->connection->query($this->grammar->insert($this, $values), $bindings); $sql = $this->grammar->insert($this, $values);
return $this->connection->statement($sql, $bindings);
} }
/** /**
* Insert an array of values into the database table and * Insert an array of values into the database table and return the ID.
* return the value of the ID column.
* *
* @param array $values * @param array $values
* @param string $sequence * @param string $sequence
@ -577,8 +710,13 @@ public function insert($values)
*/ */
public function insert_get_id($values, $sequence = null) public function insert_get_id($values, $sequence = null)
{ {
$this->connection->query($this->grammar->insert($this, $values), array_values($values)); $sql = $this->grammar->insert($this, $values);
$this->connection->statement($sql, array_values($values));
// Some database systems (Postgres) require a sequence name to be
// given when retrieving the auto-incrementing ID, so we'll pass
// the given sequence into the method just in case.
return (int) $this->connection->pdo->lastInsertId($sequence); return (int) $this->connection->pdo->lastInsertId($sequence);
} }
@ -616,7 +754,10 @@ public function decrement($column, $amount = 1)
*/ */
protected function adjust($column, $amount, $operator) protected function adjust($column, $amount, $operator)
{ {
$value = Manager::raw($this->grammar->wrap($column).$operator.$amount); // To make the adjustment to the column, we'll wrap the expression
// in an Expression instance, which forces the adjustment to be
// injected into the query as a string instead of bound.
$value = Database::raw($this->grammar->wrap($column).$operator.$amount);
return $this->update(array($column => $value)); return $this->update(array($column => $value));
} }
@ -629,9 +770,15 @@ protected function adjust($column, $amount, $operator)
*/ */
public function update($values) public function update($values)
{ {
// For update statements, we need to merge the bindings such that
// the update values occur before the where bindings in the array
// since the set statements will precede any of the where clauses
// in the SQL syntax that is generated.
$bindings = array_merge(array_values($values), $this->bindings); $bindings = array_merge(array_values($values), $this->bindings);
return $this->connection->query($this->grammar->update($this, $values), $bindings); $sql = $this->grammar->update($this, $values);
return $this->connection->update($sql, $bindings);
} }
/** /**
@ -644,16 +791,23 @@ public function update($values)
*/ */
public function delete($id = null) public function delete($id = null)
{ {
if ( ! is_null($id)) $this->where('id', '=', $id); // If an ID is given to the method, we'll set the where clause
// to match on the value of the ID. This allows the developer
// to quickly delete a row by its primary key value.
if ( ! is_null($id))
{
$this->where('id', '=', $id);
}
return $this->connection->query($this->grammar->delete($this), $this->bindings); $sql = $this->grammar->delete($this);
return $this->connection->delete($sql, $this->bindings);
} }
/** /**
* Magic Method for handling dynamic functions. * Magic Method for handling dynamic functions.
* *
* This method handles all calls to aggregate functions as well * This method handles calls to aggregates as well as dynamic where clauses.
* as the construction of dynamic where clauses.
*/ */
public function __call($method, $parameters) public function __call($method, $parameters)
{ {
@ -662,7 +816,7 @@ public function __call($method, $parameters)
return $this->dynamic_where($method, $parameters, $this); return $this->dynamic_where($method, $parameters, $this);
} }
if (in_array($method, array('abs', 'count', 'min', 'max', 'avg', 'sum'))) if (in_array($method, array('count', 'min', 'max', 'avg', 'sum')))
{ {
if ($method == 'count') if ($method == 'count')
{ {
@ -674,7 +828,7 @@ public function __call($method, $parameters)
} }
} }
throw new \BadMethodCallException("Method [$method] is not defined on the Query class."); throw new \Exception("Method [$method] is not defined on the Query class.");
} }
} }

View File

@ -1,61 +1,70 @@
<?php namespace Laravel\Database\Grammars; <?php namespace Laravel\Database\Query\Grammars;
use Laravel\Arr;
use Laravel\Database\Query; use Laravel\Database\Query;
use Laravel\Database\Expression; use Laravel\Database\Expression;
class Grammar { class Grammar extends \Laravel\Database\Grammar {
/**
* The keyword identifier for the database system.
*
* @var string
*/
protected $wrapper = '"';
/** /**
* All of the query componenets in the order they should be built. * All of the query componenets in the order they should be built.
* *
* Each derived compiler may adjust these components and place them in the
* order needed for its particular database system, providing greater
* control over how the query is structured.
*
* @var array * @var array
*/ */
protected $components = array( protected $components = array(
'aggregate', 'aggregate', 'selects', 'from', 'joins', 'wheres',
'selects', 'groupings', 'orderings', 'limit', 'offset',
'from',
'joins',
'wheres',
'orderings',
'limit',
'offset'
); );
/** /**
* Compile a SQL SELECT statement from a Query instance. * Compile a SQL SELECT statement from a Query instance.
* *
* The query will be compiled according to the order of the elements specified
* in the "components" property. The entire query is passed into each component
* compiler for convenience.
*
* @param Query $query * @param Query $query
* @return string * @return string
*/ */
final public function select(Query $query) public function select(Query $query)
{ {
$sql = array(); return $this->concatenate($this->components($query));
}
/**
* Generate the SQL for every component of the query.
*
* @param Query $query
* @return array
*/
final protected function components($query)
{
// Each portion of the statement is compiled by a function corresponding
// to an item in the components array. This lets us to keep the creation
// of the query very granular, and allows for the flexible customization
// of the query building process by each database system's grammar.
//
// Note that each component also corresponds to a public property on the
// query instance, allowing us to pass the appropriate data into each of
// the compiler functions.
foreach ($this->components as $component) foreach ($this->components as $component)
{ {
if ( ! is_null($query->$component)) if ( ! is_null($query->$component))
{ {
$sql[] = call_user_func(array($this, $component), $query); $sql[$component] = call_user_func(array($this, $component), $query);
} }
} }
return implode(' ', Arr::without($sql, array(null, ''))); return (array) $sql;
}
/**
* Concatenate an array of SQL segments, removing those that are empty.
*
* @param array $components
* @return string
*/
final protected function concatenate($components)
{
return implode(' ', array_filter($components, function($value)
{
return (string) $value !== '';
}));
} }
/** /**
@ -74,11 +83,6 @@ protected function selects(Query $query)
/** /**
* Compile an aggregating SELECT clause for a query. * Compile an aggregating SELECT clause for a query.
* *
* If an aggregate function is called on the query instance, no select
* columns will be set, so it is safe to assume that the "selects"
* compiler function will not be called. We can simply build the
* aggregating select clause within this function.
*
* @param Query $query * @param Query $query
* @return string * @return string
*/ */
@ -92,9 +96,6 @@ protected function aggregate(Query $query)
/** /**
* Compile the FROM clause for a query. * Compile the FROM clause for a query.
* *
* This method should not handle the construction of "join" clauses.
* The join clauses will be constructured by their own compiler.
*
* @param Query $query * @param Query $query
* @return string * @return string
*/ */
@ -111,8 +112,13 @@ protected function from(Query $query)
*/ */
protected function joins(Query $query) protected function joins(Query $query)
{ {
$format = '%s JOIN %s ON %s %s %s'; // We need to iterate through each JOIN clause that is attached to the
// query an translate it into SQL. The table and the columns will be
// wrapped in identifiers to avoid naming collisions.
//
// Once all of the JOINs have been compiled, we can concatenate them
// together using a single space, which should give us the complete
// set of joins in valid SQL that can appended to the query.
foreach ($query->joins as $join) foreach ($query->joins as $join)
{ {
$table = $this->wrap($join['table']); $table = $this->wrap($join['table']);
@ -121,7 +127,7 @@ protected function joins(Query $query)
$column2 = $this->wrap($join['column2']); $column2 = $this->wrap($join['column2']);
$sql[] = sprintf($format, $join['type'], $table, $column1, $join['operator'], $column2); $sql[] = "{$join['type']} JOIN {$table} ON {$column1} {$join['operator']} {$column2}";
} }
return implode(' ', $sql); return implode(' ', $sql);
@ -136,26 +142,44 @@ protected function joins(Query $query)
final protected function wheres(Query $query) final protected function wheres(Query $query)
{ {
// Each WHERE clause array has a "type" that is assigned by the query // Each WHERE clause array has a "type" that is assigned by the query
// builder, and each type has its own compiler function. We will simply // builder, and each type has its own compiler function. We will call
// iterate through the where clauses and call the appropriate compiler // the appropriate compiler for each where clause in the query.
// for each clause. //
// Keeping each particular where clause in its own "compiler" allows
// us to keep the query generation process very granular, making it
// easier to customize derived grammars for other databases.
foreach ($query->wheres as $where) foreach ($query->wheres as $where)
{ {
$sql[] = $where['connector'].' '.$this->{$where['type']}($where); $sql[] = $where['connector'].' '.$this->{$where['type']}($where);
} }
if (isset($sql)) return implode(' ', array_merge(array('WHERE 1 = 1'), $sql)); if (isset($sql))
{
// We attach the boolean connector to every where segment just
// for convenience. Once we have built the entire clause we'll
// remove the first instance of a connector from the clause.
return 'WHERE '.preg_replace('/AND |OR /', '', implode(' ', $sql), 1);
}
}
/**
* Compile a nested WHERE clause.
*
* @param array $where
* @return string
*/
protected function where_nested($where)
{
// To generate a nested WHERE clause, we'll just feed the query
// back into the "wheres" method. Once we have the clause, we
// will strip off the first six characters to get rid of the
// leading WHERE keyword.
return '('.substr($this->wheres($where['query']), 6).')';
} }
/** /**
* Compile a simple WHERE clause. * Compile a simple WHERE clause.
* *
* This method handles the compilation of the structures created by the
* "where" and "or_where" methods on the query builder.
*
* This method also handles database expressions, so care must be taken
* to implement this functionality in any derived database grammars.
*
* @param array $where * @param array $where
* @return string * @return string
*/ */
@ -220,11 +244,22 @@ protected function where_not_null($where)
* @param array $where * @param array $where
* @return string * @return string
*/ */
protected function where_raw($where) final protected function where_raw($where)
{ {
return $where['sql']; return $where['sql'];
} }
/**
* Compile the GROUP BY clause for a query.
*
* @param Query $query
* @return string
*/
protected function groupings(Query $query)
{
return 'GROUP BY '.$this->columnize($query->groupings);
}
/** /**
* Compile the ORDER BY clause for a query. * Compile the ORDER BY clause for a query.
* *
@ -235,7 +270,9 @@ protected function orderings(Query $query)
{ {
foreach ($query->orderings as $ordering) foreach ($query->orderings as $ordering)
{ {
$sql[] = $this->wrap($ordering['column']).' '.strtoupper($ordering['direction']); $direction = strtoupper($ordering['direction']);
$sql[] = $this->wrap($ordering['column']).' '.$direction;
} }
return 'ORDER BY '.implode(', ', $sql); return 'ORDER BY '.implode(', ', $sql);
@ -266,7 +303,7 @@ protected function offset(Query $query)
/** /**
* Compile a SQL INSERT statment from a Query instance. * Compile a SQL INSERT statment from a Query instance.
* *
* Note: This method handles the compilation of single row inserts and batch inserts. * This method handles the compilation of single row inserts and batch inserts.
* *
* @param Query $query * @param Query $query
* @param array $values * @param array $values
@ -274,14 +311,16 @@ protected function offset(Query $query)
*/ */
public function insert(Query $query, $values) public function insert(Query $query, $values)
{ {
// Force every insert to be treated like a batch insert. This simple makes $table = $this->wrap($query->from);
// Force every insert to be treated like a batch insert. This simply makes
// creating the SQL syntax a little easier on us since we can always treat // creating the SQL syntax a little easier on us since we can always treat
// the values as if it is an array containing multiple inserts. // the values as if it is an array containing multiple inserts.
if ( ! is_array(reset($values))) $values = array($values); if ( ! is_array(reset($values))) $values = array($values);
// Since we only care about the column names, we can pass any of the insert // Since we only care about the column names, we can pass any of the insert
// arrays into the "columnize" method. The names should be the same for // arrays into the "columnize" method. The columns should be the same for
// every insert to the table. // every insert to the table so we can just use the first record.
$columns = $this->columnize(array_keys(reset($values))); $columns = $this->columnize(array_keys(reset($values)));
// Build the list of parameter place-holders of values bound to the query. // Build the list of parameter place-holders of values bound to the query.
@ -289,24 +328,26 @@ public function insert(Query $query, $values)
// just use the first array of values. // just use the first array of values.
$parameters = $this->parameterize(reset($values)); $parameters = $this->parameterize(reset($values));
$parameters = implode(', ', array_fill(0, count($values), '('.$parameters.')')); $parameters = implode(', ', array_fill(0, count($values), "($parameters)"));
return 'INSERT INTO '.$this->wrap($query->from).' ('.$columns.') VALUES '.$parameters; return "INSERT INTO {$table} ({$columns}) VALUES {$parameters}";
} }
/** /**
* Compile a SQL UPDATE statment from a Query instance. * Compile a SQL UPDATE statment from a Query instance.
* *
* Note: Since UPDATE statements can be limited by a WHERE clause,
* this method will use the same WHERE clause compilation
* functions as the "select" method.
*
* @param Query $query * @param Query $query
* @param array $values * @param array $values
* @return string * @return string
*/ */
public function update(Query $query, $values) public function update(Query $query, $values)
{ {
$table = $this->wrap($query->from);
// Each column in the UPDATE statement needs to be wrapped in keyword
// identifiers, and a place-holder needs to be created for each value
// in the array of bindings. Of course, if the value of the binding
// is an expression, the expression string will be injected.
foreach ($values as $column => $value) foreach ($values as $column => $value)
{ {
$columns[] = $this->wrap($column).' = '.$this->parameter($value); $columns[] = $this->wrap($column).' = '.$this->parameter($value);
@ -314,7 +355,11 @@ public function update(Query $query, $values)
$columns = implode(', ', $columns); $columns = implode(', ', $columns);
return trim('UPDATE '.$this->wrap($query->from).' SET '.$columns.' '.$this->wheres($query)); // UPDATE statements may be constrained by a WHERE clause, so we'll
// run the entire where compilation process for those contraints.
// This is easily achieved by passing the query to the "wheres"
// method which will call all of the where compilers.
return trim("UPDATE {$table} SET {$columns} ".$this->wheres($query));
} }
/** /**
@ -325,103 +370,13 @@ public function update(Query $query, $values)
*/ */
public function delete(Query $query) public function delete(Query $query)
{ {
return trim('DELETE FROM '.$this->wrap($query->from).' '.$this->wheres($query)); $table = $this->wrap($query->from);
}
/** // Like the UPDATE statement, the DELETE statement is constrained
* The following functions primarily serve as utility functions for // by WHERE clauses, so we'll need to run the "wheres" method to
* the grammar. They perform tasks such as wrapping values in keyword // make the WHERE clauses for the query. The "wheres" method
* identifiers or creating variable lists of bindings. // encapsulates the logic to create the full WHERE clause.
*/ return trim("DELETE FROM {$table} ".$this->wheres($query));
/**
* Create a comma-delimited list of wrapped column names.
*
* @param array $columns
* @return string
*/
final public function columnize($columns)
{
return implode(', ', array_map(array($this, 'wrap'), $columns));
}
/**
* Wrap a value in keyword identifiers.
*
* They keyword identifier used by the method is specified as
* a property on the grammar class so it can be conveniently
* overriden without changing the wrapping logic itself.
*
* @param string $value
* @return string
*/
public function wrap($value)
{
// If the value being wrapped contains a column alias, we need to wrap
// it a little differently since each segment must be wrapped and not
// the entire string.
if (strpos(strtolower($value), ' as ') !== false)
{
return $this->alias($value);
}
// Expressions should be injected into the query as raw strings, so we
// do not want to wrap them in any way. We will just return the string
// value from the expression to be included in the query.
if ($value instanceof Expression) return $value->get();
foreach (explode('.', $value) as $segment)
{
if ($segment === '*')
{
$wrapped[] = $segment;
}
else
{
$wrapped[] = $this->wrapper.$segment.$this->wrapper;
}
}
return implode('.', $wrapped);
}
/**
* Wrap an alias in keyword identifiers.
*
* @param string $value
* @return string
*/
protected function alias($value)
{
$segments = explode(' ', $value);
return $this->wrap($segments[0]).' AS '.$this->wrap($segments[2]);
}
/**
* Create query parameters from an array of values.
*
* @param array $values
* @return string
*/
public function parameterize($values)
{
return implode(', ', array_map(array($this, 'parameter'), $values));
}
/**
* Get the appropriate query parameter string for a value.
*
* If the value is an expression, the raw expression string should
* be returned, otherwise, the parameter place-holder will be
* returned by the method.
*
* @param mixed $value
* @return string
*/
public function parameter($value)
{
return ($value instanceof Expression) ? $value->get() : '?';
} }
} }

View File

@ -1,4 +1,4 @@
<?php namespace Laravel\Database\Grammars; <?php namespace Laravel\Database\Query\Grammars;
class MySQL extends Grammar { class MySQL extends Grammar {
@ -7,6 +7,6 @@ class MySQL extends Grammar {
* *
* @var string * @var string
*/ */
protected $wrapper = '`'; protected $wrapper = '`%s`';
} }

View File

@ -0,0 +1,139 @@
<?php namespace Laravel\Database\Query\Grammars;
use Laravel\Database\Query;
class SQLServer extends Grammar {
/**
* The keyword identifier for the database system.
*
* @var string
*/
protected $wrapper = '[%s]';
/**
* Compile a SQL SELECT statement from a Query instance.
*
* @param Query $query
* @return string
*/
public function select(Query $query)
{
$sql = parent::components($query);
// SQL Server does not currently implement an "OFFSET" type keyword, so we
// actually have to generate the ANSI standard SQL for doing offset like
// functionality. In the next version of SQL Server, an OFFSET like
// keyword is included for convenience.
if ($query->offset > 0)
{
return $this->ansi_offset($query, $sql);
}
// Once all of the clauses have been compiled, we can join them all as
// one statement. Any segments that are null or an empty string will
// be removed from the array of clauses before they are imploded.
return $this->concatenate($sql);
}
/**
* Compile the SELECT clause for a query.
*
* @param Query $query
* @return string
*/
protected function selects(Query $query)
{
$select = ($query->distinct) ? 'SELECT DISTINCT ' : 'SELECT ';
// Instead of using a "LIMIT" keyword, SQL Server uses the "TOP"
// keyword within the SELECT statement. So, if we have a limit,
// we will add it here.
//
// We will not add the TOP clause if there is an offset however,
// since we will have to handle offsets using the ANSI syntax
// and will need to remove the TOP clause in that situation.
if ($query->limit > 0 and $query->offset <= 0)
{
$select .= 'TOP '.$query->limit.' ';
}
return $select.$this->columnize($query->selects);
}
/**
* Generate the ANSI standard SQL for an offset clause.
*
* @param Query $query
* @param array $components
* @return array
*/
protected function ansi_offset(Query $query, $components)
{
// An ORDER BY clause is required to make this offset query
// work, so if one doesn't exist, we'll just create a dummy
// clause to satisfy the database.
if ( ! isset($components['orderings']))
{
$components['orderings'] = 'ORDER BY (SELECT 0)';
}
// We need to add the row number to the query results so we
// can compare it against the offset and limit values given
// for the statement. To do that we'll add an expression to
// the select statement for the row number.
$orderings = $components['orderings'];
$components['selects'] .= ", ROW_NUMBER() OVER ({$orderings}) AS RowNum";
unset($components['orderings']);
$start = $query->offset + 1;
// Next we need to calculate the constraint that should be
// placed on the row number to get the correct offset and
// limit on the query. If a limit has not been set, we'll
// only add a constraint to handle offset.
if ($query->limit > 0)
{
$finish = $query->offset + $query->limit;
$constraint = "BETWEEN {$start} AND {$finish}";
}
else
{
$constraint = ">= {$start}";
}
// Now, we're finally ready to build the final SQL query.
// We'll create a common table expression with the query
// and then select all of the results from it where the
// row number is between oru given limit and offset.
$sql = $this->concatenate($components);
return "SELECT * FROM ($sql) AS TempTable WHERE RowNum {$constraint}";
}
/**
* Compile the LIMIT clause for a query.
*
* @param Query $query
* @return string
*/
protected function limit(Query $query)
{
return;
}
/**
* Compile the OFFSET clause for a query.
*
* @param Query $query
* @return string
*/
protected function offset(Query $query)
{
return;
}
}

119
laravel/database/schema.php Normal file
View File

@ -0,0 +1,119 @@
<?php namespace Laravel\Database;
use Laravel\Fluent;
use Laravel\Database as DB;
class Schema {
/**
* Begin a fluent schema operation on a database table.
*
* @param string $table
* @param Closure $callback
* @return void
*/
public static function table($table, $callback)
{
call_user_func($callback, $table = new Schema\Table($table));
static::implications($table);
return static::execute($table);
}
/**
* Execute the given schema operation against the database.
*
* @param Schema\Table $table
* @return void
*/
public static function execute($table)
{
foreach ($table->commands as $command)
{
$connection = DB::connection($table->connection);
$grammar = static::grammar($connection->driver());
// Each grammar has a function that corresponds to the command type
// and is responsible for building that's commands SQL. This lets
// the SQL generation stay very granular and makes it simply to
// add new database systems to the schema system.
if (method_exists($grammar, $method = $command->type))
{
$statements = $grammar->$method($table, $command);
// Once we have the statements, we will cast them to an array even
// though not all of the commands return an array. This is just in
// case the command needs to run more than one query to do what
// it needs to do what is requested by the developer.
foreach ((array) $statements as $statement)
{
$connection->statement($statement);
}
}
}
}
/**
* Add any implicit commands to the schema table operation.
*
* @param Schema\Table $table
* @return void
*/
protected static function implications($table)
{
// If the developer has specified columns for the table and the
// table is not being created, we will assume they simply want
// to add the columns to the table, and will generate an add
// command for them, adding the columns to the command.
if (count($table->columns) > 0 and ! $table->creating())
{
$command = new Fluent(array('type' => 'add'));
array_unshift($table->commands, $command);
}
// For some extra syntax sugar, we'll check for any implicit
// indexes on the table. The developer may specify the index
// type on the fluent column declaration. Here we'll find
// any such implicit index and add the actual command.
foreach ($table->columns as $column)
{
foreach (array('primary', 'unique', 'fulltext', 'index') as $key)
{
if ($column->$key === true)
{
$table->$key($column->name);
}
}
}
}
/**
* Create the appropriate schema grammar for the driver.
*
* @param string $driver
* @return Grammar
*/
public static function grammar($driver)
{
switch ($driver)
{
case 'mysql':
return new Schema\Grammars\MySQL;
case 'pgsql':
return new Schema\Grammars\Postgres;
case 'sqlsrv':
return new Schema\Grammars\SQLServer;
case 'sqlite':
return new Schema\Grammars\SQLite;
}
throw new \Exception("Schema operations not supported for [$driver].");
}
}

View File

@ -0,0 +1,38 @@
<?php namespace Laravel\Database\Schema\Grammars;
use Laravel\Fluent;
use Laravel\Database\Schema\Table;
abstract class Grammar extends \Laravel\Database\Grammar {
/**
* Get the appropriate data type definition for the column.
*
* @param Fluent $column
* @return string
*/
protected function type(Fluent $column)
{
return $this->{'type_'.$column->type}($column);
}
/**
* Wrap a value in keyword identifiers.
*
* @param Table|string $value
* @return string
*/
public function wrap($value)
{
// This method is primarily for convenience so we can just pass a
// column or table instance into the wrap method without sending
// in the name each time we need to wrap one of these objects.
if ($value instanceof Table or $value instanceof Fluent)
{
$value = $value->name;
}
return parent::wrap($value);
}
}

View File

@ -0,0 +1,386 @@
<?php namespace Laravel\Database\Schema\Grammars;
use Laravel\Fluent;
use Laravel\Database\Schema\Table;
class MySQL extends Grammar {
/**
* The keyword identifier for the database system.
*
* @var string
*/
public $wrapper = '`%s`';
/**
* Generate the SQL statements for a table creation command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$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.
if ( ! is_null($table->engine))
{
$sql .= ' ENGINE = '.$table->engine;
}
return $sql;
}
/**
* Geenrate the SQL statements for a table modification command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$columns = implode(', ', array_map(function($column)
{
return 'ADD '.$column;
}, $columns));
return 'ALTER TABLE '.$this->wrap($table).' '.$columns;
}
/**
* Create the individual column definitions for the table.
*
* @param Table $table
* @return array
*/
protected function columns(Table $table)
{
$columns = array();
foreach ($table->columns as $column)
{
// 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.
$sql = $this->wrap($column).' '.$this->type($column);
$elements = array('nullable', 'defaults', 'incrementer');
foreach ($elements as $element)
{
$sql .= $this->$element($table, $column);
}
$columns[] = $sql;
}
return $columns;
}
/**
* Get the SQL syntax for indicating if a column is nullable.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function nullable(Table $table, Fluent $column)
{
return ($column->nullable) ? ' NULL' : ' NOT NULL';
}
/**
* Get the SQL syntax for specifying a default value on a column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function defaults(Table $table, Fluent $column)
{
if ( ! is_null($column->default))
{
return " DEFAULT '".$column->default."'";
}
}
/**
* Get the SQL syntax for defining an auto-incrementing column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function incrementer(Table $table, Fluent $column)
{
if ($column->type == 'integer' and $column->increment)
{
return ' AUTO_INCREMENT PRIMARY KEY';
}
}
/**
* Generate the SQL statement for creating a primary key.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function primary(Table $table, Fluent $command)
{
return $this->key($table, $command, 'PRIMARY KEY');
}
/**
* Generate the SQL statement for creating a unique index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function unique(Table $table, Fluent $command)
{
return $this->key($table, $command, 'UNIQUE');
}
/**
* Generate the SQL statement for creating a full-text index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function fulltext(Table $table, Fluent $command)
{
return $this->key($table, $command, 'FULLTEXT');
}
/**
* Generate the SQL statement for creating a regular index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function index(Table $table, Fluent $command)
{
return $this->key($table, $command, 'INDEX');
}
/**
* Generate the SQL statement for creating a new index.
*
* @param Table $table
* @param Fluent $command
* @param string $type
* @return string
*/
protected function key(Table $table, Fluent $command, $type)
{
$keys = $this->columnize($command->columns);
$name = $command->name;
return 'ALTER TABLE '.$this->wrap($table)." ADD {$type} {$name}({$keys})";
}
/**
* Generate the SQL statement for a drop table command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop(Table $table, Fluent $command)
{
return 'DROP TABLE '.$this->wrap($table);
}
/**
* Generate the SQL statement for a drop column command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
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.
$columns = implode(', ', array_map(function($column)
{
return 'DROP '.$column;
}, $columns));
return 'ALTER TABLE '.$this->wrap($table).' '.$columns;
}
/**
* Generate the SQL statement for a drop primary key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_primary(Table $table, Fluent $command)
{
return 'ALTER TABLE '.$this->wrap($table).' DROP PRIMARY KEY';
}
/**
* Generate the SQL statement for a drop unqique key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_unique(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop full-text key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_fulltext(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop unqique key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_index(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
protected function drop_key(Table $table, Fluent $command)
{
return 'ALTER TABLE '.$this->wrap($table)." DROP INDEX {$command->name}";
}
/**
* Generate the data-type definition for a string.
*
* @param Fluent $column
* @return string
*/
protected function type_string(Fluent $column)
{
return 'VARCHAR('.$column->length.')';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_integer(Fluent $column)
{
return 'INT';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_float(Fluent $column)
{
return 'FLOAT';
}
/**
* Generate the data-type definition for a boolean.
*
* @param Fluent $column
* @return string
*/
protected function type_boolean(Fluent $column)
{
return 'TINYINT';
}
/**
* Generate the data-type definition for a date.
*
* @param Fluent $column
* @return string
*/
protected function type_date(Fluent $column)
{
return 'DATETIME';
}
/**
* Generate the data-type definition for a timestamp.
*
* @param Fluent $column
* @return string
*/
protected function type_timestamp(Fluent $column)
{
return 'TIMESTAMP';
}
/**
* Generate the data-type definition for a text column.
*
* @param Fluent $column
* @return string
*/
protected function type_text(Fluent $column)
{
return 'TEXT';
}
/**
* Generate the data-type definition for a blob.
*
* @param Fluent $column
* @return string
*/
protected function type_blob(Fluent $column)
{
return 'BLOB';
}
}

View File

@ -0,0 +1,381 @@
<?php namespace Laravel\Database\Schema\Grammars;
use Laravel\Fluent;
use Laravel\Database\Schema\Table;
class Postgres extends Grammar {
/**
* Generate the SQL statements for a table creation command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$sql = 'CREATE TABLE '.$this->wrap($table).' ('.$columns.')';
return $sql;
}
/**
* Geenrate the SQL statements for a table modification command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$columns = implode(', ', array_map(function($column)
{
return 'ADD COLUMN '.$column;
}, $columns));
return 'ALTER TABLE '.$this->wrap($table).' '.$columns;
}
/**
* Create the individual column definitions for the table.
*
* @param Table $table
* @return array
*/
protected function columns(Table $table)
{
$columns = array();
foreach ($table->columns as $column)
{
// 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.
$sql = $this->wrap($column).' '.$this->type($column);
$elements = array('incrementer', 'nullable', 'defaults');
foreach ($elements as $element)
{
$sql .= $this->$element($table, $column);
}
$columns[] = $sql;
}
return $columns;
}
/**
* Get the SQL syntax for indicating if a column is nullable.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function nullable(Table $table, Fluent $column)
{
return ($column->nullable) ? ' NULL' : ' NOT NULL';
}
/**
* Get the SQL syntax for specifying a default value on a column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function defaults(Table $table, Fluent $column)
{
if ( ! is_null($column->default))
{
return " DEFAULT '".$column->default."'";
}
}
/**
* Get the SQL syntax for defining an auto-incrementing column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
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.
if ($column->type == 'integer' and $column->increment)
{
return ' PRIMARY KEY';
}
}
/**
* Generate the SQL statement for creating a primary key.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function primary(Table $table, Fluent $command)
{
$columns = $this->columnize($command->columns);
return 'ALTER TABLE '.$this->wrap($table)." ADD PRIMARY KEY ({$columns})";
}
/**
* Generate the SQL statement for creating a unique index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function unique(Table $table, Fluent $command)
{
return $this->key($table, $command, true);
}
/**
* Generate the SQL statement for creating a full-text index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function fulltext(Table $table, Fluent $command)
{
$name = $command->name;
$columns = $this->columnize($command->columns);
return "CREATE INDEX {$name} ON ".$this->wrap($table)." USING gin({$columns})";
}
/**
* Generate the SQL statement for creating a regular index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function index(Table $table, Fluent $command)
{
return $this->key($table, $command);
}
/**
* Generate the SQL statement for creating a new index.
*
* @param Table $table
* @param Fluent $command
* @param bool $unique
* @return string
*/
protected function key(Table $table, Fluent $command, $unique = false)
{
$columns = $this->columnize($command->columns);
$create = ($unique) ? 'CREATE UNIQUE' : 'CREATE';
return $create." INDEX {$command->name} ON ".$this->wrap($table)." ({$columns})";
}
/**
* Generate the SQL statement for a drop table command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop(Table $table, Fluent $command)
{
return 'DROP TABLE '.$this->wrap($table);
}
/**
* Generate the SQL statement for a drop column command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
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.
$columns = implode(', ', array_map(function($column)
{
return 'DROP COLUMN '.$column;
}, $columns));
return 'ALTER TABLE '.$this->wrap($table).' '.$columns;
}
/**
* Generate the SQL statement for a drop primary key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_primary(Table $table, Fluent $command)
{
return 'ALTER TABLE '.$this->wrap($table).' DROP CONSTRAINT '.$table->name.'_pkey';
}
/**
* Generate the SQL statement for a drop unqique key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_unique(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop full-text key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_fulltext(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop index command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_index(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
protected function drop_key(Table $table, Fluent $command)
{
return 'DROP INDEX '.$command->name;
}
/**
* Generate the data-type definition for a string.
*
* @param Fluent $column
* @return string
*/
protected function type_string(Fluent $column)
{
return 'VARCHAR('.$column->length.')';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_integer(Fluent $column)
{
return ($column->increment) ? 'SERIAL' : 'INTEGER';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_float(Fluent $column)
{
return 'REAL';
}
/**
* Generate the data-type definition for a boolean.
*
* @param Fluent $column
* @return string
*/
protected function type_boolean(Fluent $column)
{
return 'SMALLINT';
}
/**
* Generate the data-type definition for a date.
*
* @param Fluent $column
* @return string
*/
protected function type_date(Fluent $column)
{
return 'TIMESTAMP';
}
/**
* Generate the data-type definition for a timestamp.
*
* @param Fluent $column
* @return string
*/
protected function type_timestamp(Fluent $column)
{
return 'TIMESTAMP';
}
/**
* Generate the data-type definition for a text column.
*
* @param Fluent $column
* @return string
*/
protected function type_text(Fluent $column)
{
return 'TEXT';
}
/**
* Generate the data-type definition for a blob.
*
* @param Fluent $column
* @return string
*/
protected function type_blob(Fluent $column)
{
return 'BYTEA';
}
}

View File

@ -0,0 +1,344 @@
<?php namespace Laravel\Database\Schema\Grammars;
use Laravel\Fluent;
use Laravel\Database\Schema\Table;
class SQLite extends Grammar {
/**
* Generate the SQL statements for a table creation command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$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.
$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 ( ! is_null($primary))
{
$columns = $this->columnize($primary->columns);
$sql .= ", PRIMARY KEY ({$columns})";
}
return $sql .= ')';
}
/**
* Geenrate the SQL statements for a table modification command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$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.
foreach ($columns as $column)
{
$sql[] = 'ALTER TABLE '.$this->wrap($table).' '.$column;
}
return (array) $sql;
}
/**
* Create the individual column definitions for the table.
*
* @param Table $table
* @return array
*/
protected function columns(Table $table)
{
$columns = array();
foreach ($table->columns as $column)
{
// 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.
$sql = $this->wrap($column).' '.$this->type($column);
$elements = array('nullable', 'defaults', 'incrementer');
foreach ($elements as $element)
{
$sql .= $this->$element($table, $column);
}
$columns[] = $sql;
}
return $columns;
}
/**
* Get the SQL syntax for indicating if a column is nullable.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function nullable(Table $table, Fluent $column)
{
return ($column->nullable) ? ' NULL' : ' NOT NULL';
}
/**
* Get the SQL syntax for specifying a default value on a column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function defaults(Table $table, Fluent $column)
{
if ( ! is_null($column->default))
{
return ' DEFAULT '.$this->wrap($column->default);
}
}
/**
* Get the SQL syntax for defining an auto-incrementing column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function incrementer(Table $table, Fluent $column)
{
if ($column->type == 'integer' and $column->increment)
{
return ' PRIMARY KEY AUTOINCREMENT';
}
}
/**
* Generate the SQL statement for creating a unique index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function unique(Table $table, Fluent $command)
{
return $this->key($table, $command, true);
}
/**
* Generate the SQL statement for creating a full-text index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function fulltext(Table $table, Fluent $command)
{
$columns = $this->columnize($command->columns);
return 'CREATE VIRTUAL TABLE '.$this->wrap($table)." USING fts4({$columns})";
}
/**
* Generate the SQL statement for creating a regular index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function index(Table $table, Fluent $command)
{
return $this->key($table, $command);
}
/**
* Generate the SQL statement for creating a new index.
*
* @param Table $table
* @param Fluent $command
* @param bool $unique
* @return string
*/
protected function key(Table $table, Fluent $command, $unique = false)
{
$columns = $this->columnize($command->columns);
$create = ($unique) ? 'CREATE UNIQUE' : 'CREATE';
return $create." INDEX {$command->name} ON ".$this->wrap($table)." ({$columns})";
}
/**
* Generate the SQL statement for a drop table command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop(Table $table, Fluent $command)
{
return 'DROP TABLE '.$this->wrap($table);
}
/**
* Generate the SQL statement for a drop unqique key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_unique(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop unqique key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_index(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
protected function drop_key(Table $table, Fluent $command)
{
return 'DROP INDEX '.$this->wrap($command->name);
}
/**
* Generate the data-type definition for a string.
*
* @param Fluent $column
* @return string
*/
protected function type_string(Fluent $column)
{
return 'VARCHAR';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_integer(Fluent $column)
{
return 'INTEGER';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_float(Fluent $column)
{
return 'FLOAT';
}
/**
* Generate the data-type definition for a boolean.
*
* @param Fluent $column
* @return string
*/
protected function type_boolean(Fluent $column)
{
return 'INTEGER';
}
/**
* Generate the data-type definition for a date.
*
* @param Fluent $column
* @return string
*/
protected function type_date(Fluent $column)
{
return 'DATETIME';
}
/**
* Generate the data-type definition for a timestamp.
*
* @param Fluent $column
* @return string
*/
protected function type_timestamp(Fluent $column)
{
return 'DATETIME';
}
/**
* Generate the data-type definition for a text column.
*
* @param Fluent $column
* @return string
*/
protected function type_text(Fluent $column)
{
return 'TEXT';
}
/**
* Generate the data-type definition for a blob.
*
* @param Fluent $column
* @return string
*/
protected function type_blob(Fluent $column)
{
return 'BLOB';
}
}

View File

@ -0,0 +1,402 @@
<?php namespace Laravel\Database\Schema\Grammars;
use Laravel\Fluent;
use Laravel\Database\Schema\Table;
class SQLServer extends Grammar {
/**
* The keyword identifier for the database system.
*
* @var string
*/
public $wrapper = '[%s]';
/**
* Generate the SQL statements for a table creation command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$sql = 'CREATE TABLE '.$this->wrap($table).' ('.$columns.')';
return $sql;
}
/**
* Geenrate the SQL statements for a table modification command.
*
* @param Table $table
* @param Fluent $command
* @return array
*/
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.
$columns = implode(', ', array_map(function($column)
{
return 'ADD '.$column;
}, $columns));
return 'ALTER TABLE '.$this->wrap($table).' '.$columns;
}
/**
* Create the individual column definitions for the table.
*
* @param Table $table
* @return array
*/
protected function columns(Table $table)
{
$columns = array();
foreach ($table->columns as $column)
{
// 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.
$sql = $this->wrap($column).' '.$this->type($column);
$elements = array('incrementer', 'nullable', 'defaults');
foreach ($elements as $element)
{
$sql .= $this->$element($table, $column);
}
$columns[] = $sql;
}
return $columns;
}
/**
* Get the SQL syntax for indicating if a column is nullable.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function nullable(Table $table, Fluent $column)
{
return ($column->nullable) ? ' NULL' : ' NOT NULL';
}
/**
* Get the SQL syntax for specifying a default value on a column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function defaults(Table $table, Fluent $column)
{
if ( ! is_null($column->default))
{
return " DEFAULT '".$column->default."'";
}
}
/**
* Get the SQL syntax for defining an auto-incrementing column.
*
* @param Table $table
* @param Fluent $column
* @return string
*/
protected function incrementer(Table $table, Fluent $column)
{
if ($column->type == 'integer' and $column->increment)
{
return ' IDENTITY PRIMARY KEY';
}
}
/**
* Generate the SQL statement for creating a primary key.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function primary(Table $table, Fluent $command)
{
$name = $command->name;
$columns = $this->columnize($columns);
return 'ALTER TABLE '.$this->wrap($table)." ADD CONSTRAINT {$name} PRIMARY KEY ({$columns})";
}
/**
* Generate the SQL statement for creating a unique index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function unique(Table $table, Fluent $command)
{
return $this->key($table, $command, true);
}
/**
* Generate the SQL statement for creating a full-text index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
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.
$sql[] = "CREATE FULLTEXT CATALOG {$command->catalog}";
$create = "CREATE FULLTEXT INDEX ON ".$this->wrap($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.
$sql[] = $create .= "KEY INDEX {$command->key} ON {$command->catalog}";
return $sql;
}
/**
* Generate the SQL statement for creating a regular index.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function index(Table $table, Fluent $command)
{
return $this->key($table, $command);
}
/**
* Generate the SQL statement for creating a new index.
*
* @param Table $table
* @param Fluent $command
* @param bool $unique
* @return string
*/
protected function key(Table $table, Fluent $command, $unique = false)
{
$columns = $this->columnize($command->columns);
$create = ($unique) ? 'CREATE UNIQUE' : 'CREATE';
return $create." INDEX {$command->name} ON ".$this->wrap($table)." ({$columns})";
}
/**
* Generate the SQL statement for a drop table command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop(Table $table, Fluent $command)
{
return 'DROP TABLE '.$this->wrap($table);
}
/**
* Generate the SQL statement for a drop column command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
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.
$columns = implode(', ', array_map(function($column)
{
return 'DROP '.$column;
}, $columns));
return 'ALTER TABLE '.$this->wrap($table).' '.$columns;
}
/**
* Generate the SQL statement for a drop primary key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_primary(Table $table, Fluent $command)
{
return 'ALTER TABLE '.$this->wrap($table).' DROP CONSTRAINT '.$command->name;
}
/**
* Generate the SQL statement for a drop unqiue key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_unique(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop full-text key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_fulltext(Table $table, Fluent $command)
{
$sql[] = "DROP FULLTEXT INDEX ".$command->name;
$sql[] = "DROP FULLTEXT CATALOG ".$command->catalog;
return $sql;
}
/**
* Generate the SQL statement for a drop index command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
public function drop_index(Table $table, Fluent $command)
{
return $this->drop_key($table, $command);
}
/**
* Generate the SQL statement for a drop key command.
*
* @param Table $table
* @param Fluent $command
* @return string
*/
protected function drop_key(Table $table, Fluent $command)
{
return "DROP INDEX {$command->name} ON ".$this->wrap($table);
}
/**
* Generate the data-type definition for a string.
*
* @param Fluent $column
* @return string
*/
protected function type_string(Fluent $column)
{
return 'NVARCHAR('.$column->length.')';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_integer(Fluent $column)
{
return 'INT';
}
/**
* Generate the data-type definition for an integer.
*
* @param Fluent $column
* @return string
*/
protected function type_float(Fluent $column)
{
return 'FLOAT';
}
/**
* Generate the data-type definition for a boolean.
*
* @param Fluent $column
* @return string
*/
protected function type_boolean(Fluent $column)
{
return 'TINYINT';
}
/**
* Generate the data-type definition for a date.
*
* @param Fluent $column
* @return string
*/
protected function type_date(Fluent $column)
{
return 'DATETIME';
}
/**
* Generate the data-type definition for a timestamp.
*
* @param Fluent $column
* @return string
*/
protected function type_timestamp(Fluent $column)
{
return 'TIMESTAMP';
}
/**
* Generate the data-type definition for a text column.
*
* @param Fluent $column
* @return string
*/
protected function type_text(Fluent $column)
{
return 'NVARCHAR(MAX)';
}
/**
* Generate the data-type definition for a blob.
*
* @param Fluent $column
* @return string
*/
protected function type_blob(Fluent $column)
{
return 'VARBINARY(MAX)';
}
}

View File

@ -0,0 +1,371 @@
<?php namespace Laravel\Database\Schema;
use Laravel\Fluent;
class Table {
/**
* The database table name.
*
* @var string
*/
public $name;
/**
* The database connection that should be used.
*
* @var string
*/
public $connection;
/**
* The engine that should be used for the table.
*
* @var string
*/
public $engine;
/**
* The columns that should be added to the table.
*
* @var array
*/
public $columns = array();
/**
* The commands that should be executed on the table.
*
* @var array
*/
public $commands = array();
/**
* Create a new schema table instance.
*
* @param string $name
* @return void
*/
public function __construct($name)
{
$this->name = $name;
}
/**
* Indicate that the table should be created.
*
* @return Fluent
*/
public function create()
{
return $this->command(__FUNCTION__);
}
/**
* Create a new primary key on the table.
*
* @param string|array $columns
* @param string $name
* @return Fluent
*/
public function primary($columns, $name = null)
{
return $this->key(__FUNCTION__, $columns, $name);
}
/**
* Create a new unique index on the table.
*
* @param string|array $columns
* @param string $name
* @return Fluent
*/
public function unique($columns, $name = null)
{
return $this->key(__FUNCTION__, $columns, $name);
}
/**
* Create a new full-text index on the table.
*
* @param string|array $columns
* @param string $name
* @return Fluent
*/
public function fulltext($columns, $name = null)
{
return $this->key(__FUNCTION__, $columns, $name);
}
/**
* Create a new index on the table.
*
* @param string|array
*/
public function index($columns, $name = null)
{
return $this->key(__FUNCTION__, $columns, $name);
}
/**
* Create a command for creating any index.
*
* @param string $type
* @param string|array $columns
* @param string $name
* @return Fluent
*/
public function key($type, $columns, $name = null)
{
$parameters = array('name' => $name, 'columns' => (array) $columns);
return $this->command($type, $parameters);
}
/**
* Drop the database table.
*
* @return Fluent
*/
public function drop()
{
return $this->command(__FUNCTION__);
}
/**
* Drop a column from the table.
*
* @param string|array $columns
* @return void
*/
public function drop_column($columns)
{
return $this->command(__FUNCTION__, array('columns' => (array) $columns));
}
/**
* Drop a primary key from the table.
*
* @param string $name
* @return void
*/
public function drop_primary($name)
{
return $this->drop_key(__FUNCTION__, $name);
}
/**
* Drop a unique index from the table.
*
* @param string $name
* @return void
*/
public function drop_unique($name)
{
return $this->drop_key(__FUNCTION__, $name);
}
/**
* Drop a full-text index from the table.
*
* @param string $name
* @return void
*/
public function drop_fulltext($name)
{
return $this->drop_key(__FUNCTION__, $name);
}
/**
* Drop an index from the table.
*
* @param string $name
* @return void
*/
public function drop_index($name)
{
return $this->drop_key(__FUNCTION__, $name);
}
/**
* Create a command to drop any type of index.
*
* @param string $type
* @param string $name
* @return Fluent
*/
protected function drop_key($type, $name)
{
return $this->command($type, array('name' => $name));
}
/**
* Add an auto-incrementing integer to the table.
*
* @param string $name
* @return Fluent
*/
public function increments($name)
{
return $this->integer($name, true);
}
/**
* Add a string column to the table.
*
* @param string $name
* @param int $length
* @return Fluent
*/
public function string($name, $length = 200)
{
return $this->column(__FUNCTION__, compact('name', 'length'));
}
/**
* Add an integer column to the table.
*
* @param string $name
* @param bool $increment
* @return Fluent
*/
public function integer($name, $increment = false)
{
return $this->column(__FUNCTION__, compact('name', 'increment'));
}
/**
* Add a float column to the table.
*
* @param string $name
* @param bool $increment
* @return Fluent
*/
public function float($name)
{
return $this->column(__FUNCTION__, compact('name'));
}
/**
* Add a boolean column to the table.
*
* @param string $name
* @return Fluent
*/
public function boolean($name)
{
return $this->column(__FUNCTION__, compact('name'));
}
/**
* Create date-time columns for creation and update timestamps.
*
* @return void
*/
public function timestamps()
{
$this->date('created_at');
$this->date('updated_at');
}
/**
* Add a date-time column to the table.
*
* @param string $name
* @return Fluent
*/
public function date($name)
{
return $this->column(__FUNCTION__, compact('name'));
}
/**
* Add a timestamp column to the table.
*
* @param string $name
* @return Fluent
*/
public function timestamp($name)
{
return $this->column(__FUNCTION__, compact('name'));
}
/**
* Add a text column to the table.
*
* @param string $name
* @return Fluent
*/
public function text($name)
{
return $this->column(__FUNCTION__, compact('name'));
}
/**
* Add a blob column to the table.
*
* @param string $name
* @return Fluent
*/
public function blob($name)
{
return $this->column(__FUNCTION__, compact('name'));
}
/**
* Set the database connection for the table operation.
*
* @param string $connection
* @return void
*/
public function on($connection)
{
$this->connection = $connection;
}
/**
* Determine if the schema table has a creation command.
*
* @return bool
*/
public function creating()
{
return ! is_null(array_first($this->commands, function($key, $value)
{
return $value->type == 'create';
}));
}
/**
* Create a new fluent command instance.
*
* @param string $type
* @param array $parameters
* @return Fluent
*/
protected function command($type, $parameters = array())
{
$parameters = array_merge(compact('type'), $parameters);
$this->commands[] = new Fluent($parameters);
return end($this->commands);
}
/**
* Create a new fluent column instance.
*
* @param string $type
* @param array $parameters
* @return Fluent
*/
protected function column($type, $parameters = array())
{
$parameters = array_merge(compact('type'), $parameters);
$this->columns[] = new Fluent($parameters);
return end($this->columns);
}
}

96
laravel/error.php Normal file
View File

@ -0,0 +1,96 @@
<?php namespace Laravel;
class Error {
/**
* Handle an exception and display the exception report.
*
* @param Exception $exception
* @return void
*/
public static function exception($exception)
{
static::log($exception);
// If detailed errors are enabled, we'll just format the exception into
// a simple error message and display it on the screen. We don't use a
// View in case the problem is in the View class itself so we can not
// run into a white screen of death situation.
if (Config::get('error.detail'))
{
echo "<html><h2>Unhandled Exception</h2>
<h3>Message:</h3>
<pre>".$exception->getMessage()."</pre>
<h3>Location:</h3>
<pre>".$exception->getFile()." on line ".$exception->getLine()."</pre>
<h3>Stack Trace:</h3>
<pre>".$exception->getTraceAsString()."</pre></html>";
}
else
{
Response::error('500')->send();
}
exit(1);
}
/**
* Handle a native PHP error as an ErrorException.
*
* @param int $code
* @param string $error
* @param string $file
* @param int $line
* @return void
*/
public static function native($code, $error, $file, $line)
{
if (error_reporting() === 0) return;
// 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 to Exceptions.
$exception = new \ErrorException($error, $code, 0, $file, $line);
if (in_array($code, Config::get('error.ignore')))
{
return static::log($exception);
}
static::exception($exception);
}
/**
* Handle the PHP shutdown event.
*
* @return void
*/
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()))
{
extract($error, EXTR_SKIP);
static::exception(new \ErrorException($message, $type, 0, $file, $line));
}
}
/**
* Log an exception.
*
* @param Exception $exception
* @return void
*/
public static function log($exception)
{
if (Config::get('error.log'))
{
call_user_func(Config::get('error.logger'), $exception);
}
}
}

73
laravel/event.php Normal file
View File

@ -0,0 +1,73 @@
<?php namespace Laravel;
class Event {
/**
* All of the registered events.
*
* @var array
*/
public static $events = array();
/**
* Determine if an event has any registered listeners.
*
* @param string $event
* @return bool
*/
public static function listeners($event)
{
return isset(static::$events[$event]);
}
/**
* Register a callback for a given event.
*
* <code>
* // Register a callback for the "start" event
* Event::listen('start', function() {return 'Started!';});
*
* // Register an object instance callback for the given event
* Event::listen('event', array($object, 'method'));
* </code>
*
* @param string $event
* @param mixed $callback
* @return void
*/
public static function listen($event, $callback)
{
static::$events[$event][] = $callback;
}
/**
* Fire an event so that all listeners are called.
*
* <code>
* // Fire the "start" event
* $responses = Event::fire('start');
*
* // Fire the "start" event passing an array of parameters
* $responses = Event::fire('start', array('Laravel', 'Framework'));
* </code>
*
* @param string $event
* @param array $parameters
* @return array
*/
public static function fire($event, $parameters = array())
{
$responses = array();
if (static::listeners($event))
{
foreach (static::$events[$event] as $callback)
{
$responses[] = call_user_func_array($callback, $parameters);
}
}
return $responses;
}
}

View File

@ -1,66 +0,0 @@
<?php namespace Laravel\Facades;
use Laravel\IoC;
/**
* The Laravel framework makes thorough use of dependency injection assisted by an application
* inversion of control container. This allows for great flexibility, easy testing, and better
* architecture. However, most PHP framework users may be used to accessing classes through
* a variety of static methods. Laravel provides "facades" to simulate this behavior while
* still using heavy dependency injection.
*
* Each class that is commonly used by the developer has a corresponding facade defined in
* this file. All of the various facades inherit from the abstract Facade class, which only
* has a single __callStatic magic method. The facade simply resolves the requested class
* out of the IoC container and calls the appropriate method.
*/
abstract class Facade {
/**
* Magic Method for passing methods to a class registered in the IoC container.
* This provides a convenient method of accessing functions on classes that
* could not otherwise be accessed staticly.
*
* Facades allow Laravel to still have a high level of dependency injection
* and testability while still accomodating the common desire to conveniently
* use classes via static methods.
*/
public static function __callStatic($method, $parameters)
{
$class = IoC::resolve(static::$resolve);
$count = count($parameters);
if ($count > 5)
{
return call_user_func_array(array($class, $method), $parameters);
}
elseif ($count == 0)
{
return $class->$method();
}
elseif ($count == 1)
{
return $class->$method($parameters[0]);
}
elseif ($count == 2)
{
return $class->$method($parameters[0], $parameters[1]);
}
elseif ($count == 3)
{
return $class->$method($parameters[0], $parameters[1], $parameters[2]);
}
elseif ($count == 4)
{
return $class->$method($parameters[0], $parameters[1], $parameters[2], $parameters[3]);
}
elseif ($count == 5)
{
return $class->$method($parameters[0], $parameters[1], $parameters[2], $parameters[3], $parameters[4]);
}
}
}
class Session extends Facade { public static $resolve = 'laravel.session'; }

View File

@ -30,12 +30,7 @@ public static function exists($path)
*/ */
public static function get($path, $default = null) public static function get($path, $default = null)
{ {
if (file_exists($path)) return (file_exists($path)) ? file_get_contents($path) : value($default);
{
return file_get_contents($path);
}
return ($default instanceof Closure) ? call_user_func($default) : $default;
} }
/** /**
@ -117,25 +112,6 @@ public static function modified($path)
return filemtime($path); return filemtime($path);
} }
/**
* Move an uploaded file to permanent storage.
*
* <code>
* // Upload the $_FILES['photo'] file to a permanent location
* File::upload('photo', 'path/to/new/home.jpg');
* </code>
*
* @param string $key
* @param string $path
* @return bool
*/
public static function upload($key, $path)
{
if ( ! isset($_FILES[$key])) return false;
return move_uploaded_file($_FILES[$key]['tmp_name'], $path);
}
/** /**
* Get a file MIME type by extension. * Get a file MIME type by extension.
* *
@ -163,7 +139,7 @@ public static function mime($extension, $default = 'application/octet-stream')
/** /**
* Determine if a file is a given type. * Determine if a file is a given type.
* *
* The Fileinfo PHP extension will be used to determine the MIME type of the file. * The Fileinfo PHP extension is used to determine the file's MIME type.
* *
* <code> * <code>
* // Determine if a file is a JPG image * // Determine if a file is a JPG image

80
laravel/fluent.php Normal file
View File

@ -0,0 +1,80 @@
<?php namespace Laravel;
class Fluent {
/**
* All of the attributes set on the fluent container.
*
* @var array
*/
protected $attributes = array();
/**
* Create a new fluent container instance.
*
* <code>
* Create a new fluent container with attributes
* $fluent = new Fluent(array('name' => 'Taylor'));
* </code>
*
* @param array $attributes
* @return void
*/
public function __construct($attributes = array())
{
foreach ($attributes as $key => $value)
{
$this->$key = $value;
}
}
/**
* Get an attribute from the fluent container.
*
* @param string $attribute
* @param mixed $default
* @return mixed
*/
public function get($attribute, $default = null)
{
return array_get($attributes, $attribute, $default);
}
/**
* Handle dynamic calls to the container to set attributes.
*
* <code>
* // Fluently set the value of a few attributes
* $fluent->name('Taylor')->age(25);
*
* // Set the value of an attribute to true (boolean)
* $fluent->nullable()->name('Taylor');
* </code>
*/
public function __call($method, $parameters)
{
$this->$method = (count($parameters) > 0) ? $parameters[0] : true;
return $this;
}
/**
* Dynamically retrieve the value of an attribute.
*/
public function __get($key)
{
if (array_key_exists($key, $this->attributes))
{
return $this->attributes[$key];
}
}
/**
* Dynamically set the value of an attribute.
*/
public function __set($key, $value)
{
$this->attributes[$key] = $value;
}
}

View File

@ -1,15 +1,10 @@
<?php namespace Laravel; <?php namespace Laravel;
use Laravel\Session\Payload as Session;
class Form { class Form {
/** /**
* All of the label names that have been created. * All of the label names that have been created.
* *
* These names are stored so that input elements can automatically be assigned
* an ID based on the corresponding label name.
*
* @var array * @var array
*/ */
protected static $labels = array(); protected static $labels = array();
@ -17,12 +12,6 @@ class Form {
/** /**
* Open a HTML form. * Open a HTML form.
* *
* If PUT or DELETE is specified as the form method, a hidden input field will be generated
* containing the request method. PUT and DELETE are not supported by HTML forms, so the
* hidden field will allow us to "spoof" PUT and DELETE requests.
*
* Unless specified, the "accept-charset" attribute will be set to the application encoding.
*
* <code> * <code>
* // Open a "POST" form to the current request URI * // Open a "POST" form to the current request URI
* echo Form::open(); * echo Form::open();
@ -51,6 +40,9 @@ public static function open($action = null, $method = 'POST', $attributes = arra
$attributes['action'] = static::action($action, $https); $attributes['action'] = static::action($action, $https);
// If a character encoding has not been specified in the attributes, we will
// use the default encoding as specified in the application configuration
// file for the "accept-charset" attribute.
if ( ! array_key_exists('accept-charset', $attributes)) if ( ! array_key_exists('accept-charset', $attributes))
{ {
$attributes['accept-charset'] = Config::get('application.encoding'); $attributes['accept-charset'] = Config::get('application.encoding');
@ -58,6 +50,10 @@ public static function open($action = null, $method = 'POST', $attributes = arra
$append = ''; $append = '';
// Since PUT and DELETE methods are not actually supported by HTML forms,
// we'll create a hidden input element that contains the request method
// and set the actual request method to POST. Laravel will look for the
// hidden element to determine the request method.
if ($method == 'PUT' or $method == 'DELETE') if ($method == 'PUT' or $method == 'DELETE')
{ {
$append = static::hidden(Request::spoofer, $method); $append = static::hidden(Request::spoofer, $method);
@ -69,9 +65,6 @@ public static function open($action = null, $method = 'POST', $attributes = arra
/** /**
* Determine the appropriate request method to use for a form. * Determine the appropriate request method to use for a form.
* *
* Since PUT and DELETE requests are spoofed using POST requests, we will substitute
* POST for any PUT or DELETE methods. Otherwise, the specified method will be used.
*
* @param string $method * @param string $method
* @return string * @return string
*/ */
@ -155,7 +148,7 @@ public static function close()
*/ */
public static function token() public static function token()
{ {
return static::input('hidden', Session::csrf_token, IoC::core('session')->token()); return static::input('hidden', Session::csrf_token, Session::token());
} }
/** /**
@ -185,9 +178,6 @@ public static function label($name, $value, $attributes = array())
/** /**
* Create a HTML input element. * Create a HTML input element.
* *
* If an ID attribute is not specified and a label has been generated matching the input
* element name, the label name will be used as the element ID.
*
* <code> * <code>
* // Create a "text" input element named "email" * // Create a "text" input element named "email"
* echo Form::input('text', 'email'); * echo Form::input('text', 'email');
@ -490,8 +480,6 @@ public static function reset($value, $attributes = array())
/** /**
* Create a HTML image input element. * Create a HTML image input element.
* *
* The URL::to_asset method will be called on the given URL.
*
* <code> * <code>
* // Create an image input element * // Create an image input element
* echo Form::image('img/submit.png'); * echo Form::image('img/submit.png');
@ -524,15 +512,16 @@ public static function button($value, $attributes = array())
/** /**
* Determine the ID attribute for a form element. * Determine the ID attribute for a form element.
* *
* An explicitly specified ID in the attributes takes first precedence, then
* the label names will be checked for a label matching the element name.
*
* @param string $name * @param string $name
* @param array $attributes * @param array $attributes
* @return mixed * @return mixed
*/ */
protected static function id($name, $attributes) protected static function id($name, $attributes)
{ {
// If an ID has been explicitly specified in the attributes, we will
// use that ID. Otherwise, we will look for an ID in the array of
// label names as this makes it convenient to give input elements
// the same ID as their corresponding labels.
if (array_key_exists('id', $attributes)) if (array_key_exists('id', $attributes))
{ {
return $attributes['id']; return $attributes['id'];

View File

@ -5,11 +5,6 @@ class Hash {
/** /**
* Hash a password using the Bcrypt hashing scheme. * Hash a password using the Bcrypt hashing scheme.
* *
* Bcrypt provides a future-proof hashing algorithm by allowing the number of
* "rounds" to be increased, thus increasing the time it takes to generate the
* hashed value. The longer it takes takes to generate the hash, the more
* impractical a rainbow table attack against the hashes becomes.
*
* <code> * <code>
* // Create a Bcrypt hash of a value * // Create a Bcrypt hash of a value
* $hash = Hash::make('secret'); * $hash = Hash::make('secret');
@ -24,7 +19,9 @@ class Hash {
*/ */
public static function make($value, $rounds = 8) public static function make($value, $rounds = 8)
{ {
return crypt($value, '$2a$'.str_pad($rounds, 2, '0', STR_PAD_LEFT).'$'.static::salt()); $work = str_pad($rounds, 2, '0', STR_PAD_LEFT);
return crypt($value, '$2a$'.$work.'$'.static::salt());
} }
/** /**
@ -46,10 +43,10 @@ public static function check($value, $hash)
*/ */
protected static function salt() protected static function salt()
{ {
// Bcrypt expects the salt to be 22 base64 encoded characters, including dots // Bcrypt expects the salt to be 22 base64 encoded characters including
// and slashes. We will get rid of the plus signs included in the base64 data // dots and slashes. We will get rid of the plus signs included in the
// and replace them with dots. OpenSSL will be used if available, since it is // base64 data and replace them with dots. OpenSSL will be used if it
// more random, otherwise we will fallback on Str::random. // is available, otherwise we will use the Str::random method.
if (function_exists('openssl_random_pseudo_bytes')) if (function_exists('openssl_random_pseudo_bytes'))
{ {
$bytes = openssl_random_pseudo_bytes(16); $bytes = openssl_random_pseudo_bytes(16);
@ -57,7 +54,9 @@ protected static function salt()
return substr(strtr(base64_encode($bytes), '+', '.'), 0 , 22); return substr(strtr(base64_encode($bytes), '+', '.'), 0 , 22);
} }
return substr(str_replace('+', '.', base64_encode(Str::random(40))), 0, 22); $salt = str_replace('+', '.', base64_encode(Str::random(40)));
return substr($salt, 0, 22);
} }
} }

View File

@ -24,4 +24,287 @@ function e($value)
function __($key, $replacements = array(), $language = null) function __($key, $replacements = array(), $language = null)
{ {
return Laravel\Lang::line($key, $replacements, $language); return Laravel\Lang::line($key, $replacements, $language);
}
/**
* Get an item from an array using "dot" notation.
*
* <code>
* // Get the $array['user']['name'] value from the array
* $name = array_get($array, 'user.name');
*
* // Return a default from if the specified item doesn't exist
* $name = array_get($array, 'user.name', 'Taylor');
* </code>
*
* @param array $array
* @param string $key
* @param mixed $default
* @return mixed
*/
function array_get($array, $key, $default = null)
{
if (is_null($key)) return $array;
foreach (explode('.', $key) as $segment)
{
if ( ! is_array($array) or ! array_key_exists($segment, $array))
{
return value($default);
}
$array = $array[$segment];
}
return $array;
}
/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* <code>
* // Set the $array['user']['name'] value on the array
* array_set($array, 'user.name', 'Taylor');
*
* // Set the $array['user']['name']['first'] value on the array
* array_set($array, 'user.name.first', 'Michael');
* </code>
*
* @param array $array
* @param string $key
* @param mixed $value
* @return void
*/
function array_set(&$array, $key, $value)
{
if (is_null($key)) return $array = $value;
$keys = explode('.', $key);
// 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.
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 necessary to hold the final value at the proper depth.
if ( ! isset($array[$key]) or ! is_array($array[$key]))
{
$array[$key] = array();
}
$array =& $array[$key];
}
$array[array_shift($keys)] = $value;
}
/**
* Remove an array item from a given array using "dot" notation.
*
* <code>
* // Remove the $array['user']['name'] item from the array
* array_forget($array, 'user.name');
*
* // Remove the $array['user']['name']['first'] item from the array
* array_forget($array, 'user.name.first');
* </code>
*
* @param array $array
* @param string $key
* @return void
*/
function array_forget(&$array, $key)
{
$keys = explode('.', $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.
while (count($keys) > 1)
{
$key = array_shift($keys);
// 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.
if ( ! isset($array[$key]) or ! is_array($array[$key]))
{
return;
}
$array =& $array[$key];
}
unset($array[array_shift($keys)]);
}
/**
* Return the first element in an array which passes a given truth test.
*
* <code>
* // Return the first array element that equals "Taylor"
* $value = array_first($array, function($k, $v) {return $v == 'Taylor';});
*
* // Return a default value if no matching element is found
* $value = array_first($array, function($k, $v) {return $v == 'Taylor'}, 'Default');
* </code>
*
* @param array $array
* @param Closure $callback
* @param mixed $default
* @return mixed
*/
function array_first($array, $callback, $default = null)
{
foreach ($array as $key => $value)
{
if (call_user_func($callback, $key, $value)) return $value;
}
return value($default);
}
/**
* Spin through the array, executing a callback with each key and element.
*
* @param array $array
* @param mixed $callback
* @return array
*/
function array_spin($array, $callback)
{
return array_map($callback, array_keys($array), array_values($array));
}
/**
* Return the first element of an array.
*
* This is simply a convenient wrapper around the "reset" method.
*
* @param array $array
* @return mixed
*/
function head($array)
{
return reset($array);
}
/**
* Generate an application URL.
*
* <code>
* // Create a URL to a location within the application
* $url = path('user/profile');
*
* // Create a HTTPS URL to a location within the application
* $url = path('user/profile', true);
* </code>
*
* @param string $url
* @param bool $https
* @return string
*/
function path($url = '', $https = false)
{
return Laravel\URL::to($url, $https);
}
/**
* Generate an application URL to an asset.
*
* @param string $url
* @param bool $https
* @return string
*/
function asset($url, $https = false)
{
return Laravel\URL::to_asset($url, $https);
}
/**
* Generate a URL to a controller action.
*
* <code>
* // Generate a URL to the "index" method of the "user" controller
* $url = action('user@index');
*
* // Generate a URL to http://example.com/user/profile/taylor
* $url = action('user@profile', array('taylor'));
* </code>
*
* @param string $action
* @param array $parameters
* @param bool $https
* @return string
*/
function action($action, $parameters = array(), $https = false)
{
return Laravel\URL::to_action($action, $parameters, $https);
}
/**
* Generate a URL from a route name.
*
* <code>
* // Create a URL to the "profile" named route
* $url = route('profile');
*
* // Create a URL to the "profile" named route with wildcard parameters
* $url = route('profile', array($username));
* </code>
*
* @param string $name
* @param array $parameters
* @param bool $https
* @return string
*/
function route($name, $parameters = array(), $https = false)
{
return Laravel\URL::to_route($name, $parameters, $https);
}
/**
* Determine if a given string begins with a given value.
*
* @param string $haystack
* @param string $needle
* @return bool
*/
function starts_with($haystack, $needle)
{
return strpos($haystack, $needle) === 0;
}
/**
* Determine if a given string contains a given sub-string.
*
* @param string $haystack
* @param string $needle
* @return bool
*/
function str_contains($haystack, $needle)
{
return strpos($haystack, $needle) !== false;
}
/**
* Return the value of the given item.
*
* If the given item is a Closure the result of the Closure will be returned.
*
* @param mixed $value
* @return mixed
*/
function value($value)
{
return ($value instanceof Closure) ? call_user_func($value) : $value;
} }

View File

@ -12,7 +12,7 @@ class HTML {
*/ */
public static function entities($value) public static function entities($value)
{ {
return htmlentities($value, ENT_QUOTES, Config::$items['application']['encoding'], false); return htmlentities($value, ENT_QUOTES, Config::get('application.encoding'), false);
} }
/** /**
@ -58,13 +58,7 @@ public static function style($url, $attributes = array())
{ {
$defaults = array('media' => 'all', 'type' => 'text/css', 'rel' => 'stylesheet'); $defaults = array('media' => 'all', 'type' => 'text/css', 'rel' => 'stylesheet');
foreach ($defaults as $attribute => $default) $attributes = $attributes + $defaults;
{
if ( ! array_key_exists($attribute, $attributes))
{
$attributes[$attribute] = $default;
}
}
$url = static::entities(URL::to_asset($url)); $url = static::entities(URL::to_asset($url));
@ -274,6 +268,9 @@ private static function listing($type, $list, $attributes = array())
foreach ($list as $key => $value) foreach ($list as $key => $value)
{ {
// If the value is an array, we will recurse the function so that we can
// produce a nested list within the list being built. Of course, nested
// lists may exist within nested lists, etc.
if (is_array($value)) if (is_array($value))
{ {
$html .= static::listing($type, $value); $html .= static::listing($type, $value);
@ -290,9 +287,6 @@ private static function listing($type, $list, $attributes = array())
/** /**
* Build a list of HTML attributes from an array. * Build a list of HTML attributes from an array.
* *
* Numeric-keyed attributes will be assigned the same key and value to handle
* attributes such as "autofocus" and "required".
*
* @param array $attributes * @param array $attributes
* @return string * @return string
*/ */
@ -302,6 +296,9 @@ public static function attributes($attributes)
foreach ((array) $attributes as $key => $value) foreach ((array) $attributes as $key => $value)
{ {
// For numeric keys, we will assume that the key and the value are the
// same, as this will conver HTML attributes such as "required" that
// may be specified as required="required".
if (is_numeric($key)) $key = $value; if (is_numeric($key)) $key = $value;
if ( ! is_null($value)) if ( ! is_null($value))
@ -325,19 +322,20 @@ protected static function obfuscate($value)
foreach (str_split($value) as $letter) foreach (str_split($value) as $letter)
{ {
// To properly obfuscate the value, we will randomly convert each
// letter to its entity or hexadecimal representation, keeping a
// bot from sniffing the randomly obfuscated letters from the
// page and guarding against e-mail harvesting.
switch (rand(1, 3)) switch (rand(1, 3))
{ {
// Convert the letter to its entity representation.
case 1: case 1:
$safe .= '&#'.ord($letter).';'; $safe .= '&#'.ord($letter).';';
break; break;
// Convert the letter to a Hex character code.
case 2: case 2:
$safe .= '&#x'.dechex(ord($letter)).';'; $safe .= '&#x'.dechex(ord($letter)).';';
break; break;
// No encoding.
case 3: case 3:
$safe .= $letter; $safe .= $letter;
} }
@ -346,39 +344,4 @@ protected static function obfuscate($value)
return $safe; return $safe;
} }
/**
* Magic Method for handling dynamic static methods.
*
* This method primarily handles dynamic calls to create links to named routes.
*
* <code>
* // Generate a link to the "profile" named route
* echo HTML::link_to_profile('Profile');
*
* // Generate a link to the "profile" route and add some parameters
* echo HTML::link_to_profile('Profile', array('taylor'));
*
* // Generate a link to the "profile" named route using HTTPS
* echo HTML::link_to_secure_profile('Profile');
* </code>
*/
public static function __callStatic($method, $parameters)
{
if (strpos($method, 'link_to_secure_') === 0)
{
array_unshift($parameters, substr($method, 15));
return forward_static_call_array('HTML::link_to_secure_route', $parameters);
}
if (strpos($method, 'link_to_') === 0)
{
array_unshift($parameters, substr($method, 8));
return forward_static_call_array('HTML::link_to_route', $parameters);
}
throw new \BadMethodCallException("Method [$method] is not defined on the HTML class.");
}
} }

View File

@ -1,205 +0,0 @@
<?php namespace Laravel;
class Inflector {
/**
* The words that have been converted to singular.
*
* @var array
*/
protected static $singular_cache = array();
/**
* The words that have been converted to plural.
*
* @var array
*/
protected static $plural_cache = array();
/**
* Plural word forms.
*
* @var array
*/
private static $plural = array(
'/(quiz)$/i' => "$1zes",
'/^(ox)$/i' => "$1en",
'/([m|l])ouse$/i' => "$1ice",
'/(matr|vert|ind)ix|ex$/i' => "$1ices",
'/(x|ch|ss|sh)$/i' => "$1es",
'/([^aeiouy]|qu)y$/i' => "$1ies",
'/(hive)$/i' => "$1s",
'/(?:([^f])fe|([lr])f)$/i' => "$1$2ves",
'/(shea|lea|loa|thie)f$/i' => "$1ves",
'/sis$/i' => "ses",
'/([ti])um$/i' => "$1a",
'/(tomat|potat|ech|her|vet)o$/i' => "$1oes",
'/(bu)s$/i' => "$1ses",
'/(alias)$/i' => "$1es",
'/(octop)us$/i' => "$1i",
'/(ax|test)is$/i' => "$1es",
'/(us)$/i' => "$1es",
'/s$/i' => "s",
'/$/' => "s"
);
/**
* Singular word forms.
*
* @var array
*/
private static $singular = array(
'/(quiz)zes$/i' => "$1",
'/(matr)ices$/i' => "$1ix",
'/(vert|ind)ices$/i' => "$1ex",
'/^(ox)en$/i' => "$1",
'/(alias)es$/i' => "$1",
'/(octop|vir)i$/i' => "$1us",
'/(cris|ax|test)es$/i' => "$1is",
'/(shoe)s$/i' => "$1",
'/(o)es$/i' => "$1",
'/(bus)es$/i' => "$1",
'/([m|l])ice$/i' => "$1ouse",
'/(x|ch|ss|sh)es$/i' => "$1",
'/(m)ovies$/i' => "$1ovie",
'/(s)eries$/i' => "$1eries",
'/([^aeiouy]|qu)ies$/i' => "$1y",
'/([lr])ves$/i' => "$1f",
'/(tive)s$/i' => "$1",
'/(hive)s$/i' => "$1",
'/(li|wi|kni)ves$/i' => "$1fe",
'/(shea|loa|lea|thie)ves$/i' => "$1f",
'/(^analy)ses$/i' => "$1sis",
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => "$1$2sis",
'/([ti])a$/i' => "$1um",
'/(n)ews$/i' => "$1ews",
'/(h|bl)ouses$/i' => "$1ouse",
'/(corpse)s$/i' => "$1",
'/(us)es$/i' => "$1",
'/(us|ss)$/i' => "$1",
'/s$/i' => "",
);
/**
* Irregular word forms.
*
* @var array
*/
private static $irregular = array(
'child' => 'children',
'foot' => 'feet',
'goose' => 'geese',
'man' => 'men',
'move' => 'moves',
'person' => 'people',
'sex' => 'sexes',
'tooth' => 'teeth',
);
/**
* Uncountable word forms.
*
* @var array
*/
private static $uncountable = array(
'audio',
'equipment',
'deer',
'fish',
'gold',
'information',
'money',
'rice',
'police',
'series',
'sheep',
'species',
);
/**
* Convert a word to its plural form.
*
* Optionally, a count argument may be provided. If the count is greater than
* one, the word will be pluralized, otherwise the word will be returned from
* the method unchanged.
*
* <code>
* // Get the plural form of the word "child"
* $children = Inflector::plural('child');
*
* // Returns "comments"
* $comments = Inflector::plural('comment', 10);
*
* // Returns "comment"
* $comment = Inflector::plural('comment', 1);
* </code>
*
* @param string $value
* @param int $count
* @return string
*/
public static function plural($value, $count = null)
{
if ( ! is_null($count) and $count == 1) return $value;
$irregular = array_flip(static::$irregular);
$plural = static::inflect($value, static::$plural_cache, $irregular, static::$plural);
return static::$plural_cache[$value] = $plural;
}
/**
* Convert a word to its singular form.
*
* @param string $value
* @return string
*/
public static function singular($value)
{
$singular = static::inflect($value, static::$singular_cache, static::$irregular, static::$singular);
return static::$singular_cache[$value] = $singular;
}
/**
* Convert a word to its singular or plural form.
*
* @param string $value
* @param array $cache
* @param array $irregular
* @param array $source
* @return string
*/
protected static function inflect($value, $cache, $irregular, $source)
{
if (array_key_exists($value, $cache))
{
return $cache[$value];
}
if (in_array(strtolower($value), static::$uncountable))
{
return $value;
}
foreach ($irregular as $irregular => $pattern)
{
if (preg_match($pattern = '/'.$pattern.'$/i', $value))
{
return preg_replace($pattern, $irregular, $value);
}
}
foreach ($source as $pattern => $inflected)
{
if (preg_match($pattern, $value))
{
return preg_replace($pattern, $inflected, $value);
}
}
return $value;
}
}

View File

@ -17,9 +17,7 @@ class Input {
const old_input = 'laravel_old_input'; const old_input = 'laravel_old_input';
/** /**
* Get all of the input data for the request. * Get all of the input data for the request, including files.
*
* This method returns a merged array containing Input::get() and Input::files().
* *
* @return array * @return array
*/ */
@ -31,7 +29,7 @@ public static function all()
/** /**
* Determine if the input data contains an item. * Determine if the input data contains an item.
* *
* If the item is in the input array, but is an empty string, false will be returned. * If the input item is an empty string, false will be returned.
* *
* @param string $key * @param string $key
* @return bool * @return bool
@ -44,7 +42,7 @@ public static function has($key)
/** /**
* Get an item from the input data. * Get an item from the input data.
* *
* This method should be used for all request methods (GET, POST, PUT, and DELETE). * This method is used for all request verbs (GET, POST, PUT, and DELETE).
* *
* <code> * <code>
* // Get the "email" item from the input array * // Get the "email" item from the input array
@ -60,56 +58,45 @@ public static function has($key)
*/ */
public static function get($key = null, $default = null) public static function get($key = null, $default = null)
{ {
return Arr::get(static::$input, $key, $default); return array_get(static::$input, $key, $default);
} }
/** /**
* Flash the input for the current request to the session. * Get a subset of the items from the input data.
*
* The input data to be flashed may be controlled by using a filter and an array
* of included or excluded input data. This provides a convenient way of keeping
* sensitive information like passwords out of the session.
* *
* <code> * <code>
* // Flash all of the input data to the session * // Get only the email from the input data
* Input::flash(); * $value = Input::only('email');
* *
* // Flash only a few input items to the session * // Get only the username and email from the input data
* Input::flash('only', array('name', 'email')); * $input = Input::only(array('username', 'email'));
*
* // Flash all but a few input items to the session
* Input::flash('except', array('password'));
* </code> * </code>
* *
* @return void * @param array $keys
* @return array
*/ */
public static function flash($filter = null, $items = array()) public static function only($keys)
{ {
$flash = static::get(); return array_intersect_key(static::get(), array_flip((array) $keys));
// Since the items flashed to the session can be filtered, we will iterate
// all of the input data and either remove or include the input item based
// on the specified filter and array of items to be flashed.
if ($filter == 'only')
{
$flash = array_intersect_key($flash, array_flip($items));
}
elseif ($filter == 'except')
{
$flash = array_diff_key($flash, array_flip($items));
}
IoC::core('session')->flash(Input::old_input, $flash);
} }
/** /**
* Flush the old input from the session. * Get all of the input data except for a specified array of items.
* *
* @return void * <code>
* // Get all of the input data except for username
* $input = Input::except('username');
*
* // Get all of the input data except for username and email
* $input = Input::except(array('username', 'email'));
* </code>
*
* @param array $keys
* @return array
*/ */
public static function flush() public static function except($keys)
{ {
IoC::core('session')->flash(Input::old_input, array()); return array_diff_key(static::get(), array_flip($keys));
} }
/** /**
@ -140,9 +127,7 @@ public static function had($key)
*/ */
public static function old($key = null, $default = null) public static function old($key = null, $default = null)
{ {
$old = IoC::core('session')->get(Input::old_input, array()); return array_get(Session::get(Input::old_input, array()), $key, $default);
return Arr::get($old, $key, $default);
} }
/** /**
@ -152,7 +137,7 @@ public static function old($key = null, $default = null)
* // Get the array of information for the "picture" upload * // Get the array of information for the "picture" upload
* $picture = Input::file('picture'); * $picture = Input::file('picture');
* *
* // Get a specific element from the file array * // Get a specific element from within the file's data array
* $size = Input::file('picture.size'); * $size = Input::file('picture.size');
* </code> * </code>
* *
@ -162,7 +147,7 @@ public static function old($key = null, $default = null)
*/ */
public static function file($key = null, $default = null) public static function file($key = null, $default = null)
{ {
return Arr::get($_FILES, $key, $default); return array_get($_FILES, $key, $default);
} }
/** /**
@ -171,8 +156,8 @@ public static function file($key = null, $default = null)
* This method is simply a convenient wrapper around move_uploaded_file. * This method is simply a convenient wrapper around move_uploaded_file.
* *
* <code> * <code>
* // Move the "picture" item from the $_FILES array to a permanent location * // Move the "picture" file to a permanent location on disk
* Input::upload('picture', 'path/to/storage/picture.jpg'); * Input::upload('picture', 'path/to/photos/picture.jpg');
* </code> * </code>
* *
* @param string $key * @param string $key
@ -181,7 +166,44 @@ public static function file($key = null, $default = null)
*/ */
public static function upload($key, $path) public static function upload($key, $path)
{ {
return File::upload($key, $path); if (is_null(static::file($key))) return false;
return move_uploaded_file(static::file("{$key}.tmp_name"), $path);
}
/**
* Flash the input for the current request to the session.
*
* <code>
* // Flash all of the input to the session
* Input::flash();
*
* // Flash only a few input items to the session
* Input::flash('only', array('name', 'email'));
*
* // Flash all but a few input items to the session
* Input::flash('except', array('password', 'social_number'));
* </code>
*
* @param string $filter
* @param array $keys
* @return void
*/
public static function flash($filter = null, $keys = array())
{
$flash = ( ! is_null($filter)) ? static::$filter($keys) : static::get();
Session::flash(Input::old_input, $flash);
}
/**
* Flush all of the old input from the session.
*
* @return void
*/
public static function flush()
{
Session::flash(Input::old_input, array());
} }
} }

View File

@ -1,4 +1,4 @@
<?php namespace Laravel; <?php namespace Laravel; use Closure;
class IoC { class IoC {
@ -7,49 +7,25 @@ class IoC {
* *
* @var array * @var array
*/ */
protected static $registry = array(); public static $registry = array();
/** /**
* The resolved singleton instances. * The resolved singleton instances.
* *
* @var array * @var array
*/ */
protected static $singletons = array(); public static $singletons = array();
/**
* Bootstrap the application IoC container.
*
* This method is called automatically the first time the class is loaded.
*
* @param array $registry
* @return void
*/
public static function bootstrap($registry = array())
{
if (Config::load('container'))
{
static::$registry = Config::$items['container'];
}
}
/** /**
* Register an object and its resolver. * Register an object and its resolver.
* *
* The IoC container instance is always passed to the resolver, allowing the
* nested resolution of other objects from the container.
*
* <code>
* // Register an object and its resolver
* IoC::register('mailer', function($c) {return new Mailer;});
* </code>
*
* @param string $name * @param string $name
* @param Closure $resolver * @param Closure $resolver
* @return void * @return void
*/ */
public static function register($name, $resolver, $singleton = false) public static function register($name, Closure $resolver, $singleton = false)
{ {
static::$registry[$name] = array('resolver' => $resolver, 'singleton' => $singleton); static::$registry[$name] = compact('resolver', 'singleton');
} }
/** /**
@ -67,7 +43,6 @@ public static function registered($name)
* Register an object as a singleton. * Register an object as a singleton.
* *
* Singletons will only be instantiated the first time they are resolved. * Singletons will only be instantiated the first time they are resolved.
* The same instance will be returned on subsequent requests.
* *
* @param string $name * @param string $name
* @param Closure $resolver * @param Closure $resolver
@ -79,10 +54,7 @@ public static function singleton($name, $resolver)
} }
/** /**
* Register an instance as a singleton. * Register an existing instance as a singleton.
*
* This method allows you to register an already existing object instance
* with the container to be managed as a singleton instance.
* *
* <code> * <code>
* // Register an instance as a singleton in the container * // Register an instance as a singleton in the container
@ -105,11 +77,8 @@ public static function instance($name, $instance)
* // Resolve the "laravel.router" class from the container * // Resolve the "laravel.router" class from the container
* $input = IoC::core('router'); * $input = IoC::core('router');
* *
* // Equivalent resolution using the "resolve" method * // Equivalent resolution of the router using the "resolve" method
* $input = IoC::resolve('laravel.router'); * $input = IoC::resolve('laravel.router');
*
* // Pass an array of parameters to the resolver
* $input = IoC::core('router', array('test'));
* </code> * </code>
* *
* @param string $name * @param string $name
@ -128,7 +97,7 @@ public static function core($name, $parameters = array())
* // Get an instance of the "mailer" object registered in the container * // Get an instance of the "mailer" object registered in the container
* $mailer = IoC::resolve('mailer'); * $mailer = IoC::resolve('mailer');
* *
* // Pass an array of parameters to the resolver * // Get an instance of the "mailer" object and pass parameters to the resolver
* $mailer = IoC::resolve('mailer', array('test')); * $mailer = IoC::resolve('mailer', array('test'));
* </code> * </code>
* *
@ -145,12 +114,19 @@ public static function resolve($name, $parameters = array())
if ( ! static::registered($name)) if ( ! static::registered($name))
{ {
throw new \OutOfBoundsException("Error resolving [$name]. No resolver has been registered."); throw new \Exception("Error resolving [$name]. No resolver has been registered.");
} }
$object = call_user_func(static::$registry[$name]['resolver'], $parameters); $object = call_user_func(static::$registry[$name]['resolver'], $parameters);
if (isset(static::$registry[$name]['singleton']) and static::$registry[$name]['singleton']) // If the resolver is registering as a singleton resolver, we will cache
// the instance of the object in the container so we can resolve it next
// time without having to instantiate a new instance of the object.
//
// This allows the developer to reuse objects that do not need to be
// instantiated each time they are needed, such as a SwiftMailer or
// Twig object that can be shared.
if (isset(static::$registry[$name]['singleton']))
{ {
return static::$singletons[$name] = $object; return static::$singletons[$name] = $object;
} }
@ -158,11 +134,4 @@ public static function resolve($name, $parameters = array())
return $object; return $object;
} }
} }
/**
* We only bootstrap the IoC container once the class has been
* loaded since there isn't any reason to load the container
* configuration until the class is first requested.
*/
IoC::bootstrap();

View File

@ -26,7 +26,7 @@ class Lang {
/** /**
* All of the loaded language lines. * All of the loaded language lines.
* *
* The array is keyed by [$language][$file]. * The array is keyed by [$bundle][$language][$file].
* *
* @var array * @var array
*/ */
@ -54,6 +54,9 @@ protected function __construct($key, $replacements = array(), $language = null)
* // Create a new language line instance for a given line * // Create a new language line instance for a given line
* $line = Lang::line('validation.required'); * $line = Lang::line('validation.required');
* *
* // Create a new language line for a line belonging to a bundle
* $line = Lang::line('admin::messages.welcome');
*
* // Specify some replacements for the language line * // Specify some replacements for the language line
* $line = Lang::line('validation.required', array('attribute' => 'email')); * $line = Lang::line('validation.required', array('attribute' => 'email'));
* </code> * </code>
@ -65,10 +68,7 @@ protected function __construct($key, $replacements = array(), $language = null)
*/ */
public static function line($key, $replacements = array(), $language = null) public static function line($key, $replacements = array(), $language = null)
{ {
if (is_null($language)) if (is_null($language)) $language = Config::get('application.language');
{
$language = Config::$items['application']['language'];
}
return new static($key, $replacements, $language); return new static($key, $replacements, $language);
} }
@ -76,9 +76,6 @@ public static function line($key, $replacements = array(), $language = null)
/** /**
* Get the language line as a string. * Get the language line as a string.
* *
* If a language is specified, it should correspond to a directory
* within your application language directory.
*
* <code> * <code>
* // Get a language line * // Get a language line
* $line = Lang::line('validation.required')->get(); * $line = Lang::line('validation.required')->get();
@ -98,92 +95,104 @@ public function get($language = null, $default = null)
{ {
if (is_null($language)) $language = $this->language; if (is_null($language)) $language = $this->language;
list($file, $line) = $this->parse($this->key); list($bundle, $file, $line) = $this->parse($this->key);
if ( ! $this->load($file)) // If the file doesn't exist, we'll just return the default value that was
// given to the method. The default value is also returned even when the
// file exists and the file does not actually contain any lines.
if ( ! static::load($bundle, $language, $file))
{ {
return ($default instanceof Closure) ? call_user_func($default) : $default; return value($default);
} }
return $this->replace(Arr::get(static::$lines[$language][$file], $line, $default)); $lines = static::$lines[$bundle][$language][$file];
}
/** $line = array_get($lines, $line, $default);
* Make all necessary replacements on a language line.
* // If the line is not a string, it probably means the developer asked for
* Replacements place-holder are prefixed with a colon, and are replaced // the entire langauge file and the value of the requested value will be
* with the appropriate value based on the replacement array set for the // an array containing all of the lines in the file.
* language line instance. if (is_string($line))
*
* @param string $line
* @return string
*/
protected function replace($line)
{
foreach ($this->replacements as $key => $value)
{ {
$line = str_replace(':'.$key, $value, $line); foreach ($this->replacements as $key => $value)
{
$line = str_replace(':'.$key, $value, $line);
}
} }
return $line; return $line;
} }
/** /**
* Parse a language key into its file and line segments. * Parse a language key into its bundle, file, and line segments.
* *
* Language keys are formatted similarly to configuration keys. The first * Language lines follow a {bundle}::{file}.{line} naming convention.
* segment represents the language file, while the second segment
* represents a language line within that file.
* *
* @param string $key * @param string $key
* @return array * @return array
*/ */
protected function parse($key) protected function parse($key)
{ {
if (count($segments = explode('.', $key)) > 1) $bundle = Bundle::name($key);
{
return array($segments[0], implode('.', array_slice($segments, 1)));
}
throw new \InvalidArgumentException("Invalid language line [$key]."); $segments = explode('.', Bundle::element($key));
// If there are not at least two segments in the array, it means that
// the developer is requesting the entire language line array to be
// returned. If that is the case, we'll make the item "null".
if (count($segments) >= 2)
{
$line = implode('.', array_slice($segments, 1));
return array($bundle, $segments[0], $line);
}
else
{
return array($bundle, $segments[0], null);
}
} }
/** /**
* Load all of the language lines from a language file. * Load all of the language lines from a language file.
* *
* If the language file is successfully loaded, true will be returned. * @param string $bundle
* * @param string $language
* @param string $file * @param string $file
* @return bool * @return bool
*/ */
protected function load($file) public static function load($bundle, $language, $file)
{ {
if (isset(static::$lines[$this->language][$file])) return true; if (isset(static::$lines[$bundle][$language][$file]))
$language = array();
if (file_exists($path = LANG_PATH.$this->language.'/'.$file.EXT))
{ {
$language = array_merge($language, require $path); return true;
} }
// If language lines were actually found, they will be loaded into $lines = array();
// the array containing all of the lines for all languages and files.
// The array is keyed by the language and the file name. // Language files can belongs to the application or to any bundle
if (count($language) > 0) // that is installed for the application. So, we'll need to use
{ // the bundle's path when checking for the file.
static::$lines[$this->language][$file] = $language; //
} // This is similar to the loading method for configuration files,
// but we do not need to cascade across directories since most
return isset(static::$lines[$this->language][$file]); // likely language files are static across environments.
$path = Bundle::path($bundle)."language/{$language}/{$file}".EXT;
if (file_exists($path)) $lines = require $path;
static::$lines[$bundle][$language][$file] = $lines;
return count($lines) > 0;
} }
/** /**
* Get the string content of the language line. * Get the string content of the language line.
*
* @return string
*/ */
public function __toString() public function __toString()
{ {
return $this->get(); return (string) $this->get();
} }
} }

View File

@ -1,61 +1,26 @@
<?php namespace Laravel; <?php namespace Laravel;
/** /**
* Bootstrap the core framework components like the IoC container, * Bootstrap the core framework components like the IoC container and
* configuration class, and the class auto-loader. Once this file * the configuration class, and the class auto-loader. Once this file
* has run, the framework is essentially ready for use. * has run, the framework is essentially ready for use.
*/ */
require 'core.php'; require 'core.php';
/** /**
* Create the exception logging function. All of the error logging * Register the default timezone for the application. This will be the
* is routed through here to avoid duplicate code. This Closure * default timezone used by all date / timezone functions throughout
* will determine if the actual logging Closure should be called. * the entire application.
*/ */
$logger = function($exception) date_default_timezone_set(Config::get('application.timezone'));
{
if (Config::$items['error']['log'])
{
call_user_func(Config::$items['error']['logger'], $exception);
}
};
/**
* Create the exception handler function. All of the error handlers
* registered by the framework call this closure to avoid duplicate
* code. This Closure will pass the exception to the developer
* defined handler in the configuration file.
*/
$handler = function($exception) use ($logger)
{
$logger($exception);
if (Config::$items['error']['detail'])
{
echo "<html><h2>Unhandled Exception</h2>
<h3>Message:</h3>
<pre>".$exception->getMessage()."</pre>
<h3>Location:</h3>
<pre>".$exception->getFile()." on line ".$exception->getLine()."</pre>
<h3>Stack Trace:</h3>
<pre>".$exception->getTraceAsString()."</pre></html>";
}
else
{
Response::error('500')->send();
}
exit(1);
};
/** /**
* Register the PHP exception handler. The framework throws exceptions * Register the PHP exception handler. The framework throws exceptions
* on every error that cannot be handled. All of those exceptions will * on every error that cannot be handled. All of those exceptions will
* be sent through this closure for processing. * be sent through this closure for processing.
*/ */
set_exception_handler(function($exception) use ($handler) set_exception_handler(function($e)
{ {
$handler($exception); Error::exception($e);
}); });
/** /**
@ -65,18 +30,9 @@
* errors are ignored and errors in the developer configured whitelist * errors are ignored and errors in the developer configured whitelist
* are silently logged. * are silently logged.
*/ */
set_error_handler(function($code, $error, $file, $line) use ($logger) set_error_handler(function($code, $error, $file, $line)
{ {
if (error_reporting() === 0) return; Error::native($code, $error, $file, $line);
$exception = new \ErrorException($error, $code, 0, $file, $line);
if (in_array($code, Config::$items['error']['ignore']))
{
return $logger($exception);
}
throw $exception;
}); });
/** /**
@ -85,14 +41,9 @@
* has occured, we will convert it to an ErrorException and pass it * has occured, we will convert it to an ErrorException and pass it
* to the common exception handler for the framework. * to the common exception handler for the framework.
*/ */
register_shutdown_function(function() use ($handler) register_shutdown_function(function()
{ {
if ( ! is_null($error = error_get_last())) Error::shutdown();
{
extract($error, EXTR_SKIP);
$handler(new \ErrorException($message, $type, 0, $file, $line));
}
}); });
/** /**
@ -114,21 +65,17 @@
* payload will be registered in the IoC container as an instance * payload will be registered in the IoC container as an instance
* so it can be retrieved easily throughout the application. * so it can be retrieved easily throughout the application.
*/ */
if (Config::$items['session']['driver'] !== '') if (Config::get('session.driver') !== '')
{ {
$driver = Session\Drivers\Factory::make(Config::$items['session']['driver']); Session::start(Config::get('session.driver'));
$session = new Session\Payload($driver); Session::load(Cookie::get(Config::get('session.cookie')));
$session->load(Cookie::get(Config::$items['session']['cookie']));
IoC::instance('laravel.session', $session);
} }
/** /**
* Gather the input to the application based on the current request. * Gather the input to the application based on the current request.
* The input will be gathered based on the current request method and * The input will be gathered based on the current request method
* will be set on the Input manager. * and will be set on the Input manager.
*/ */
$input = array(); $input = array();
@ -157,28 +104,55 @@
/** /**
* The spoofed request method is removed from the input so it is not * The spoofed request method is removed from the input so it is not
* unexpectedly included in Input::all() or Input::get(). Leaving it * unexpectedly included in Input::all() or Input::get(). Leaving it
* in the input array could cause unexpected results if the developer * in the input array could cause unexpected results if an Eloquent
* fills an Eloquent model with the input. * model is filled with the input.
*/ */
unset($input[Request::spoofer]); unset($input[Request::spoofer]);
Input::$input = $input; Input::$input = $input;
/**
* Start all of the bundles that are specified in the configuration
* array of auto-loaded bundles. This gives the developer the ability
* to conveniently and automatically load bundles that are used on
* every request to their application.
*/
foreach (Config::get('application.bundles') as $bundle)
{
Bundle::start($bundle);
}
/**
* Load the "application" bundle. Though the application folder is
* not typically considered a bundle, it is started like one and
* essentially serves as the "default" bundle.
*/
Bundle::start(DEFAULT_BUNDLE);
/**
* If the first segment of the request URI corresponds with a bundle,
* we will start that bundle. By convention, bundles handle all URIs
* which begin with their bundle name.
*/
$bundle = URI::segment(1);
if ( ! is_null($bundle) and Bundle::routable($bundle))
{
Bundle::start($bundle);
}
/** /**
* Route the request to the proper route in the application. If a * Route the request to the proper route in the application. If a
* route is found, the route will be called with the current request * route is found, the route will be called with the current request
* instance. If no route is found, the 404 response will be returned * instance. If no route is found, the 404 response will be returned
* to the browser. * to the browser.
*/ */
Routing\Filter::register(require APP_PATH.'filters'.EXT); if (count(URI::$segments) > 15)
{
throw new \Exception("Invalid request. Too many URI segments.");
}
$loader = new Routing\Loader(APP_PATH, ROUTE_PATH); Request::$route = Routing\Router::route(Request::method(), URI::current());
$router = new Routing\Router($loader, CONTROLLER_PATH);
IoC::instance('laravel.routing.router', $router);
Request::$route = $router->route(Request::method(), URI::current());
if ( ! is_null(Request::$route)) if ( ! is_null(Request::$route))
{ {
@ -195,9 +169,9 @@
* driver is a sweeper, session garbage collection might be * driver is a sweeper, session garbage collection might be
* performed depending on the "sweepage" probability. * performed depending on the "sweepage" probability.
*/ */
if (Config::$items['session']['driver'] !== '') if (Config::get('session.driver') !== '')
{ {
IoC::core('session')->save(); Session::save();
} }
$response->send(); $response->send();

65
laravel/log.php Normal file
View File

@ -0,0 +1,65 @@
<?php namespace Laravel;
class Log {
/**
* Log an exception to the log file.
*
* @param Exception $e
* @return void
*/
public static function exception($e)
{
static::write('error', static::format($e));
}
/**
* Format a log friendly message from the given exception.
*
* @param Exception $e
* @return string
*/
protected static function format($e)
{
return $e->getMessage().' in '.$e->getFile().' on line '.$e->getLine();
}
/**
* Write a message to the log file.
*
* <code>
* // Write an "error" messge to the log file
* Log::write('error', 'Something went horribly wrong!');
*
* // Write an "error" message using the class' magic method
* Log::error('Something went horribly wrong!');
* </code>
*
* @param string $type
* @param string $message
* @return void
*/
public static function write($type, $message)
{
$message = date('Y-m-d H:i:s').' '.Str::upper($type)." - {$message}".PHP_EOL;
File::append(STORAGE_PATH.'logs/'.date('Y-m').'.log', $message);
}
/**
* Dynamically write a log message.
*
* <code>
* // Write an "error" message to the log file
* Log::error('This is an error!');
*
* // Write a "warning" message to the log file
* Log::warning('This is a warning!');
* </code>
*/
public static function __callStatic($method, $parameters)
{
static::write($method, $parameters[0]);
}
}

View File

@ -7,42 +7,38 @@ class Memcached {
* *
* @var Memcache * @var Memcache
*/ */
protected static $instance; protected static $connection;
/** /**
* Get the Memcached connection instance. * Get the Memcached connection instance.
* *
* This connection will be managed as a singleton instance so that only * <code>
* one connection to the Memcached severs will be established. * // Get the Memcache connection and get an item from the cache
* $name = Memcached::connection()->get('name');
*
* // Get the Memcache connection and place an item in the cache
* Memcached::connection()->set('name', 'Taylor');
* </code>
* *
* @return Memcache * @return Memcache
*/ */
public static function instance() public static function connection()
{ {
if (is_null(static::$instance)) if (is_null(static::$connection))
{ {
static::$instance = static::connect(Config::get('cache.memcached')); static::$connection = static::connect(Config::get('cache.memcached'));
} }
return static::$instance; return static::$connection;
} }
/** /**
* Create a new Memcached connection instance. * Create a new Memcached connection instance.
* *
* The configuration array passed to this method should be an array of
* server hosts / ports, like those defined in the cache configuration
* file.
*
* <code>
* // Create a new localhost Memcached connection instance.
* $memcache = Memcached::connect(array('host' => '127.0.0.1', 'port' => 11211));
* </code>
*
* @param array $servers * @param array $servers
* @return Memcache * @return Memcache
*/ */
public static function connect($servers) protected static function connect($servers)
{ {
$memcache = new \Memcache; $memcache = new \Memcache;
@ -53,7 +49,7 @@ public static function connect($servers)
if ($memcache->getVersion() === false) if ($memcache->getVersion() === false)
{ {
throw new \RuntimeException('Could not establish memcached connection.'); throw new \Exception('Could not establish memcached connection.');
} }
return $memcache; return $memcache;

View File

@ -12,8 +12,6 @@ class Messages {
/** /**
* Create a new Messages instance. * Create a new Messages instance.
* *
* The Messages class provides a convenient wrapper around an array of strings.
*
* @return void * @return void
*/ */
public function __construct($messages = array()) public function __construct($messages = array())
@ -62,14 +60,14 @@ public function has($key)
} }
/** /**
* Get the first message for a given key. * Get the first message from the container for a given key.
* *
* <code> * <code>
* // Get the first message for the e-mail attribute * // Echo the first message for the e-mail attribute
* $email = $messages->first('email'); * echo $messages->first('email');
* *
* // Format the first message for the e-mail attribute * // Format the first message for the e-mail attribute
* $email = $messages->first('email', '<p>:message</p>'); * echo $messages->first('email', '<p>:message</p>');
* </code> * </code>
* *
* @param string $key * @param string $key
@ -82,14 +80,14 @@ public function first($key, $format = ':message')
} }
/** /**
* Get all of the messages for a key. * Get all of the messages from the container for a given key.
* *
* <code> * <code>
* // Get all of the messages for the e-mail attribute * // Echo all of the messages for the e-mail attribute
* $email = $messages->get('email'); * echo $messages->get('email');
* *
* // Format all of the messages for the e-mail attribute * // Format all of the messages for the e-mail attribute
* $email = $messages->get('email', '<p>:message</p>'); * echo $messages->get('email', '<p>:message</p>');
* </code> * </code>
* *
* @param string $key * @param string $key
@ -107,7 +105,7 @@ public function get($key, $format = ':message')
} }
/** /**
* Get all of the messages for every key. * Get all of the messages for every key in the container.
* *
* <code> * <code>
* // Get all of the messages in the collector * // Get all of the messages in the collector

View File

@ -173,8 +173,8 @@ public function links($adjacent = 3)
// //
// If there are not enough pages to make the creation of a slider possible // If there are not enough pages to make the creation of a slider possible
// based on the adjacent pages, we will simply display all of the pages. // based on the adjacent pages, we will simply display all of the pages.
// Otherwise, we will create a "truncating" slider which displays a nice // Otherwise, we will create a "truncating" slider which displays a
// window of pages based on the current page. // nice window of pages based on the current page.
if ($this->last < 7 + ($adjacent * 2)) if ($this->last < 7 + ($adjacent * 2))
{ {
$links = $this->range(1, $this->last); $links = $this->range(1, $this->last);
@ -288,7 +288,10 @@ protected function element($element, $page, $text, $disabled)
{ {
$class = "{$element}_page"; $class = "{$element}_page";
if (is_null($text)) $text = Lang::line("pagination.{$element}")->get($this->language); if (is_null($text))
{
$text = Lang::line("pagination.{$element}")->get($this->language);
}
// Each consumer of this method provides a "disabled" Closure which can // Each consumer of this method provides a "disabled" Closure which can
// be used to determine if the element should be a span element or an // be used to determine if the element should be a span element or an
@ -383,12 +386,14 @@ protected function link($page, $text, $class)
*/ */
protected function appendage($appends) protected function appendage($appends)
{ {
if ( ! is_null($this->appendage)) if ( ! is_null($this->appendage)) return $this->appendage;
if (count($appends) <= 0)
{ {
return $this->appendage; return $this->appendage = '';
} }
return $this->appendage = (count($appends) > 0) ? '&'.http_build_query($appends) : ''; return $this->appendage = '&'.http_build_query($appends);
} }
/** /**

View File

@ -9,11 +9,8 @@ class Redirect extends Response {
* // Create a redirect response to a location within the application * // Create a redirect response to a location within the application
* return Redirect::to('user/profile'); * return Redirect::to('user/profile');
* *
* // Create a redirect with a 301 status code * // Create a redirect response with a 301 status code
* return Redirect::to('user/profile', 301); * return Redirect::to('user/profile', 301);
*
* // Create a redirect response to a location outside of the application
* return Redirect::to('http://google.com');
* </code> * </code>
* *
* @param string $url * @param string $url
@ -39,13 +36,48 @@ public static function to_secure($url, $status = 302)
} }
/** /**
* Add an item to the session flash data. * Create a redirect response to a named route.
*
* This is useful for passing status messages or other temporary data to the next request.
* *
* <code> * <code>
* // Create a redirect response and flash something to the session * // Create a redirect response to the "login" named route
* return Redirect::to('user/profile')->with('message', 'Welcome Back!'); * return Redirect::to_route('login');
*
* // Create a redirect response to the "profile" named route with parameters
* return Redirect::to_route('profile', array($username));
* </code>
*
* @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)
{
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);
}
/**
* Add an item to the session flash data.
*
* This is useful for "passing" status messages or other data to the next request.
*
* <code>
* // Create a redirect response and flash to the session
* return Redirect::to('profile')->with('message', 'Welcome Back!');
* </code> * </code>
* *
* @param string $key * @param string $key
@ -56,10 +88,10 @@ public function with($key, $value)
{ {
if (Config::get('session.driver') == '') if (Config::get('session.driver') == '')
{ {
throw new \LogicException('A session driver must be set before setting flash data.'); throw new \Exception('A session driver must be set before setting flash data.');
} }
IoC::core('session')->flash($key, $value); Session::flash($key, $value);
return $this; return $this;
} }
@ -71,13 +103,13 @@ public function with($key, $value)
* *
* <code> * <code>
* // Redirect and flash all of the input data to the session * // Redirect and flash all of the input data to the session
* return Redirect::to_login()->with_input(); * return Redirect::to('login')->with_input();
* *
* // Redirect and flash only a few of the input items * // Redirect and flash only a few of the input items
* return Redirect::to_login()->with_input('only', array('email', 'username')); * return Redirect::to('login')->with_input('only', array('email', 'username'));
* *
* // Redirect and flash all but a few of the input items * // Redirect and flash all but a few of the input items
* return Redirect::to_login()->with_input('except', array('password', 'ssn')); * return Redirect::to('login')->with_input('except', array('password', 'ssn'));
* </code> * </code>
* *
* @param string $filter * @param string $filter
@ -87,6 +119,7 @@ public function with($key, $value)
public function with_input($filter = null, $items = array()) public function with_input($filter = null, $items = array())
{ {
Input::flash($filter, $items); Input::flash($filter, $items);
return $this; return $this;
} }
@ -96,11 +129,8 @@ public function with_input($filter = null, $items = array())
* This method allows you to conveniently pass validation errors back to views. * This method allows you to conveniently pass validation errors back to views.
* *
* <code> * <code>
* // Redirect and flash a validator's errors the session * // Redirect and flash validator errors the session
* return Redirect::to('register')->with_errors($validator); * return Redirect::to('register')->with_errors($validator);
*
* // Redirect and flash a message container to the session
* return Redirect::to('register')->with_errors($messages);
* </code> * </code>
* *
* @param Validator|Messages $container * @param Validator|Messages $container
@ -113,40 +143,4 @@ public function with_errors($container)
return $this->with('errors', $errors); return $this->with('errors', $errors);
} }
/**
* Magic Method to handle creation of redirects to named routes.
*
* <code>
* // Create a redirect response to the "profile" named route
* return Redirect::to_profile();
*
* // Create a redirect response to a named route using HTTPS
* return Redirect::to_secure_profile();
*
* // Create a redirect response to a named route with wildcard parameters
* return Redirect::to_profile(array($username));
* </code>
*/
public static function __callStatic($method, $parameters)
{
// Extract the parameters that should be placed in the URL. These parameters
// are used to fill all of the wildcard slots in the route URI definition.
// They are passed as the first parameter to this magic method.
$wildcards = (isset($parameters[0])) ? $parameters[0] : array();
$status = (isset($parameters[1])) ? $parameters[1] : 302;
if (strpos($method, 'to_secure_') === 0)
{
return static::to(URL::to_route(substr($method, 10), $wildcards, true), $status);
}
if (strpos($method, 'to_') === 0)
{
return static::to(URL::to_route(substr($method, 3), $wildcards), $status);
}
throw new \BadMethodCallException("Method [$method] is not defined on the Redirect class.");
}
} }

View File

@ -65,7 +65,7 @@ public static function db($name = 'default')
{ {
if (is_null($config = Config::get("database.redis.{$name}"))) if (is_null($config = Config::get("database.redis.{$name}")))
{ {
throw new \DomainException("Redis database [$name] is not defined."); throw new \Exception("Redis database [$name] is not defined.");
} }
static::$databases[$name] = new static($config['host'], $config['port']); static::$databases[$name] = new static($config['host'], $config['port']);
@ -98,7 +98,7 @@ public function run($method, $parameters)
switch (substr($response, 0, 1)) switch (substr($response, 0, 1))
{ {
case '-': case '-':
throw new \RuntimeException('Redis error: '.substr(trim($response), 4)); throw new \Exception('Redis error: '.substr(trim($response), 4));
case '+': case '+':
case ':': case ':':
@ -111,7 +111,7 @@ public function run($method, $parameters)
return $this->multibulk($response); return $this->multibulk($response);
default: default:
throw new \UnexpectedValueException("Unknown Redis response: ".substr($response, 0, 1)); throw new \Exception("Unknown Redis response: ".substr($response, 0, 1));
} }
} }
@ -128,7 +128,7 @@ protected function connect()
if ($this->connection === false) if ($this->connection === false)
{ {
throw new \RuntimeException("Error making Redis connection: {$error} - {$message}"); throw new \Exception("Error making Redis connection: {$error} - {$message}");
} }
return $this->connection; return $this->connection;

View File

@ -1,7 +1,6 @@
<?php namespace Laravel; <?php namespace Laravel;
use Closure; use Closure;
use Laravel\Session\Payload as Session;
class Request { class Request {
@ -22,10 +21,6 @@ class Request {
/** /**
* Get the URI for the current request. * Get the URI for the current request.
* *
* If the request is to the root of the application, a single forward slash
* will be returned. Otherwise, the URI will be returned with all of the
* leading and trailing slashes removed.
*
* @return string * @return string
*/ */
public static function uri() public static function uri()
@ -36,10 +31,6 @@ public static function uri()
/** /**
* Get the request method. * Get the request method.
* *
* This will usually be the value of the REQUEST_METHOD $_SERVER variable
* However, when the request method is spoofed using a hidden form value,
* the method will be stored in the $_POST array.
*
* @return string * @return string
*/ */
public static function method() public static function method()
@ -50,15 +41,13 @@ public static function method()
/** /**
* Get an item from the $_SERVER array. * Get an item from the $_SERVER array.
* *
* Like most array retrieval methods, a default value may be specified.
*
* @param string $key * @param string $key
* @param mixed $default * @param mixed $default
* @return string * @return string
*/ */
public static function server($key = null, $default = null) public static function server($key = null, $default = null)
{ {
return Arr::get($_SERVER, strtoupper($key), $default); return array_get($_SERVER, strtoupper($key), $default);
} }
/** /**
@ -92,7 +81,7 @@ public static function ip($default = '0.0.0.0')
return $_SERVER['REMOTE_ADDR']; return $_SERVER['REMOTE_ADDR'];
} }
return ($default instanceof Closure) ? call_user_func($default) : $default; return value($default);
} }
/** /**
@ -102,7 +91,7 @@ public static function ip($default = '0.0.0.0')
*/ */
public static function protocol() public static function protocol()
{ {
return Arr::get($_SERVER, 'SERVER_PROTOCOL', 'HTTP/1.1'); return array_get($_SERVER, 'SERVER_PROTOCOL', 'HTTP/1.1');
} }
/** /**
@ -124,7 +113,7 @@ public static function secure()
*/ */
public static function forged() public static function forged()
{ {
return Input::get(Session::csrf_token) !== IoC::core('session')->token(); return Input::get(Session::csrf_token) !== Session::token();
} }
/** /**
@ -139,6 +128,16 @@ public static function ajax()
return strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; return strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
} }
/**
* Determine if the current request is via the command line.
*
* @return bool
*/
public static function cli()
{
return defined('STDIN');
}
/** /**
* Get the route handling the current request. * Get the route handling the current request.
* *

View File

@ -14,7 +14,7 @@ class Response {
* *
* @var int * @var int
*/ */
public $status; public $status = 200;
/** /**
* The response headers. * The response headers.
@ -28,7 +28,7 @@ class Response {
* *
* @var array * @var array
*/ */
protected $statuses = array( public static $statuses = array(
100 => 'Continue', 100 => 'Continue',
101 => 'Switching Protocols', 101 => 'Switching Protocols',
200 => 'OK', 200 => 'OK',
@ -103,7 +103,7 @@ public function __construct($content, $status = 200, $headers = array())
* return Response::make('Not Found', 404); * return Response::make('Not Found', 404);
* *
* // Create a response with some custom headers * // Create a response with some custom headers
* return Respone::make(json_encode($user), 200, array('content-type' => 'application/json')); * return Response::make(json_encode($user), 200, array('header' => 'value'));
* </code> * </code>
* *
* @param mixed $content * @param mixed $content
@ -136,32 +136,12 @@ public static function view($view, $data = array())
return new static(View::make($view, $data)); return new static(View::make($view, $data));
} }
/**
* Create a new response instance containing a named view.
*
* <code>
* // Create a response with the "layout" named view
* return Response::of('layout');
*
* // Create a response with the "layout" named view and data
* return Response::of('layout', array('name' => 'Taylor'));
* </code>
*
* @param string $name
* @param array $data
* @return Response
*/
public static function of($name, $data = array())
{
return new static(View::of($name, $data));
}
/** /**
* Create a new error response instance. * Create a new error response instance.
* *
* The response status code will be set using the specified code. * The response status code will be set using the specified code.
* *
* Note: The specified error should match a view in your views/error directory. * The specified error should match a view in your views/error directory.
* *
* <code> * <code>
* // Create a 404 response * // Create a 404 response
@ -215,21 +195,32 @@ public static function download($path, $name = null, $headers = array())
} }
/** /**
* Get the evaluated string contents of the response. * Prepare a response from the given value.
* *
* @return string * If the value is not a response, it will be converted into a response
* instance and the content will be cast to a string.
*
* @param mixed $response
* @return Response
*/ */
public function render() public static function prepare($response)
{ {
return ($this->content instanceof View) ? $this->content->render() : (string) $this->content; if ( ! $response instanceof Response) $response = new static($response);
// We'll need to force the response to be a string before closing the session,
// since the developer may be using the session within a view, and we can't
// age the flash data until the view is rendered.
//
// Since this method is used by both the Route and Controller classes, it is
// a convenient spot to cast the application response to a string before it
// is returned to the main request handler.
$response->content = (string) $response->content;
return $response;
} }
/** /**
* Send the response to the browser. * Send the headers and content of the response to the browser.
*
* All of the response headers will be sent to the browser first, followed by
* the content of the response instance, which will be evaluated and rendered
* by the render method.
* *
* @return void * @return void
*/ */
@ -237,47 +228,63 @@ public function send()
{ {
if ( ! headers_sent()) $this->send_headers(); if ( ! headers_sent()) $this->send_headers();
echo $this->render(); echo (string) $this->content;
} }
/** /**
* Send all of the response headers to the browser. * Send all of the response headers to the browser.
* *
* The developer may set response headers using the "header" method. All of
* the headers set by the developer will be automatically sent to the browser
* when the response is sent via the "send" method. There is no need to call
* this method before calling the "send" method.
*
* The protocol and status header will be set automatically, as well as the
* content-type and charset, unless those headers have been set explicitly.
* The content-type charset used will be the application encoding.
*
* @return void * @return void
*/ */
public function send_headers() public function send_headers()
{ {
if ( ! isset($this->headers['Content-Type'])) // If the server is using FastCGI, we need to send a slightly different
// protocol and status header than we normally would. Otherwise it will
// not call any custom scripts setup to handle 404 responses.
//
// The status header will contain both the code and the status message,
// such as "OK" or "Not Found". For typical servers, the HTTP protocol
// will also be included with the status.
if (isset($_SERVER['FCGI_SERVER_VERSION']))
{ {
$encoding = Config::$items['application']['encoding']; header('Status: '.$this->status.' '.$this->message());
}
$this->header('Content-Type', "text/html; charset={$encoding}"); else
{
header(Request::protocol().' '.$this->status.' '.$this->message());
} }
header(Request::protocol().' '.$this->status.' '.$this->statuses[$this->status]); // If the content type was not set by the developer, we will set the
// header to a default value that indicates to the browser that the
// response is HTML and that it uses the default encoding.
if ( ! isset($this->headers['Content-Type']))
{
$encoding = Config::get('application.encoding');
$this->header('Content-Type', 'text/html; charset='.$encoding);
}
// Once the framework controlled headers have been sentm, we can
// simply iterate over the developer's headers and send each one
// to the browser. Headers with the same name will be overriden.
foreach ($this->headers as $name => $value) foreach ($this->headers as $name => $value)
{ {
header($name.': '.$value, true); header("{$name}: {$value}", true);
} }
} }
/** /**
* Add a header to the response. * Get the status code message for the response.
* *
* <code> * @return string
* // Add a header to a response instance */
* return Response::make('foo')->header('content-type', 'application/json'); public function message()
* </code> {
return static::$statuses[$this->status];
}
/**
* Add a header to the array of response headers.
* *
* @param string $name * @param string $name
* @param string $value * @param string $value
@ -301,24 +308,4 @@ public function status($status)
return $this; return $this;
} }
/**
* Magic Method for handling the dynamic creation of Responses containing named views.
*
* <code>
* // Create a response instance with the "layout" named view
* return Response::of_layout();
*
* // Create a response instance with a named view and data
* return Response::of_layout(array('name' => 'Taylor'));
* </code>
*/
public static function __callStatic($method, $parameters)
{
if (strpos($method, 'of_') === 0)
{
return static::of(substr($method, 3), Arr::get($parameters, 0, array()));
}
throw new \BadMethodCallException("Method [$method] is not defined on the Response class.");
}
} }

View File

@ -1,7 +1,9 @@
<?php namespace Laravel\Routing; <?php namespace Laravel\Routing;
use Laravel\IoC; use Laravel\IoC;
use Laravel\Str;
use Laravel\View; use Laravel\View;
use Laravel\Bundle;
use Laravel\Request; use Laravel\Request;
use Laravel\Redirect; use Laravel\Redirect;
use Laravel\Response; use Laravel\Response;
@ -30,13 +32,15 @@ abstract class Controller {
protected $filters = array(); protected $filters = array();
/** /**
* Handle the delegation of a route to a controller method. * Call an action method on a controller.
* *
* The controller destination should follow a {controller}@{method} convention. * <code>
* Nested controllers may be delegated to using dot syntax. * // Call the "show" method on the "user" controller
* $response = Controller::call('user@show');
* *
* For example, a destination of "user.profile@show" would call the User_Profile * // Call the "profile" method on the "user/admin" controller and pass parameters
* controller's show method with the given parameters. * $response = Controller::call('user.admin@profile', array($username));
* </code>
* *
* @param string $destination * @param string $destination
* @param array $parameters * @param array $parameters
@ -44,43 +48,47 @@ abstract class Controller {
*/ */
public static function call($destination, $parameters = array()) public static function call($destination, $parameters = array())
{ {
if (strpos($destination, '@') === false) list($bundle, $destination) = Bundle::parse($destination);
{
throw new \InvalidArgumentException("Route delegate [{$destination}] has an invalid format."); // 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.
Bundle::start($bundle);
list($controller, $method) = explode('@', $destination); list($controller, $method) = explode('@', $destination);
$controller = static::resolve($controller); $controller = static::resolve($bundle, $controller);
if (is_null($controller)) // If the controller could not be resolved, we're out of options and will
{ // return the 404 error response. Of course, if we found the controller,
return Response::error('404'); // we can go ahead and execute the requested method on the instance.
} if (is_null($controller)) return Response::error('404');
return $controller->execute($method, $parameters); return $controller->execute($method, $parameters);
} }
/** /**
* Resolve a controller name to a controller instance. * Resolve a bundle and controller name to a controller instance.
* *
* @param string $container * @param string $bundle
* @param string $controller * @param string $controller
* @return Controller * @return Controller
*/ */
public static function resolve($controller) public static function resolve($bundle, $controller)
{ {
if ( ! static::load($controller)) return; if ( ! static::load($bundle, $controller)) return;
// If the controller is registered in the IoC container, we will resolve // If the controller is registered in the IoC container, we will resolve
// it out of the container. Using constructor injection on controllers // it out of the container. Using constructor injection on controllers
// via the container allows more flexible and testable applications. // via the container allows more flexible and testable applications.
if (IoC::registered('controllers.'.$controller)) $resolver = 'controller: '.Bundle::identifier($bundle, $controller);
if (IoC::registered($resolver))
{ {
return IoC::resolve('controllers.'.$controller); return IoC::resolve($resolver);
} }
$controller = str_replace(' ', '_', ucwords(str_replace('.', ' ', $controller))).'_Controller'; $controller = static::format($bundle, $controller);
$controller = new $controller; $controller = new $controller;
@ -89,7 +97,7 @@ public static function resolve($controller)
// layout property, replacing the string layout name. // layout property, replacing the string layout name.
if ( ! is_null($controller->layout)) if ( ! is_null($controller->layout))
{ {
$controller->layout = View::make($controller->layout); $controller->layout = $controller->layout();
} }
return $controller; return $controller;
@ -98,14 +106,15 @@ public static function resolve($controller)
/** /**
* Load the file for a given controller. * Load the file for a given controller.
* *
* @param string $bundle
* @param string $controller * @param string $controller
* @return bool * @return bool
*/ */
protected static function load($controller) protected static function load($bundle, $controller)
{ {
$controller = strtolower(str_replace('.', '/', $controller)); $controller = strtolower(str_replace('.', '/', $controller));
if (file_exists($path = CONTROLLER_PATH.$controller.EXT)) if (file_exists($path = Bundle::path($bundle).'controllers/'.$controller.EXT))
{ {
require_once $path; require_once $path;
@ -115,6 +124,24 @@ protected static function load($controller)
return false; return false;
} }
/**
* Format a bundle and controller identifier into the controller's class name.
*
* @param string $bundle
* @param string $controller
* @return string
*/
protected static function format($bundle, $controller)
{
// If the controller's bundle is not the application bundle, we will
// prepend the bundle to the identifier so the bundle is prefixed to
// the class name when it is formatted. Bundle controllers are
// always prefixed with the bundle's name by convention.
if ($bundle !== DEFAULT_BUNDLE) $controller = $bundle.'.'.$controller;
return Str::classify($controller).'_Controller';
}
/** /**
* Execute a controller method with the given parameters. * Execute a controller method with the given parameters.
* *
@ -124,47 +151,18 @@ protected static function load($controller)
*/ */
public function execute($method, $parameters = array()) public function execute($method, $parameters = array())
{ {
// Again, as was the case with route closures, if the controller // Again, as was the case with route closures, if the controller "before"
// "before" filters return a response, it will be considered the // filters return a response, it will be considered the response to the
// response to the request and the controller method will not be // request and the controller method will not be used to handle the
// used to handle the request to the application. // request to the application.
$response = Filter::run($this->filters('before', $method), array(), true); $response = Filter::run($this->filters('before', $method), array(), true);
if (is_null($response)) if (is_null($response))
{ {
// The developer may mark the controller as being "RESTful" which $response = $this->response($method, $parameters);
// indicates that the controller actions are prefixed with the
// HTTP verb they respond to rather than the word "action".
if ($this->restful)
{
$action = strtolower(Request::method()).'_'.$method;
}
else
{
$action = "action_{$method}";
}
$response = call_user_func_array(array($this, $action), $parameters);
// If the controller has specified a layout view. The response
// returned by the controller method will be bound to that view
// and the layout will be considered the response.
if (is_null($response) and ! is_null($this->layout))
{
$response = $this->layout;
}
} }
if ( ! $response instanceof Response) $response = Response::prepare($response);
{
$response = new Response($response);
}
// Stringify the response. We need to force the response to be
// stringed before closing the session, since the developer may
// be using the session within their views, so we cannot age
// the session data until the view is rendered.
$response->content = $response->render();
Filter::run($this->filters('after', $method), array($response)); Filter::run($this->filters('after', $method), array($response));
@ -172,9 +170,45 @@ public function execute($method, $parameters = array())
} }
/** /**
* Register filters on the controller's methods. * Execute a controller action and return the response.
* *
* Generally, this method will be used in the controller's constructor. * Unlike the "execute" method, no filters will be run and the response
* from the controller action will not be changed in any way before it
* is returned to the consumer.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function response($method, $parameters = array())
{
// The developer may mark the controller as being "RESTful" which
// indicates that the controller actions are prefixed with the
// HTTP verb they respond to rather than the word "action".
if ($this->restful)
{
$action = strtolower(Request::method()).'_'.$method;
}
else
{
$action = "action_{$method}";
}
$response = call_user_func_array(array($this, $action), $parameters);
// If the controller has specified a layout view. The response
// returned by the controller method will be bound to that
// view and the layout will be considered the response.
if (is_null($response) and ! is_null($this->layout))
{
$response = $this->layout;
}
return $response;
}
/**
* Register filters on the controller's methods.
* *
* <code> * <code>
* // Set a "foo" after filter on the controller * // Set a "foo" after filter on the controller
@ -184,46 +218,54 @@ public function execute($method, $parameters = array())
* $this->filter('after', 'foo|bar')->only(array('user', 'profile')); * $this->filter('after', 'foo|bar')->only(array('user', 'profile'));
* </code> * </code>
* *
* @param string $event
* @param string|array $filters * @param string|array $filters
* @param mixed $parameters
* @return Filter_Collection * @return Filter_Collection
*/ */
protected function filter($name, $filters) protected function filter($event, $filters, $parameters = null)
{ {
$this->filters[$name][] = new Filter_Collection($name, $filters); $this->filters[$event][] = new Filter_Collection($filters, $parameters);
return $this->filters[$name][count($this->filters[$name]) - 1]; return $this->filters[$event][count($this->filters[$event]) - 1];
} }
/** /**
* Get an array of filter names defined for the destination. * Get an array of filter names defined for the destination.
* *
* @param string $name * @param string $event
* @param string $method * @param string $method
* @return array * @return array
*/ */
protected function filters($name, $method) protected function filters($event, $method)
{ {
if ( ! isset($this->filters[$name])) return array(); if ( ! isset($this->filters[$event])) return array();
$filters = array(); $filters = array();
foreach ($this->filters[$name] as $filter) foreach ($this->filters[$event] as $collection)
{ {
if ($filter->applies($method)) if ($collection->applies($method))
{ {
$filters = array_merge($filters, $filter->filters); $filters[] = $collection;
} }
} }
return array_unique($filters); return $filters;
}
/**
* Create the layout that is assigned to the controller.
*
* @return View
*/
public function layout()
{
return View::make($this->layout);
} }
/** /**
* Magic Method to handle calls to undefined functions on the controller. * Magic Method to handle calls to undefined functions on the controller.
*
* By default, the 404 response will be returned for an calls to undefined
* methods on the controller. However, this method may also be overridden
* and used as a pseudo-router by the controller.
*/ */
public function __call($method, $parameters) public function __call($method, $parameters)
{ {
@ -243,12 +285,9 @@ public function __call($method, $parameters)
*/ */
public function __get($key) public function __get($key)
{ {
if (IoC::registered($key)) if (IoC::registered($key)) return IoC::resolve($key);
{
return IoC::resolve($key);
}
throw new \OutOfBoundsException("Attempting to access undefined property [$key] on controller."); throw new \Exception("Accessing undefined property [$key] on controller.");
} }
} }

View File

@ -1,5 +1,7 @@
<?php namespace Laravel\Routing; <?php namespace Laravel\Routing;
use Closure;
use Laravel\Bundle;
use Laravel\Request; use Laravel\Request;
class Filter { class Filter {
@ -9,60 +11,53 @@ class Filter {
* *
* @var array * @var array
*/ */
protected static $filters = array(); public static $filters = array();
/** /**
* Register an array of route filters. * All of the registered filter aliases.
* *
* @param array $filters * @var array
*/
public static $aliases = array();
/**
* Register a filter for the application.
*
* <code>
* // Register a closure as a filter
* Filter::register('before', function() {});
*
* // Register a class callback as a filter
* Filter::register('before', array('Class', 'method'));
* </code>
*
* @param string $name
* @param Closure $callback
* @return void * @return void
*/ */
public static function register($filters) public static function register($name, Closure $callback)
{ {
static::$filters = array_merge(static::$filters, $filters); if (isset(static::$aliases[$name])) $name = static::$aliases[$name];
static::$filters[$name] = $callback;
} }
/** /**
* Call a filter or set of filters. * Alias a filter so it can be used by another name.
* *
* @param array|string $filters * This is convenient for shortening filters that are registered by bundles.
* @param array $pass *
* @param bool $override * @param string $filter
* @return mixed * @param string $alias
* @return void
*/ */
public static function run($filters, $pass = array(), $override = false) public static function alias($filter, $alias)
{ {
foreach (static::parse($filters) as $filter) static::$aliases[$alias] = $filter;
{
$parameters = array();
// Parameters may be passed into routes by specifying the list of
// parameters after a colon. If parameters are present, we will
// merge them into the parameter array that was passed to the
// method and slice the parameters off of the filter string.
if (($colon = strpos($filter, ':')) !== false)
{
$parameters = explode(',', substr($filter, $colon + 1));
$filter = substr($filter, 0, $colon);
}
if ( ! isset(static::$filters[$filter])) continue;
$parameters = array_merge($pass, $parameters);
$response = call_user_func_array(static::$filters[$filter], $parameters);
// "Before" filters may override the request cycle. For example,
// an authentication filter may redirect a user to a login view
// if they are not logged in. Because of this, we will return
// the first filter response if overriding is enabled.
if ( ! is_null($response) and $override) return $response;
}
} }
/** /**
* Parse a string of filters into an array. * Parse a filter definition into an array of filters.
* *
* @param string|array $filters * @param string|array $filters
* @return array * @return array
@ -72,16 +67,67 @@ public static function parse($filters)
return (is_string($filters)) ? explode('|', $filters) : (array) $filters; return (is_string($filters)) ? explode('|', $filters) : (array) $filters;
} }
/**
* Call a filter or set of filters.
*
* @param array $collections
* @param array $pass
* @param bool $override
* @return mixed
*/
public static function run($collections, $pass = array(), $override = false)
{
foreach ($collections as $collection)
{
foreach ($collection->filters as $filter)
{
list($filter, $parameters) = $collection->get($filter);
// We will also go ahead and start the bundle for the developer. This allows
// the developer to specify bundle filters on routes without starting the
// bundle manually, and performance is improved since the bundle is only
// started when needed.
Bundle::start(Bundle::name($filter));
if ( ! isset(static::$filters[$filter])) continue;
$callback = static::$filters[$filter];
// Parameters may be passed into filters by specifying the list of parameters
// as an array, or by registering a Closure which will return the array of
// parameters. If parameters are present, we will merge them with the
// parameters that were given to the method.
$response = call_user_func_array($callback, array_merge($pass, $parameters));
// "Before" filters may override the request cycle. For example, an auth
// filter may redirect a user to a login view if they are not logged in.
// Because of this, we will return the first filter response if
// overriding is enabled for the filter collections
if ( ! is_null($response) and $override)
{
return $response;
}
}
}
}
} }
class Filter_Collection { class Filter_Collection {
/** /**
* The event being filtered. * The filters contained by the collection.
* *
* @var string * @var string|array
*/ */
public $name; public $filters = array();
/**
* The parameters specified for the filter.
*
* @var mixed
*/
public $parameters;
/** /**
* The included controller methods. * The included controller methods.
@ -97,13 +143,6 @@ class Filter_Collection {
*/ */
public $except = array(); public $except = array();
/**
* The filters contained by the collection.
*
* @var string|array
*/
public $filters = array();
/** /**
* The HTTP methods for which the filter applies. * The HTTP methods for which the filter applies.
* *
@ -114,21 +153,73 @@ class Filter_Collection {
/** /**
* Create a new filter collection instance. * Create a new filter collection instance.
* *
* @param string $name
* @param string|array $filters * @param string|array $filters
* @param mixed $parameters
*/ */
public function __construct($name, $filters) public function __construct($filters, $parameters = null)
{ {
$this->name = $name; $this->parameters = $parameters;
$this->filters = Filter::parse($filters); $this->filters = Filter::parse($filters);
} }
/** /**
* Determine if this collection's filters apply to a given method. * Parse the filter string, returning the filter name and parameters.
* *
* Methods may be included / excluded using the "only" and "except" methods on the * @param string $filter
* filter collection. Also, the "on" method may be used to set certain filters to * @return array
* only run when the request uses a given HTTP verb. */
public function get($filter)
{
// If the parameters were specified by passing an array into the collection,
// then we will simply return those parameters. Combining passed parameters
// with parameters specified directly in the filter attachment is not
// currently supported by the framework.
if ( ! is_null($this->parameters))
{
return array($filter, $this->parameters());
}
// If no parameters were specified when the collection was created, we will
// check the filter string itself to see if the parameters were injected
// into the string as raw values, such as "role:admin".
if (($colon = strpos(Bundle::element($filter), ':')) !== false)
{
$parameters = explode(',', substr(Bundle::element($filter), $colon + 1));
// If the filter belongs to a bundle, we need to re-calculate the position
// of the parameter colon, since we originally calculated it without the
// bundle identifier because the identifier uses colons as well.
if (($bundle = Bundle::name($filter)) !== DEFAULT_BUNDLE)
{
$colon = strlen($bundle.'::') + $colon;
}
return array(substr($filter, 0, $colon), $parameters);
}
// If no parameters were specified when the collection was created or
// in the filter string, we will just return the filter name as is
// and give back an empty array of parameters.
return array($filter, array());
}
/**
* Evaluate the collection's parameters and return a parameters array.
*
* @return array
*/
protected function parameters()
{
if ($this->parameters instanceof Closure)
{
$this->parameters = call_user_func($this->parameters);
}
return $this->parameters;
}
/**
* Determine if this collection's filters apply to a given method.
* *
* @param string $method * @param string $method
* @return bool * @return bool
@ -145,7 +236,9 @@ public function applies($method)
return false; return false;
} }
if (count($this->methods) > 0 and ! in_array(strtolower(Request::method()), $this->methods)) $request = strtolower(Request::method());
if (count($this->methods) > 0 and ! in_array($request, $this->methods))
{ {
return false; return false;
} }
@ -156,15 +249,12 @@ public function applies($method)
/** /**
* Set the excluded controller methods. * Set the excluded controller methods.
* *
* When methods are excluded, the collection's filters will be run for each
* controller method except those explicitly specified via this method.
*
* <code> * <code>
* // Specify a filter for all methods except "index" * // Specify a filter for all methods except "index"
* $this->filter('before', 'auth')->except('index'); * $this->filter('before', 'auth')->except('index');
* *
* // Specify a filter for all methods except "index" and "home" * // Specify a filter for all methods except "index" and "home"
* $this->filter('before', 'auth')->except('index', 'home'); * $this->filter('before', 'auth')->except(array('index', 'home'));
* </code> * </code>
* *
* @param array $methods * @param array $methods
@ -172,23 +262,19 @@ public function applies($method)
*/ */
public function except($methods) public function except($methods)
{ {
$this->except = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods; $this->except = (array) $methods;
return $this; return $this;
} }
/** /**
* Set the included controller methods. * Set the included controller methods.
* *
* This method is the inverse of the "except" methods. The methods specified
* via this method are the only controller methods on which the collection's
* filters will be run.
*
* <code> * <code>
* // Specify a filter for only the "index" method * // Specify a filter for only the "index" method
* $this->filter('before', 'auth')->only('index'); * $this->filter('before', 'auth')->only('index');
* *
* // Specify a filter for only the "index" and "home" methods * // Specify a filter for only the "index" and "home" methods
* $this->filter('before', 'auth')->only('index', 'home'); * $this->filter('before', 'auth')->only(array('index', 'home'));
* </code> * </code>
* *
* @param array $methods * @param array $methods
@ -196,23 +282,19 @@ public function except($methods)
*/ */
public function only($methods) public function only($methods)
{ {
$this->only = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods; $this->only = (array) $methods;
return $this; return $this;
} }
/** /**
* Set the HTTP methods for which the filter applies. * Set the HTTP methods for which the filter applies.
* *
* Since some filters, such as the CSRF filter, only make sense in a POST
* request context, this method allows you to limit which HTTP methods
* the filter will apply to.
*
* <code> * <code>
* // Specify that a filter only applies on POST requests * // Specify that a filter only applies on POST requests
* $this->filter('before', 'csrf')->on('post'); * $this->filter('before', 'csrf')->on('post');
* *
* // Specify that a filter applies for multiple HTTP request methods * // Specify that a filter applies for multiple HTTP request methods
* $this->filter('before', 'csrf')->on('post', 'put'); * $this->filter('before', 'csrf')->on(array('post', 'put'));
* </code> * </code>
* *
* @param array $methods * @param array $methods
@ -220,13 +302,7 @@ public function only($methods)
*/ */
public function on($methods) public function on($methods)
{ {
$methods = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods; $method = array_map('strtolower', (array) $methods);
foreach ($methods as $method)
{
$this->methods[] = strtolower($method);
}
return $this; return $this;
} }

View File

@ -1,120 +0,0 @@
<?php namespace Laravel\Routing;
use Laravel\Arr;
use RecursiveIteratorIterator as Iterator;
use RecursiveDirectoryIterator as DirectoryIterator;
class Loader {
/**
* The location of the base routes file.
*
* @var string
*/
protected $base;
/**
* The directory containing nested route files.
*
* @var string
*/
protected $nest;
/**
* A cache for all of the routes defined for the entire application.
*
* @var array
*/
protected $everything;
/**
* Create a new route loader instance.
*
* @param string $base
* @param string $nest
* @return void
*/
public function __construct($base, $nest)
{
$this->base = $base;
$this->nest = $nest;
}
/**
* Load the applicable routes for a given URI.
*
* @param string $uri
* @return array
*/
public function load($uri)
{
$segments = Arr::without(explode('/', $uri), '');
return array_merge($this->nested($segments), require $this->base.'routes'.EXT);
}
/**
* Get the appropriate routes from the routes directory for a given URI.
*
* This method works backwards through the URI segments until we find the
* deepest possible matching route directory. Once the deepest directory
* is found, all of the applicable routes will be returend.
*
* @param array $segments
* @return array
*/
protected function nested($segments)
{
foreach (array_reverse($segments, true) as $key => $value)
{
$path = $this->nest.implode('/', array_slice($segments, 0, $key + 1)).EXT;
if (file_exists($path)) return require $path;
}
return array();
}
/**
* Get every route defined for the application.
*
* The entire routes directory will be searched recursively to gather
* every route for the application. Of course, the routes in the root
* routes file will be returned as well.
*
* @return array
*/
public function everything()
{
if ( ! is_null($this->everything)) return $this->everything;
$routes = array();
// First, we'll grab the base routes from the application directory.
// Once we have these, we'll merge all of the nested routes in the
// routes directory into this array of routes.
if (file_exists($path = $this->base.'routes'.EXT))
{
$routes = array_merge($routes, require $path);
}
if ( ! is_dir($this->nest)) return $routes;
$iterator = new Iterator(new DirectoryIterator($this->nest), Iterator::SELF_FIRST);
foreach ($iterator as $file)
{
// Since some Laravel developers may place HTML files in the route
// directories, we will check for the PHP extension before merging
// the file. Typically, the HTML files are present in installations
// that are not using mod_rewrite and the public directory.
if (filetype($file) === 'file' and strpos($file, EXT) !== false)
{
$routes = array_merge(require $file, $routes);
}
}
return $this->everything = $routes;
}
}

View File

@ -1,7 +1,7 @@
<?php namespace Laravel\Routing; <?php namespace Laravel\Routing;
use Closure; use Closure;
use Laravel\Arr; use Laravel\Bundle;
use Laravel\Response; use Laravel\Response;
class Route { class Route {
@ -14,18 +14,25 @@ class Route {
public $key; public $key;
/** /**
* The URIs the route responds to. * The URI the route responds to.
* *
* @var array * @var string
*/ */
public $uris; public $uris;
/** /**
* The route callback or array. * The bundle in which the route was registered.
*
* @var string
*/
public $bundle;
/**
* The action that is assigned to the route.
* *
* @var mixed * @var mixed
*/ */
public $callback; public $action;
/** /**
* The parameters that will passed to the route callback. * The parameters that will passed to the route callback.
@ -38,62 +45,43 @@ class Route {
* Create a new Route instance. * Create a new Route instance.
* *
* @param string $key * @param string $key
* @param mixed $callback * @param array $action
* @param array $parameters * @param array $parameters
* @return void * @return void
*/ */
public function __construct($key, $callback, $parameters = array()) public function __construct($key, $action, $parameters = array())
{ {
$this->key = $key; $this->key = $key;
$this->callback = $callback; $this->action = $action;
$this->parameters = $parameters; $this->parameters = $parameters;
// Extract each URI from the route key. Since the route key has the // Extract each URI from the route key. Since the route key has the request
// request method, we will extract that from the string. If the URI // method, we will extract that from the string. If the URI points to the
// points to the root of the application, a single forward slash // root of the application, a single forward slash will be returned.
// will be returned since that is used for the root route. $uris = array_get($action, 'handles', array());
if (strpos($key, ', ') === false)
{
$this->uris = array($this->extract($this->key));
}
else
{
$this->uris = array_map(array($this, 'extract'), explode(', ', $key));
}
if ( ! $this->callable($callback)) $this->uris = array_map(array($this, 'extract'), $uris);
{
throw new \InvalidArgumentException('Invalid route defined for URI ['.$this->key.']');
}
}
/** // Determine the bundle in which the route was registered. We will know
* Determine if the given route callback is callable. // the bundle by the first segment of the route's URI. We need to know
* // the bundle so we know if we need to run a bundle's global filters
* Route callbacks must be either a Closure, array, or string. // when executing the route.
* $this->bundle = Bundle::resolve(head(explode('/', $this->uris[0])));
* @param mixed $callback
* @return bool
*/
protected function callable($callback)
{
return $callback instanceof Closure or is_array($callback) or is_string($callback);
} }
/** /**
* Retrieve the URI from a given route destination. * Retrieve the URI from a given route destination.
* *
* If the request is to the root of the application, a single slash * If the request is to the application root, a slash is returned.
* will be returned, otherwise the leading slash will be removed.
* *
* @param string $segment * @param string $segment
* @return string * @return string
*/ */
protected function extract($segment) protected static function extract($segment)
{ {
$segment = substr($segment, strpos($segment, ' ') + 1); $uri = substr($segment, strpos($segment, ' ') + 1);
return ($segment !== '/') ? trim($segment, '/') : $segment; return ($uri !== '/') ? trim($uri, '/') : $uri;
} }
/** /**
@ -103,116 +91,119 @@ protected function extract($segment)
*/ */
public function call() public function call()
{ {
// Since "before" filters can halt the request cycle, we will return
// any response from the before filters. Allowing filters to halt the
// request cycle makes tasks like authorization convenient.
//
// The route is responsible for running the global filters, and any // The route is responsible for running the global filters, and any
// filters defined on the route itself. Since all incoming requests // filters defined on the route itself. Since all incoming requests
// come through a route (either defined or ad-hoc), it makes sense // come through a route (either defined or ad-hoc), it makes sense
// to let the route handle the global filters. If the route uses // to let the route handle the global filters.
// a controller, the controller will only call its own filters. $response = Filter::run($this->filters('before'), array(), true);
$before = array_merge(array('before'), $this->filters('before'));
$response = Filter::run($before, array(), true); if (is_null($response))
if (is_null($response) and ! is_null($response = $this->response()))
{ {
if ($response instanceof Delegate) $response = $this->response();
{
$response = Controller::call($response->destination, $this->parameters);
}
} }
if ( ! $response instanceof Response) $response = Response::prepare($response);
{
$response = new Response($response);
}
// Stringify the response. We need to force the response to be Filter::run($this->filters('after'), array($response));
// stringed before closing the session, since the developer may
// be using the session within their views, so we cannot age
// the session data until the view is rendered.
$response->content = $response->render();
$filters = array_merge($this->filters('after'), array('after'));
Filter::run($filters, array($response));
return $response; return $response;
} }
/** /**
* Call the closure defined for the route, or get the route delegator. * Execute the route action and return the response.
* *
* Note that this method differs from the "call" method in that it does * Unlike the "call" method, none of the attached filters will be run.
* not resolve the controller or prepare the response. Delegating to
* controller's is handled by the "call" method.
* *
* @return mixed * @return mixed
*/ */
protected function response() public function response()
{ {
// If the route callback is an instance of a Closure, we can call the // If the action is a string, it is simply pointing the route to a
// route function directly. There are no before or after filters to // controller action, and we can just call the action and return
// parse out of the route. // its response. This is the most basic form of route, and is
if ($this->callback instanceof Closure) // the simplest to handle.
if ( ! is_null($delegate = $this->delegate()))
{ {
return call_user_func_array($this->callback, $this->parameters); return Controller::call($delegate, $this->parameters);
} }
// If the route is an array, we will return the first value with a
// key of "uses", or the first instance of a Closure. If the value
// is a string, the route is delegating the responsibility for
// for handling the request to a controller.
elseif (is_array($this->callback))
{
$callback = Arr::first($this->callback, function($key, $value)
{
return $key == 'uses' or $value instanceof Closure;
});
if ($callback instanceof Closure) // If the route does not have a delegate, it should either be a
{ // Closure instance or have a Closure in its action array, so
return call_user_func_array($callback, $this->parameters); // we will attempt to get the Closure and call it.
} elseif ( ! is_null($handler = $this->handler()))
else
{
return new Delegate($callback);
}
}
elseif (is_string($this->callback))
{ {
return new Delegate($this->callback); return call_user_func_array($handler, $this->parameters);
} }
} }
/** /**
* Get an array of filter names defined for the route. * Get the filters that are attached to the route for a given event.
* *
* @param string $name * If the route belongs to a bundle, the bundle's global filters are returned too.
*
* @param string $filter
* @return array * @return array
*/ */
public function filters($name) protected function filters($event)
{ {
if (is_array($this->callback) and isset($this->callback[$name])) // Add the global filters to the array. We will also attempt to add
{ // the bundle's global filter as well. However, we'll need to keep
$filters = $this->callback[$name]; // the array unique since the default bundle's global filter will
// be the same as the application's global filter.
$filters = array_unique(array($event, Bundle::prefix($this->bundle).$event));
return (is_string($filters)) ? explode('|', $filters) : (array) $filters; // 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.
if (isset($this->action[$event]))
{
$filters = array_merge($filters, Filter::parse($this->action[$event]));
} }
return array(); return array(new Filter_Collection($filters));
}
/**
* Get the controller action delegate assigned to the route.
*
* If no delegate is assigned, null will be returned by the method.
*
* @return string
*/
protected function delegate()
{
return array_get($this->action, 'uses');
}
/**
* 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()
{
return array_first($this->action, function($key, $value)
{
return $value instanceof Closure;
});
} }
/** /**
* Determine if the route has a given name. * Determine if the route has a given name.
* *
* <code>
* // Determine if the route is the "login" route
* $login = Request::route()->is('login');
* </code>
*
* @param string $name * @param string $name
* @return bool * @return bool
*/ */
public function is($name) public function is($name)
{ {
return is_array($this->callback) and Arr::get($this->callback, 'name') === $name; return is_array($this->action) and array_get($this->action, 'name') === $name;
} }
/** /**
@ -223,20 +214,12 @@ public function is($name)
*/ */
public function handles($uri) public function handles($uri)
{ {
return in_array($uri, $this->uris); $pattern = '#'.str_replace('*', '(.*)', $uri).'#';
}
/** return ! is_null(array_first($this->uris, function($key, $uri) use ($pattern)
* Magic Method to handle dynamic method calls to determine the name of the route.
*/
public function __call($method, $parameters)
{
if (strpos($method, 'is_') === 0)
{ {
return $this->is(substr($method, 3)); return preg_match($pattern, $uri);
} }));
throw new \BadMethodCallException("Call to undefined method [$method] on Route class.");
} }
} }

View File

@ -1,56 +1,27 @@
<?php namespace Laravel\Routing; use Laravel\Request; <?php namespace Laravel\Routing; use Closure, Laravel\Bundle;
class Delegate {
/**
* The destination of the route delegate.
*
* @var string
*/
public $destination;
/**
* Create a new route delegate instance.
*
* @param string $destination
* @return void
*/
public function __construct($destination)
{
$this->destination = $destination;
}
}
class Router { class Router {
/** /**
* The route loader instance. * All of the routes that have been registered.
*
* @var Loader
*/
public $loader;
/**
* The named routes that have been found so far.
* *
* @var array * @var array
*/ */
protected $names = array(); public static $routes = array();
/** /**
* The path the application controllers. * All of the route names that have been matched with URIs.
* *
* @var string * @var array
*/ */
protected $controllers; public static $names = array();
/** /**
* The wildcard patterns supported by the router. * The wildcard patterns supported by the router.
* *
* @var array * @var array
*/ */
protected $patterns = array( public static $patterns = array(
'(:num)' => '([0-9]+)', '(:num)' => '([0-9]+)',
'(:any)' => '([a-zA-Z0-9\.\-_]+)', '(:any)' => '([a-zA-Z0-9\.\-_]+)',
); );
@ -60,44 +31,91 @@ class Router {
* *
* @var array * @var array
*/ */
protected $optional = array( public static $optional = array(
'/(:num?)' => '(?:/([0-9]+)', '/(:num?)' => '(?:/([0-9]+)',
'/(:any?)' => '(?:/([a-zA-Z0-9\.\-_]+)', '/(:any?)' => '(?:/([a-zA-Z0-9\.\-_]+)',
); );
/** /**
* Create a new router for a request method and URI. * Register a route with the router.
* *
* @param Loader $loader * <code>
* @param string $controllers * // 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!';});
* </code>
*
* @param string|array $route
* @param string $action
* @return void * @return void
*/ */
public function __construct(Loader $loader, $controllers) public static function register($route, $action)
{ {
$this->loader = $loader; foreach ((array) $route as $uri)
$this->controllers = $controllers; {
// 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.
//
// Note that all route actions are converted to arrays. This just
// gives us a convenient and consistent way of accessing it since
// we can always make an assumption that the action is an array,
// and it lets us store the URIs on the action for each route.
if (is_string($action))
{
static::$routes[$uri]['uses'] = $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
{
// PHP 5.3.2 has a bug that causes closures cast as arrays
// to yield an empty array. We will work around this by
// manually adding the Closure instance to a new array.
if ($action instanceof Closure) $action = array($action);
static::$routes[$uri] = (array) $action;
}
static::$routes[$uri]['handles'] = (array) $route;
}
} }
/** /**
* Find a route by name. * Find a route by name.
* *
* The returned array will be identical the array defined in the routes.php file.
* *
* @param string $name * @param string $name
* @return array * @return array
*/ */
public function find($name) public static function find($name)
{ {
if (array_key_exists($name, $this->names)) return $this->names[$name]; if (isset(static::$names[$name])) return static::$names[$name];
// To find a named route, we need to iterate through every route defined // If no route names have been found at all, we will assume no reverse
// for the application. We will cache the routes by name so we can load // routing has been done, and we will load the routes file for all of
// them very quickly if we need to find them a second time. // the bundle that are installed for the application.
foreach ($this->loader->everything() as $key => $value) if (count(static::$names) == 0)
{ {
if (is_array($value) and isset($value['name']) and $value['name'] === $name) foreach (Bundle::all() as $bundle)
{ {
return $this->names[$name] = array($key => $value); Bundle::routes($bundle);
}
}
// 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)
{
if (isset($value['name']) and $value['name'] == $name)
{
return static::$names[$name] = array($key => $value);
} }
} }
} }
@ -109,59 +127,55 @@ public function find($name)
* @param string $uri * @param string $uri
* @return Route * @return Route
*/ */
public function route($method, $uri) public static function route($method, $uri)
{ {
$routes = $this->loader->load($uri);
// All route URIs begin with the request method and have a leading // All route URIs begin with the request method and have a leading
// slash before the URI. We'll put the request method and URI into // slash before the URI. We'll put the request method and URI in
// that format so we can easily check for literal matches. // that format so we can easily check for literal matches.
$destination = $method.' /'.trim($uri, '/'); $destination = $method.' /'.trim($uri, '/');
if (isset($routes[$destination])) if (array_key_exists($destination, static::$routes))
{ {
return new Route($destination, $routes[$destination], array()); return new Route($destination, static::$routes[$destination], array());
} }
// If no literal route match was found, we will iterate through all // If we can't find a literal match, we'll iterate through all of
// of the routes and check each of them one at a time, translating // the registered routes attempting to find a matching route that
// any wildcards in the route into actual regular expressions. // uses wildcards or regular expressions.
foreach ($routes as $keys => $callback) if ( ! is_null($route = static::search($destination)))
{ {
// Only check the routes that couldn't be matched literally... return $route;
if (strpos($keys, '(') !== false or strpos($keys, ',') !== false)
{
if ( ! is_null($route = $this->match($destination, $keys, $callback)))
{
return $route;
}
}
} }
return $this->controller($method, $uri, $destination); // If there are no literal matches and no routes that match the
// request, we'll use convention to search for a controller to
// handle the request. If no controller can be found, the 404
// error response will be returned by the application.
$segments = array_diff(explode('/', trim($uri, '/')), array(''));
return static::controller(DEFAULT_BUNDLE, $method, $destination, $segments);
} }
/** /**
* Attempt to match a given route destination to a given route. * Attempt to match a destination to one of the registered routes.
*
* The destination's methods and URIs will be compared against the route's.
* If there is a match, the Route instance will be returned, otherwise null
* will be returned by the method.
* *
* @param string $destination * @param string $destination
* @param array $keys * @return Route
* @param mixed $callback
* @return mixed
*/ */
protected function match($destination, $keys, $callback) protected static function search($destination)
{ {
foreach (explode(', ', $keys) as $key) foreach (static::$routes as $route => $action)
{ {
if (preg_match('#^'.$this->wildcards($key).'$#', $destination, $parameters)) // Since routes that don't use wildcards or regular expressions
// should have been caught by the literal route check, we will
// only check routes that have a parentheses, indicating that
// there are wildcards or regular expressions.
if (strpos($route, '(') !== false)
{ {
array_shift($parameters); if (preg_match('#^'.static::wildcards($route).'$#', $destination, $parameters))
{
return new Route($keys, $callback, $parameters); return new Route($route, $action, array_slice($parameters, 1));
}
} }
} }
} }
@ -169,32 +183,47 @@ protected function match($destination, $keys, $callback)
/** /**
* Attempt to find a controller for the incoming request. * Attempt to find a controller for the incoming request.
* *
* @param string $bundle
* @param string $method * @param string $method
* @param string $uri
* @param string $destination * @param string $destination
* @param array $segments
* @return Route * @return Route
*/ */
protected function controller($method, $uri, $destination) protected static function controller($bundle, $method, $destination, $segments)
{ {
// If the request is to the root of the application, an ad-hoc route // If there are no more segments in the URI, we will just create a route
// will be generated to the home controller's "index" method, making // for the default controller of the bundle, which is "home". We'll also
// it the default controller method. // use the default method, which is "index".
if ($uri === '/') return new Route($method.' /', 'home@index'); if (count($segments) == 0)
$segments = explode('/', trim($uri, '/'));
// If there are more than 20 request segments, we will halt the request
// and throw an exception. This is primarily to protect against DDoS
// attacks which could overwhelm the server by feeding it too many
// segments in the URI, causing the loops in this class to bog.
if (count($segments) > 20)
{ {
throw new \Exception("Invalid request. There are more than 20 URI segments."); $uri = ($bundle == DEFAULT_BUNDLE) ? '/' : "/{$bundle}";
$action = array('uses' => Bundle::prefix($bundle).'home@index');
return new Route($method.' '.$uri, $action);
} }
if ( ! is_null($key = $this->controller_key($segments))) $directory = Bundle::path($bundle).'controllers/';
// We need to determine in which directory to look for the controllers.
// If the first segment of the request corresponds to a bundle that
// is installed for the application, we will use that bundle's
// controller path, otherwise we'll use the application's.
if (Bundle::routable($segments[0]))
{
$bundle = $segments[0];
// We shift the bundle name off of the URI segments because it will not
// be used to find a controller within the bundle. If we were to leave
// it in the segments, every bundle controller would need to be nested
// within a sub-directory matching the bundle name.
array_shift($segments);
return static::controller($bundle, $method, $destination, $segments);
}
if ( ! is_null($key = static::controller_key($segments, $directory)))
{ {
// Extract the various parts of the controller call from the URI.
// First, we'll extract the controller name, then, since we need // First, we'll extract the controller name, then, since we need
// to extract the method and parameters, we will remove the name // to extract the method and parameters, we will remove the name
// of the controller from the URI. Then we can shift the method // of the controller from the URI. Then we can shift the method
@ -206,31 +235,37 @@ protected function controller($method, $uri, $destination)
$method = (count($segments) > 0) ? array_shift($segments) : 'index'; $method = (count($segments) > 0) ? array_shift($segments) : 'index';
return new Route($destination, $controller.'@'.$method, $segments); // 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 application.
$prefix = Bundle::prefix($bundle);
$action = array('uses' => $prefix.$controller.'@'.$method);
return new Route($destination, $action, $segments);
} }
} }
/** /**
* Search for a controller that can handle the current request. * Get the URI index for the controller that should handle the request.
* *
* If a controller is found, the array key for the controller name in the URI * @param string $directory
* segments will be returned by the method, otherwise NULL will be returned. * @param array $segments
* The deepest possible controller will be considered the controller that
* should handle the request.
*
* @param array $segments
* @return int * @return int
*/ */
protected function controller_key($segments) protected static function controller_key($segments, $directory)
{ {
// To find the proper controller, we need to iterate backwards through // To find the proper controller, we need to iterate backwards through
// the URI segments and take the first file that matches. That file // the URI segments and take the first file that matches. That file
// should be the deepest controller matched by the URI. // should be the deepest possible controller matched by the URI.
foreach (array_reverse($segments, true) as $key => $value) $reverse = array_reverse($segments, true);
foreach ($reverse as $key => $value)
{ {
$controller = implode('/', array_slice($segments, 0, $key + 1)).EXT; $controller = implode('/', array_slice($segments, 0, $key + 1)).EXT;
if (file_exists($path = $this->controllers.$controller)) if (file_exists($directory.$controller))
{ {
return $key + 1; return $key + 1;
} }
@ -243,16 +278,16 @@ protected function controller_key($segments)
* @param string $key * @param string $key
* @return string * @return string
*/ */
protected function wildcards($key) protected static function wildcards($key)
{ {
// For optional parameters, first translate the wildcards to their // For optional parameters, first translate the wildcards to their
// regex equivalent, sans the ")?" ending. We will add the endings // regex equivalent, sans the ")?" ending. We'll add the endings
// back on after we know how many replacements we made. // back on after we know how many replacements we made.
$key = str_replace(array_keys($this->optional), array_values($this->optional), $key, $count); $key = str_replace(array_keys(static::$optional), array_values(static::$optional), $key, $count);
$key .= ($count > 0) ? str_repeat(')?', $count) : ''; $key .= ($count > 0) ? str_repeat(')?', $count) : '';
return str_replace(array_keys($this->patterns), array_values($this->patterns), $key); return str_replace(array_keys(static::$patterns), array_values(static::$patterns), $key);
} }
} }

Some files were not shown because too many files have changed in this diff Show More