diff --git a/application/bundle.php b/application/bundle.php
new file mode 100644
index 00000000..d1b9e362
--- /dev/null
+++ b/application/bundle.php
@@ -0,0 +1,42 @@
+ APP_PATH.'models/user.php',
+ //'Role' => APP_PATH.'models/role.php',
+));
\ No newline at end of file
diff --git a/application/composers.php b/application/composers.php
deleted file mode 100644
index c806c5fa..00000000
--- a/application/composers.php
+++ /dev/null
@@ -1,47 +0,0 @@
- 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.
- }),
-
-);
\ No newline at end of file
diff --git a/application/config/application.php b/application/config/application.php
index 1c112ad6..b20fcd49 100644
--- a/application/config/application.php
+++ b/application/config/application.php
@@ -7,7 +7,9 @@
| 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.
|
- | However, if you are using mod_rewrite or something similar to get
- | cleaner URLs, set this option to an empty string.
+ | However, if you are using mod_rewrite to get cleaner URLs, just set
+ | this option to an empty string and we'll take care of the rest.
|
*/
@@ -32,11 +34,10 @@
| 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
| 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
- | 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
- | 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',
+ /*
+ |--------------------------------------------------------------------------
+ | 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
@@ -112,26 +130,26 @@
*/
'aliases' => array(
- 'Arr' => 'Laravel\\Arr',
- 'Asset' => 'Laravel\\Asset',
'Auth' => 'Laravel\\Auth',
+ 'Asset' => 'Laravel\\Asset',
'Autoloader' => 'Laravel\\Autoloader',
- 'Benchmark' => 'Laravel\\Benchmark',
- 'Cache' => 'Laravel\\Cache\\Manager',
+ 'Bundle' => 'Laravel\\Bundle',
+ 'Cache' => 'Laravel\\Cache',
'Config' => 'Laravel\\Config',
'Controller' => 'Laravel\\Routing\\Controller',
'Cookie' => 'Laravel\\Cookie',
'Crypter' => 'Laravel\\Crypter',
- 'DB' => 'Laravel\\Database\\Manager',
- 'Eloquent' => 'Laravel\\Database\\Eloquent\\Model',
+ 'DB' => 'Laravel\\Database',
+ 'Event' => 'Laravel\\Event',
'File' => 'Laravel\\File',
+ 'Filter' => 'Laravel\\Routing\\Filter',
'Form' => 'Laravel\\Form',
'Hash' => 'Laravel\\Hash',
'HTML' => 'Laravel\\HTML',
- 'Inflector' => 'Laravel\\Inflector',
'Input' => 'Laravel\\Input',
'IoC' => 'Laravel\\IoC',
'Lang' => 'Laravel\\Lang',
+ 'Log' => 'Laravel\\Log',
'Memcached' => 'Laravel\\Memcached',
'Paginator' => 'Laravel\\Paginator',
'URL' => 'Laravel\\URL',
@@ -139,9 +157,13 @@
'Redis' => 'Laravel\\Redis',
'Request' => 'Laravel\\Request',
'Response' => 'Laravel\\Response',
+ 'Router' => 'Laravel\\Routing\\Router',
+ 'Schema' => 'Laravel\\Database\\Schema',
'Section' => 'Laravel\\Section',
- 'Session' => 'Laravel\\Facades\\Session',
+ 'Session' => 'Laravel\\Session',
'Str' => 'Laravel\\Str',
+ 'Task' => 'Laravel\\CLI\\Tasks\\Task',
+ 'URI' => 'Laravel\\URI',
'Validator' => 'Laravel\\Validator',
'View' => 'Laravel\\View',
),
diff --git a/application/config/auth.php b/application/config/auth.php
index c4671f01..36343d4f 100644
--- a/application/config/auth.php
+++ b/application/config/auth.php
@@ -2,45 +2,27 @@
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
|--------------------------------------------------------------------------
|
- | This closure is called by the Auth::user() method when attempting to
- | retrieve a user by their ID stored in the session.
+ | This closure is called by the Auth class' "user" method when trying to
+ | 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
- | no user with the given ID is registered to use your application, you do
- | not need to return anything.
- |
- | Of course, a simple, elegant authentication solution is already provided
- | for you using Eloquent and the default Laravel hashing engine.
+ | Of course, a simple and elegant authentication solution has already
+ | been provided for you using the query builder and hashing engine.
+ | We love making your life as easy as possible.
|
*/
'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
- | 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
- | represents the user being authenticated. If the credentials are not
- | valid, don't return anything.
- |
- | Note: If a user object is returned, it must have an "id" property.
+ | represents the user being authenticated. As long as it has a property
+ | for the "id", any object will work. If the credentials are not valid,
+ | you don't meed to return anything.
|
*/
- '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))
{
@@ -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
| 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.
|
*/
diff --git a/application/config/cache.php b/application/config/cache.php
index abd07eec..73fd4c9c 100644
--- a/application/config/cache.php
+++ b/application/config/cache.php
@@ -7,12 +7,15 @@
| 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
- | by storing commonly accessed data in memory or in a file.
+ | A variety of awesome drivers are available for you to use with Laravel.
+ | 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
|--------------------------------------------------------------------------
|
- | This key will be prepended to item keys stored using Memcached and APC to
- | prevent collisions with other applications on the server.
+ | This key will be prepended to item keys stored using Memcached and APC
+ | 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',
+ /*
+ |--------------------------------------------------------------------------
+ | 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
|--------------------------------------------------------------------------
|
- | 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
- | 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
+ | For more information, check out: http://memcached.org
|
*/
diff --git a/application/config/container.php b/application/config/container.php
deleted file mode 100644
index 2e9b5ef0..00000000
--- a/application/config/container.php
+++ /dev/null
@@ -1,54 +0,0 @@
- 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);
- | })
- |
- */
-
-);
\ No newline at end of file
diff --git a/application/config/database.php b/application/config/database.php
index ebcac4d9..f2ba145e 100644
--- a/application/config/database.php
+++ b/application/config/database.php
@@ -7,39 +7,29 @@
| Default Database Connection
|--------------------------------------------------------------------------
|
- | The name of your default database connection.
- |
- | This connection will be the default for all database operations unless a
- | different connection is specified when performing the operation.
+ | The name of your default database connection. This connection will used
+ | as the default for all database operations unless a different name is
+ | given when performing said operation. This connection name should be
+ | listed in the array of connections below.
|
*/
- 'default' => 'sqlite',
+ 'default' => 'mysql',
/*
|--------------------------------------------------------------------------
| 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
- | 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.
+ | Drivers: 'mysql', 'pgsql', 'sqlsrv', 'sqlite'.
|
*/
@@ -68,6 +58,14 @@
'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
| 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.
- |
- | For more information regarding Redis, check out: http://redis.io
+ | To get the scoop on Redis, check out: http://redis.io
|
*/
diff --git a/application/config/error.php b/application/config/error.php
index 881ce8dd..87db9665 100644
--- a/application/config/error.php
+++ b/application/config/error.php
@@ -7,7 +7,7 @@
| 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
| information about about them will be displayed.
|
@@ -22,10 +22,11 @@
|
| Detailed error messages contain information about the file in which an
| 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
- | for enhanced security and user experience. The error stack trace could
- | contain sensitive information that should not be publicly visible.
+ | If your application is in production, you'll want to turn off the error
+ | details for enhanced security and user experience since the exception
+ | stack trace could contain sensitive information.
|
*/
@@ -56,18 +57,13 @@
|
| 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
- | a single text file within the application storage directory.
- |
- | Of course, you are free to implement more complex solutions including
- | emailing the exceptions details to your team, etc.
+ | text files within the application storage directory.
|
*/
'logger' => function($exception)
{
- $message = (string) $exception;
-
- File::append(STORAGE_PATH.'log.txt', date('Y-m-d H:i:s').' - '.$message.PHP_EOL);
+ Log::exception($exception);
},
);
\ No newline at end of file
diff --git a/laravel/config/mimes.php b/application/config/mimes.php
similarity index 100%
rename from laravel/config/mimes.php
rename to application/config/mimes.php
diff --git a/application/config/session.php b/application/config/session.php
index c72a3dd4..3b6e6a69 100644
--- a/application/config/session.php
+++ b/application/config/session.php
@@ -7,12 +7,12 @@
| 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
- | multiple requests from the same user of your application.
- |
- | Supported Drivers: 'cookie', 'file', 'database', 'memcached', 'apc', 'redis'.
+ | Drivers: 'cookie', 'file', 'database', 'memcached', 'apc', 'redis'.
|
*/
@@ -23,9 +23,9 @@
| Session Database
|--------------------------------------------------------------------------
|
- | The database table on which the session should be stored.
- |
- | This option is only relevant when using the "database" session driver.
+ | The database table on which the session should be stored. It probably
+ | goes without saying that this option only matters if you are using
+ | the super slick database session driver.
|
*/
@@ -40,8 +40,9 @@
| This option specifies the probability of session garbage collection
| occuring for any given request.
|
- | For example, the default value states that garbage collection has about
- | a 2% (2 / 100) chance of occuring for any given request.
+ | For example, the default value states that garbage collection has a
+ | 2% chance of occuring for any given request to the application.
+ | Feel free to tune this to your application's size and speed.
|
*/
diff --git a/application/config/strings.php b/application/config/strings.php
new file mode 100644
index 00000000..3ee44f56
--- /dev/null
+++ b/application/config/strings.php
@@ -0,0 +1,119 @@
+ 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',
+
+ ),
+
+);
\ No newline at end of file
diff --git a/application/controllers/home.php b/application/controllers/home.php
index 982cc513..cc407246 100644
--- a/application/controllers/home.php
+++ b/application/controllers/home.php
@@ -7,10 +7,10 @@ class Home_Controller extends Controller {
| The Default Controller
|--------------------------------------------------------------------------
|
- | Instead of using RESTful routes and anonymous functions, you may wish to
- | use controllers to organize your application API. You'll love them.
+ | Instead of using RESTful routes and anonymous functions, you might wish
+ | 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
| controller and call the default method, which is "action_index".
|
diff --git a/application/filters.php b/application/filters.php
deleted file mode 100644
index 41ac6010..00000000
--- a/application/filters.php
+++ /dev/null
@@ -1,68 +0,0 @@
- 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');
- },
-
-);
\ No newline at end of file
diff --git a/application/language/en/pagination.php b/application/language/en/pagination.php
index 8c0b694f..f58ada06 100644
--- a/application/language/en/pagination.php
+++ b/application/language/en/pagination.php
@@ -8,8 +8,8 @@
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
- | the pagination links. They may be easily changed by the developer to
- | anything they wish.
+ | the pagination links. You're free to change them to anything you want.
+ | If you come up with something more exciting, let us know.
|
*/
diff --git a/application/language/en/validation.php b/application/language/en/validation.php
index a8e6d3c6..71553c5a 100644
--- a/application/language/en/validation.php
+++ b/application/language/en/validation.php
@@ -8,11 +8,12 @@
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
- | with something more reader friendly, such as "E-Mail Address" instead
- | of "email".
+ | with something more reader friendly such as "E-Mail Address" instead
+ | of "email". Your users will thank you.
|
- | The Validator class will automatically search this array of lines when
- | attempting to replace the :attribute place-holder in error messages.
+ | The Validator class will automatically search this array of lines it
+ | 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
| for different input types such as strings and files.
|
- | These language lines may be easily changed by the developer to provide
- | custom error messages in their application. Error messages for custom
- | validation rules may also be added to this file.
+ | These language lines may be easily changed to provide custom error
+ | messages in your application. Error messages for custom validation
+ | rules may also be added to this file.
|
*/
diff --git a/application/migrations/.gitignore b/application/migrations/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/application/routes.php b/application/routes.php
index 7bb7acd6..7bfd2439 100644
--- a/application/routes.php
+++ b/application/routes.php
@@ -1,42 +1,87 @@
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.";
- | }
- |
- */
+Router::register(array('GET /', 'GET /home'), function()
+{
+ return View::make('home.index');
+});
- '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!';
+| }));
+|
+*/
-);
\ No newline at end of file
+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');
+});
\ No newline at end of file
diff --git a/application/storage/cache/.gitignore b/application/storage/cache/.gitignore
deleted file mode 100644
index f59ec20a..00000000
--- a/application/storage/cache/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*
\ No newline at end of file
diff --git a/application/storage/sessions/.gitignore b/application/storage/sessions/.gitignore
deleted file mode 100644
index f59ec20a..00000000
--- a/application/storage/sessions/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*
\ No newline at end of file
diff --git a/application/storage/views/.gitignore b/application/storage/views/.gitignore
deleted file mode 100644
index f59ec20a..00000000
--- a/application/storage/views/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*
\ No newline at end of file
diff --git a/application/tasks/.gitignore b/application/tasks/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/artisan b/artisan
new file mode 100644
index 00000000..f31604ac
--- /dev/null
+++ b/artisan
@@ -0,0 +1,49 @@
+
+ * @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;
\ No newline at end of file
diff --git a/bundles/.gitignore b/bundles/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/changelog.md b/changelog.md
index f4abe742..9779ef6c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,30 +1,5 @@
# 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
- Fix: Fixed raw_where in query builder.
diff --git a/laravel/arr.php b/laravel/arr.php
deleted file mode 100644
index 966a6252..00000000
--- a/laravel/arr.php
+++ /dev/null
@@ -1,166 +0,0 @@
-
- * // 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');
- *
- *
- * @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.
- *
- *
- * // Set the $array['user']['name'] value on the array
- * Arr::set($array, 'user.name', 'Taylor');
- *
- *
- * @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.
- *
- *
- * // Remove the $array['user']['name'] item from the array
- * Arr::forget($array, 'user.name');
- *
- *
- * @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.
- *
- *
- * // 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');
- *
- *
- * @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.
- *
- *
- * // 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'));
- *
- *
- * @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;
- }
-
-}
\ No newline at end of file
diff --git a/laravel/asset.php b/laravel/asset.php
index 5b90a66a..f278143f 100644
--- a/laravel/asset.php
+++ b/laravel/asset.php
@@ -1,4 +1,4 @@
-
* // Call the "styles" method on the default container
@@ -60,6 +60,13 @@ class Asset_Container {
*/
public $name;
+ /**
+ * The bundle that the assets belong to.
+ *
+ * @var string
+ */
+ public $bundle = DEFAULT_BUNDLE;
+
/**
* All of the registered assets.
*
@@ -83,8 +90,8 @@ public function __construct($name)
* Add an asset to the container.
*
* 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
- * extension, you may use the style or script methods to register assets.
+ * asset being registered (CSS or JavaScript). When using a non-standard
+ * extension, the style/script methods may be used to register assets.
*
*
* // 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';
- 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;
}
+ /**
+ * 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.
*
@@ -219,6 +249,14 @@ protected function asset($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']);
}
@@ -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
// 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)
{
$sorted[$asset] = $value;
@@ -289,7 +327,8 @@ protected function evaluate_asset($asset, $value, $original, &$sorted, &$assets)
* Verify that an asset's dependency is valid.
*
* 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 $dependency
@@ -299,16 +338,20 @@ protected function evaluate_asset($asset, $value, $original, &$sorted, &$assets)
*/
protected function dependency_is_valid($asset, $dependency, $original, $assets)
{
- if ( ! isset($original[$dependency])) return false;
-
- if ($dependency === $asset)
+ if ( ! isset($original[$dependency]))
{
- 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']))
{
- throw new \LogicException("Assets [$asset] and [$dependency] have a circular dependency.");
+ throw new \Exception("Assets [$asset] and [$dependency] have a circular dependency.");
}
+
+ return true;
}
}
diff --git a/laravel/auth.php b/laravel/auth.php
index da5bd080..a43aa689 100644
--- a/laravel/auth.php
+++ b/laravel/auth.php
@@ -7,7 +7,7 @@ class Auth {
*
* @var object
*/
- protected static $user;
+ public static $user;
/**
* 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.
*
- * 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.
- *
*
- *
- * @param string $class
- * @return string
- */
- protected static function controller($class)
- {
- $controller = str_replace(array('_', '_Controller'), array('/', ''), $class);
-
- return CONTROLLER_PATH.strtolower($controller).EXT;
+ // Once we have formatted the class name, we will simply spin
+ // through the registered PSR-0 directories and attempt to
+ // locate and load the class into the script.
+ foreach (static::$psr as $directory)
+ {
+ if (file_exists($path = $directory.strtolower($file).EXT))
+ {
+ return require $path;
+ }
+ elseif (file_exists($path = $directory.$file.EXT))
+ {
+ return require $path;
+ }
+ }
}
/**
* 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.
- *
*
* // Get the current user of the application
* $user = Auth::user();
@@ -62,20 +56,26 @@ public static function check()
* $email = Auth::user()->email;
*
*
- * @return object
+ * @return object|null
*/
public static function 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
- // exists, we will attempt to recall the user based on the cookie value.
- // Since all cookies contain a fingerprint hash verifying that the have
- // not been modified on the client, we should be able to trust it.
+ static::$user = call_user_func($config['user'], $id);
+
+ // If the user wasn't found in the database but a "remember me" cookie
+ // 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);
if (is_null(static::$user) and ! is_null($recaller))
@@ -94,13 +94,17 @@ public static function user()
*/
protected static function recall($recaller)
{
- // When the "remember me" cookie is stored, it is encrypted and contains the
- // user's ID and a long, random string. The ID and string are separated by
- // a pipe character. Since we exploded the decrypted string, we can just
- // pass the first item in the array to the user Closure.
+ // When the remember me cookie is stored, it is encrypted and contains
+ // the user's ID and a long, random string. The segments are separated
+ // by a pipe character so we'll explode on that.
$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);
@@ -111,12 +115,13 @@ protected static function recall($recaller)
/**
* Attempt to log a user into the application.
*
- * If the credentials are valid, the user will be logged into the application
- * and their user ID will be stored in the session via the "login" method.
+ *
+ * // 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
- * application for one year or until they logout. The user is remembered via
- * an encrypted cookie.
+ * // Attempt to login a user and set the "remember me" cookie
+ * Auth::attempt('username', 'password', true);
+ *
*
* @param string $username
* @param string $password
@@ -127,32 +132,35 @@ public static function attempt($username, $password = null, $remember = false)
{
$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))
- {
- static::login($user, $remember);
+ // If the user credentials were authenticated by the closure, we will
+ // log the user into the application, which will store their user ID
+ // 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.
*
- * 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.
- *
*
- * // Login a user by passing a user object
- * Auth::login($user);
- *
* // Login the user with an ID of 15
* Auth::login(15);
*
+ * // Login a user by passing a user object
+ * Auth::login($user);
+ *
* // Login a user and set a "remember me" cookie
* Auth::login($user, true);
*
@@ -167,11 +175,11 @@ public static function login($user, $remember = false)
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
* @return void
@@ -183,7 +191,7 @@ protected static function remember($id)
// This method assumes the "remember me" cookie should have the same
// configuration as the session cookie. Since this cookie, like the
// session cookie, should be kept very secure, it's probably safe
- // to assume the settings are the same.
+ // to assume the settings are the same for this cookie.
$config = Config::get('session');
extract($config, EXTR_SKIP);
@@ -194,14 +202,13 @@ protected static function remember($id)
/**
* 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
*/
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());
static::$user = null;
@@ -213,11 +220,9 @@ public static function logout()
// When forgetting the cookie, we need to also pass in the path and
// domain that would have been used when the cookie was originally
// 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);
- IoC::core('session')->forget(Auth::user_key);
+ Session::forget(Auth::user_key);
}
}
\ No newline at end of file
diff --git a/laravel/autoloader.php b/laravel/autoloader.php
index 89603b72..982865ed 100644
--- a/laravel/autoloader.php
+++ b/laravel/autoloader.php
@@ -1,4 +1,4 @@
-
- * // Returns "user/profile"...
- * $file = static::controller('User_Profile_Controller');
- *
* // Register a class mapping with the Autoloader
- * Autoloader::maps(array('User' => MODEL_PATH.'user'.EXT));
+ * Autoloader::map(array('User' => APP_PATH.'models/user.php'));
*
*
* @param array $mappings
* @return void
*/
- public static function maps($mappings)
+ public static function map($mappings)
{
- foreach ($mappings as $class => $path)
- {
- static::$mappings[$class] = $path;
- }
+ static::$mappings = array_merge(static::$mappings, $mappings);
}
/**
- * 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
- * the application libraries directory. This method provides an easy way
- * to indicate that some libraries should be loaded using the PSR-0
- * naming conventions instead of the Laravel conventions.
- *
- *
- * // Register the "Assetic" library with the Autoloader
- * Autoloader::libraries('Assetic');
- *
- * // Register several libraries with the Autoloader
- * Autoloader::libraries(array('Assetic', 'Twig'));
- *
- *
- * @param array $libraries
+ * @param string $class
+ * @param string $alias
* @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));
}
}
\ No newline at end of file
diff --git a/laravel/benchmark.php b/laravel/benchmark.php
deleted file mode 100644
index a0d29f3c..00000000
--- a/laravel/benchmark.php
+++ /dev/null
@@ -1,49 +0,0 @@
-
+ * // 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');
+ *
+ *
+ * @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.
+ *
+ *
+ * // Returns "admin" as the bundle name for the identifier
+ * $bundle = Bundle::name('admin::home.index');
+ *
+ *
+ * @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.
+ *
+ *
+ * // Returns "home.index" as the element name for the identifier
+ * $bundle = Bundle::bundle('admin::home.index');
+ *
+ *
+ * @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.
+ *
+ *
+ * // Returns "admin::home.index"
+ * $identifier = Bundle::identifier('admin', 'home.index');
+ *
+ * // Returns "home.index"
+ * $identifier = Bundle::identifier('application', 'home.index');
+ *
+ *
+ * @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.
+ *
+ *
+ * // Returns array(null, 'admin.user')
+ * $element = Bundle::parse('admin.user');
+ *
+ * // Parses "admin::user" and returns array('admin', 'user')
+ * $element = Bundle::parse('admin::user');
+ *
+ *
+ * @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;
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cache/manager.php b/laravel/cache.php
similarity index 52%
rename from laravel/cache/manager.php
rename to laravel/cache.php
index a216a2ea..cc044373 100644
--- a/laravel/cache/manager.php
+++ b/laravel/cache.php
@@ -1,23 +1,18 @@
-
* // 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 ( ! 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];
@@ -53,33 +48,33 @@ protected static function factory($driver)
switch ($driver)
{
case 'apc':
- return new Drivers\APC(Config::get('cache.key'));
+ return new Cache\Drivers\APC(Config::get('cache.key'));
case 'file':
- return new Drivers\File(CACHE_PATH);
+ return new Cache\Drivers\File(CACHE_PATH);
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':
- 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:
- 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.
- *
- * Passing method calls to the driver instance provides a convenient API
- * for the developer when always using the default cache driver.
+ * Magic Method for calling the methods on the default cache driver.
*
*
- * // Call the "get" method on the default driver
+ * // Call the "get" method on the default cache driver
* $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);
*
*/
diff --git a/laravel/cache/drivers/database.php b/laravel/cache/drivers/database.php
new file mode 100644
index 00000000..85e8b13d
--- /dev/null
+++ b/laravel/cache/drivers/database.php
@@ -0,0 +1,113 @@
+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.
+ *
+ *
+ * // Put an item in the cache for 15 minutes
+ * Cache::put('name', 'Taylor', 15);
+ *
+ *
+ * @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'));
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cache/drivers/driver.php b/laravel/cache/drivers/driver.php
index a0241c5e..96bf0ef2 100644
--- a/laravel/cache/drivers/driver.php
+++ b/laravel/cache/drivers/driver.php
@@ -28,9 +28,7 @@ abstract public function has($key);
*/
public function get($key, $default = null)
{
- if ( ! is_null($item = $this->retrieve($key))) return $item;
-
- return ($default instanceof Closure) ? call_user_func($default) : $default;
+ return ( ! is_null($item = $this->retrieve($key))) ? $item : value($default);
}
/**
@@ -57,8 +55,7 @@ abstract protected function retrieve($key);
abstract public function put($key, $value, $minutes);
/**
- * Get an item from the cache. If the item doesn't exist in the
- * cache, store the default value in the cache and return it.
+ * Get an item from the cache, or cache and return the default value.
*
*
* // 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;
- $default = ($default instanceof Closure) ? call_user_func($default) : $default;
-
- $this->put($key, $default, $minutes);
+ $this->put($key, value($default), $minutes);
return $default;
}
@@ -92,4 +87,15 @@ public function remember($key, $default, $minutes)
*/
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);
+ }
+
}
diff --git a/laravel/cache/drivers/file.php b/laravel/cache/drivers/file.php
index 6bf7c6e0..dbf4520f 100644
--- a/laravel/cache/drivers/file.php
+++ b/laravel/cache/drivers/file.php
@@ -68,7 +68,7 @@ protected function retrieve($key)
*/
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);
}
@@ -81,10 +81,7 @@ public function put($key, $value, $minutes)
*/
public function forget($key)
{
- if (file_exists($this->path.$key))
- {
- @unlink($this->path.$key);
- }
+ if (file_exists($this->path.$key)) @unlink($this->path.$key);
}
}
\ No newline at end of file
diff --git a/laravel/cli/artisan.php b/laravel/cli/artisan.php
new file mode 100644
index 00000000..b81355e6
--- /dev/null
+++ b/laravel/cli/artisan.php
@@ -0,0 +1,62 @@
+getMessage();
+}
+
+echo PHP_EOL;
\ No newline at end of file
diff --git a/laravel/cli/command.php b/laravel/cli/command.php
new file mode 100644
index 00000000..09f9c5a8
--- /dev/null
+++ b/laravel/cli/command.php
@@ -0,0 +1,109 @@
+$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';
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cli/tasks/bundle/bundler.php b/laravel/cli/tasks/bundle/bundler.php
new file mode 100644
index 00000000..67a209e8
--- /dev/null
+++ b/laravel/cli/tasks/bundle/bundler.php
@@ -0,0 +1,127 @@
+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;
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cli/tasks/bundle/providers/github.php b/laravel/cli/tasks/bundle/providers/github.php
new file mode 100644
index 00000000..174c9916
--- /dev/null
+++ b/laravel/cli/tasks/bundle/providers/github.php
@@ -0,0 +1,27 @@
+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';
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cli/tasks/bundle/repository.php b/laravel/cli/tasks/bundle/repository.php
new file mode 100644
index 00000000..2ee43d9c
--- /dev/null
+++ b/laravel/cli/tasks/bundle/repository.php
@@ -0,0 +1,29 @@
+api.$bundle);
+
+ return json_decode($bundle, true);
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cli/tasks/migrate/database.php b/laravel/cli/tasks/migrate/database.php
new file mode 100644
index 00000000..e42f2713
--- /dev/null
+++ b/laravel/cli/tasks/migrate/database.php
@@ -0,0 +1,83 @@
+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');
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cli/tasks/migrate/migrator.php b/laravel/cli/tasks/migrate/migrator.php
new file mode 100644
index 00000000..dbd16f41
--- /dev/null
+++ b/laravel/cli/tasks/migrate/migrator.php
@@ -0,0 +1,235 @@
+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'];
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cli/tasks/migrate/resolver.php b/laravel/cli/tasks/migrate/resolver.php
new file mode 100644
index 00000000..b6bf5064
--- /dev/null
+++ b/laravel/cli/tasks/migrate/resolver.php
@@ -0,0 +1,159 @@
+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;
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/cli/tasks/migrate/stub.php b/laravel/cli/tasks/migrate/stub.php
new file mode 100644
index 00000000..6ce685f4
--- /dev/null
+++ b/laravel/cli/tasks/migrate/stub.php
@@ -0,0 +1,25 @@
+
*
@@ -46,29 +48,38 @@ public static function has($key)
* // Get the "session" configuration array
* $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
* $timezone = Config::get('application.timezone');
*
*
* @param string $key
- * @param string $default
* @return array
*/
- public static function get($key, $default = null)
+ public static function get($key)
{
- list($file, $key) = static::parse($key);
-
- if ( ! static::load($file))
+ // First, we'll check the keyed cache of configuration items, as this will
+ // be the fastest method of retrieving the configuration option. After an
+ // 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,
// meaning we need to return the entire array of configuration item from the
// 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
* 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
* Config::set('application.timezone', 'UTC');
*
@@ -88,61 +102,53 @@ public static function get($key, $default = null)
*/
public static function set($key, $value)
{
- list($file, $key) = static::parse($key);
-
- static::load($file);
-
- if (is_null($key))
- {
- Arr::set(static::$items, $file, $value);
- }
- else
- {
- Arr::set(static::$items[$file], $key, $value);
- }
+ static::$cache[$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
- * 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.
+ * Configuration items are named using the {bundle}::{file}.{item} convention.
*
* @param string $key
* @return array
*/
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)
{
- return array($segments[0], implode('.', array_slice($segments, 1)));
+ return array($bundle, $segments[0], implode('.', array_slice($segments, 1)));
}
else
{
- return array($segments[0], null);
+ return array($bundle, $segments[0], null);
}
}
/**
* Load all of the configuration items from a configuration file.
*
+ * @param string $bundle
* @param string $file
* @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();
- // Configuration files cascade. Typically, the system configuration array is
- // loaded first, followed by the application array, providing the convenient
- // cascading of configuration options from system to application.
- foreach (static::$paths as $directory)
+ // Configuration files cascade. Typically, the bundle configuration array is
+ // loaded first, followed by the environment array, providing the convenient
+ // cascading of configuration options across environments.
+ foreach (static::paths($bundle) as $directory)
{
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;
}
}
\ No newline at end of file
diff --git a/laravel/config/ascii.php b/laravel/config/ascii.php
deleted file mode 100644
index 37b795bf..00000000
--- a/laravel/config/ascii.php
+++ /dev/null
@@ -1,73 +0,0 @@
- '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',
-
-);
\ No newline at end of file
diff --git a/laravel/cookie.php b/laravel/cookie.php
index f478174c..bcc2c643 100644
--- a/laravel/cookie.php
+++ b/laravel/cookie.php
@@ -1,8 +1,10 @@
-
+ * // 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');
+ *
+ *
* @param string $name
* @param mixed $default
* @return string
*/
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 serves to verify that the contents of the cookie have not
- // been modified by the user. We can verify the integrity of the cookie
- // by extracting the value and re-hashing it, then comparing that hash
- // against the hash stored in the cookie.
- if (isset($value[40]) and $value[40] === '~')
- {
- list($hash, $value) = explode('~', $value, 2);
+ // The hash signature and the cookie value are separated by a tilde
+ // character for convenience. To separate the hash and the contents
+ // we can simply expode on that character.
+ //
+ // By re-feeding the cookie value into the "sign" method, we should
+ // 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);
- if (static::hash($name, $value) === $hash)
- {
- return $value;
- }
+ if (static::hash($name, $value) === $hash)
+ {
+ 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
- * @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 the response headers have already been sent, the cookie will not be set.
*
- * If a negative number of minutes is specified, the cookie will be deleted.
+ *
+ * // Set the value of the "favorite" cookie
+ * Cookie::put('favorite', 'Laravel');
*
- * This method's signature is very similar to the PHP setcookie method.
- * However, you simply need to pass the number of minutes for which you
- * wish the cookie to be valid. No funky time calculation is required.
+ * // Set the value of the "favorite" cookie for twenty minutes
+ * Cookie::put('favorite', 'Laravel', 20);
+ *
*
* @param string $name
* @param string $value
@@ -81,39 +78,51 @@ public static function forever($name, $value, $path = '/', $domain = null, $secu
* @param string $path
* @param string $domain
* @param bool $secure
- * @param bool $http_only
* @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;
$time = ($minutes !== 0) ? time() + ($minutes * 60) : 0;
- $value = static::hash($name, $value).'~'.$value;
-
- if ($minutes < 0)
- {
- unset($_COOKIE[$name]);
- }
- else
- {
- $_COOKIE[$name] = $value;
- }
-
- return setcookie($name, $value, $time, $path, $domain, $secure, $http_only);
+ return setcookie($name, static::sign($name, $value), $time, $path, $domain, $secure);
}
/**
- * 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
- * been modified by the user, since they serve as a fingerprint of the cookie
- * contents. The application key is used to salt the salts.
+ *
+ * // Set a cookie that should last one year
+ * Cookie::forever('favorite', 'Blue');
+ *
*
- * When the cookie is read using the "get" method, the value will be extracted
- * from the cookie and hashed, if the hash in the cookie and the hashed value
- * do not match, we know the cookie has been changed on the client.
+ * @param string $name
+ * @param string $value
+ * @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 $value
@@ -121,7 +130,7 @@ public static function put($name, $value, $minutes = 0, $path = '/', $domain = n
*/
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 $domain
* @param bool $secure
- * @param bool $http_only
* @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);
}
}
\ No newline at end of file
diff --git a/laravel/core.php b/laravel/core.php
index 066cfad6..6c799e7e 100644
--- a/laravel/core.php
+++ b/laravel/core.php
@@ -9,59 +9,22 @@
define('EXT', '.php');
define('CRLF', "\r\n");
define('BLADE_EXT', '.blade.php');
-define('APP_PATH', realpath($application).'/');
-define('PUBLIC_PATH', realpath($public).'/');
-define('SYS_PATH', realpath($laravel).'/');
-define('STORAGE_PATH', APP_PATH.'storage/');
-define('CACHE_PATH', STORAGE_PATH.'cache/');
-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);
+define('CACHE_PATH', STORAGE_PATH.'cache'.DS);
+define('DATABASE_PATH', STORAGE_PATH.'database'.DS);
+define('SESSION_PATH', STORAGE_PATH.'sessions'.DS);
+define('DEFAULT_BUNDLE', 'application');
+define('MB_STRING', (int) function_exists('mb_get_info'));
/**
* 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
* 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.'facades'.EXT;
+require SYS_PATH.'helpers'.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.
* 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.
*/
Autoloader::$mappings = array(
- 'Laravel\\Arr' => SYS_PATH.'arr'.EXT,
- 'Laravel\\Asset' => SYS_PATH.'asset'.EXT,
'Laravel\\Auth' => SYS_PATH.'auth'.EXT,
+ 'Laravel\\Asset' => SYS_PATH.'asset'.EXT,
'Laravel\\Benchmark' => SYS_PATH.'benchmark'.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\\Cookie' => SYS_PATH.'cookie'.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\\Fluent' => SYS_PATH.'fluent'.EXT,
'Laravel\\Form' => SYS_PATH.'form'.EXT,
'Laravel\\Hash' => SYS_PATH.'hash'.EXT,
'Laravel\\HTML' => SYS_PATH.'html'.EXT,
- 'Laravel\\Inflector' => SYS_PATH.'inflector'.EXT,
'Laravel\\Input' => SYS_PATH.'input'.EXT,
'Laravel\\IoC' => SYS_PATH.'ioc'.EXT,
'Laravel\\Lang' => SYS_PATH.'lang'.EXT,
+ 'Laravel\\Log' => SYS_PATH.'log'.EXT,
'Laravel\\Memcached' => SYS_PATH.'memcached'.EXT,
'Laravel\\Messages' => SYS_PATH.'messages'.EXT,
'Laravel\\Paginator' => SYS_PATH.'paginator'.EXT,
@@ -99,40 +67,62 @@
'Laravel\\Request' => SYS_PATH.'request'.EXT,
'Laravel\\Response' => SYS_PATH.'response'.EXT,
'Laravel\\Section' => SYS_PATH.'section'.EXT,
+ 'Laravel\\Session' => SYS_PATH.'session'.EXT,
'Laravel\\Str' => SYS_PATH.'str'.EXT,
'Laravel\\URI' => SYS_PATH.'uri'.EXT,
'Laravel\\URL' => SYS_PATH.'url'.EXT,
'Laravel\\Validator' => SYS_PATH.'validator'.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\\Driver' => SYS_PATH.'cache/drivers/driver'.EXT,
'Laravel\\Cache\\Drivers\\File' => SYS_PATH.'cache/drivers/file'.EXT,
'Laravel\\Cache\\Drivers\\Memcached' => SYS_PATH.'cache/drivers/memcached'.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\\Expression' => SYS_PATH.'database/expression'.EXT,
- 'Laravel\\Database\\Manager' => SYS_PATH.'database/manager'.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\\MySQL' => SYS_PATH.'database/connectors/mysql'.EXT,
'Laravel\\Database\\Connectors\\Postgres' => SYS_PATH.'database/connectors/postgres'.EXT,
'Laravel\\Database\\Connectors\\SQLite' => SYS_PATH.'database/connectors/sqlite'.EXT,
- 'Laravel\\Database\\Eloquent\\Hydrator' => SYS_PATH.'database/eloquent/hydrator'.EXT,
- 'Laravel\\Database\\Eloquent\\Model' => SYS_PATH.'database/eloquent/model'.EXT,
- 'Laravel\\Database\\Grammars\\Grammar' => SYS_PATH.'database/grammars/grammar'.EXT,
- 'Laravel\\Database\\Grammars\\MySQL' => SYS_PATH.'database/grammars/mysql'.EXT,
+ 'Laravel\\Database\\Connectors\\SQLServer' => SYS_PATH.'database/connectors/sqlserver'.EXT,
+ 'Laravel\\Database\\Query\\Grammars\\Grammar' => SYS_PATH.'database/query/grammars/grammar'.EXT,
+ 'Laravel\\Database\\Query\\Grammars\\MySQL' => SYS_PATH.'database/query/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\\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\\Router' => SYS_PATH.'routing/router'.EXT,
+
'Laravel\\Session\\Payload' => SYS_PATH.'session/payload'.EXT,
'Laravel\\Session\\Drivers\\APC' => SYS_PATH.'session/drivers/apc'.EXT,
'Laravel\\Session\\Drivers\\Cookie' => SYS_PATH.'session/drivers/cookie'.EXT,
'Laravel\\Session\\Drivers\\Database' => SYS_PATH.'session/drivers/database'.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\\Memcached' => SYS_PATH.'session/drivers/memcached'.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
- * the default timezone used by all date / timezone functions in
- * the entire application.
+ * Register all of the core class aliases. These aliases provide a
+ * convenient way of working with the Laravel core classes without
+ * 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']);
-
-/**
- * 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;
\ No newline at end of file
+Autoloader::$aliases = Config::get('application.aliases');
\ No newline at end of file
diff --git a/laravel/crypter.php b/laravel/crypter.php
index c6e3e2e4..eb45a518 100644
--- a/laravel/crypter.php
+++ b/laravel/crypter.php
@@ -1,8 +1,8 @@
-
- * // Encrypt a string using the Mcrypt PHP extension
- * $encrypted = Crypter::encrpt('secret');
- *
+ * The string will be encrypted using the AES-256 scheme and will be base64 encoded.
*
* @param string $value
* @return string
@@ -50,26 +41,27 @@ public static function encrypt($value)
/**
* 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
* @return string
*/
public static function decrypt($value)
{
- if (($value = base64_decode($value)) === false)
- {
- throw new \InvalidArgumentException('Input value is not valid base64 data.');
- }
+ $value = base64_decode($value);
+ // 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());
$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()
{
- return Config::$items['application']['key'];
+ return Config::get('application.key');
}
}
\ No newline at end of file
diff --git a/laravel/database/manager.php b/laravel/database.php
similarity index 65%
rename from laravel/database/manager.php
rename to laravel/database.php
index f24118e0..2595bc9e 100644
--- a/laravel/database/manager.php
+++ b/laravel/database.php
@@ -1,16 +1,16 @@
-connect($config);
}
/**
* 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
* @return Connector
*/
@@ -83,16 +69,19 @@ protected static function connector($driver)
switch ($driver)
{
case 'sqlite':
- return new Connectors\SQLite(DATABASE_PATH);
+ return new Database\Connectors\SQLite;
case 'mysql':
- return new Connectors\MySQL;
+ return new Database\Connectors\MySQL;
case 'pgsql':
- return new Connectors\Postgres;
+ return new Database\Connectors\Postgres;
+
+ case 'sqlsrv':
+ return new Database\Connectors\SQLServer;
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.
*
- * This provides a convenient API for querying or examining the default database connection.
+ *
+ * // 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();
+ *
*/
public static function __callStatic($method, $parameters)
{
diff --git a/laravel/database/connection.php b/laravel/database/connection.php
index 9147412a..c9dbad4d 100644
--- a/laravel/database/connection.php
+++ b/laravel/database/connection.php
@@ -9,13 +9,6 @@ class Connection {
*/
public $pdo;
- /**
- * All of the queries that have been executed on the connection.
- *
- * @var array
- */
- public $queries = array();
-
/**
* The connection configuration array.
*
@@ -30,6 +23,13 @@ class Connection {
*/
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.
*
@@ -46,6 +46,14 @@ public function __construct(PDO $pdo, $config)
/**
* Begin a fluent query against a table.
*
+ *
+ * // 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();
+ *
+ *
* @param string $table
* @return Query
*/
@@ -57,27 +65,22 @@ public function table($table)
/**
* Create a new query grammar for the connection.
*
- * Query grammars allow support for new database systems to be added quickly
- * 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
+ * @return Query\Grammars\Grammar
*/
protected function 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())
{
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:
- 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())
{
- $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.
- *
- * 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.
- *
- *
- * // 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));
- *
+ * Execute a SQL query and return an array of StdClass objects.
*
* @param string $sql
* @param array $bindings
- * @return mixed
+ * @return array
*/
public function query($sql, $bindings = array())
{
- // Since expressions are injected into the query as raw strings, we need
- // 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]);
- }
+ list($statement, $result) = $this->execute($sql, $bindings);
- $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);
- $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.
*
- * 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 array $bindings
* @return string
*/
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)
{
for ($i = 0; $i < count($bindings); $i++)
{
- // If the binding is an array, we can assume it is being used to fill
- // a "where in" condition, so we will replace the next place-holder
- // in the query with the correct number of parameters based on the
- // number of elements in this binding.
+ // If the binding is an array, we can assume it is being used
+ // to fill a "where in" condition, so we'll replace the next
+ // place-holder in the SQL query with the correct number of
+ // parameters based on the elements in the binding.
if (is_array($bindings[$i]))
{
$parameters = implode(', ', array_fill(0, count($bindings[$i]), '?'));
@@ -200,33 +260,6 @@ protected function transform($sql, $bindings)
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.
*
diff --git a/laravel/database/connectors/connector.php b/laravel/database/connectors/connector.php
index 3294bbc0..01065583 100644
--- a/laravel/database/connectors/connector.php
+++ b/laravel/database/connectors/connector.php
@@ -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
* @return PDO
@@ -24,7 +24,7 @@ abstract class Connector {
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.
*
diff --git a/laravel/database/connectors/mysql.php b/laravel/database/connectors/mysql.php
index 260722f2..16cbc2a7 100644
--- a/laravel/database/connectors/mysql.php
+++ b/laravel/database/connectors/mysql.php
@@ -3,7 +3,7 @@
class MySQL extends Connector {
/**
- * Establish a PDO database connection for a given database configuration.
+ * Establish a PDO database connection.
*
* @param array $config
* @return PDO
@@ -15,7 +15,7 @@ public function connect($config)
// Format the initial MySQL PDO connection string. These options are required
// for every MySQL connection that is established. The connection strings
// 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
// to establish a PDO connection; however, may be needed in certain server
diff --git a/laravel/database/connectors/postgres.php b/laravel/database/connectors/postgres.php
index f9c0e672..0ac9f832 100644
--- a/laravel/database/connectors/postgres.php
+++ b/laravel/database/connectors/postgres.php
@@ -3,7 +3,7 @@
class Postgres extends Connector {
/**
- * Establish a PDO database connection for a given database configuration.
+ * Establish a PDO database connection.
*
* @param array $config
* @return PDO
@@ -15,7 +15,7 @@ public function connect($config)
// Format the initial Postgres PDO connection string. These options are required
// for every Postgres connection that is established. The connection strings
// 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
// to establish a PDO connection; however, may be needed in certain server
diff --git a/laravel/database/connectors/sqlite.php b/laravel/database/connectors/sqlite.php
index 0ffe8ed6..61f12b14 100644
--- a/laravel/database/connectors/sqlite.php
+++ b/laravel/database/connectors/sqlite.php
@@ -3,25 +3,7 @@
class SQLite extends Connector {
/**
- * The path to the SQLite databases for the application.
- *
- * @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.
+ * Establish a PDO database connection.
*
* @param array $config
* @return PDO
@@ -32,27 +14,19 @@ public function connect($config)
// 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
- // 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.
if ($config['database'] == ':memory:')
{
return new PDO('sqlite::memory:', null, null, $options);
}
- // First, we will check for the database in the default storage directory for the
- // 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'))
+ if (file_exists($path = DATABASE_PATH.$config['database'].'.sqlite'))
{
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.");
}
}
diff --git a/laravel/database/connectors/sqlserver.php b/laravel/database/connectors/sqlserver.php
new file mode 100644
index 00000000..8e57b99c
--- /dev/null
+++ b/laravel/database/connectors/sqlserver.php
@@ -0,0 +1,38 @@
+ 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));
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/eloquent/hydrator.php b/laravel/database/eloquent/hydrator.php
deleted file mode 100644
index 7c188d63..00000000
--- a/laravel/database/eloquent/hydrator.php
+++ /dev/null
@@ -1,212 +0,0 @@
-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;
- }
- }
-
-}
diff --git a/laravel/database/eloquent/model.php b/laravel/database/eloquent/model.php
deleted file mode 100644
index 0699a730..00000000
--- a/laravel/database/eloquent/model.php
+++ /dev/null
@@ -1,538 +0,0 @@
-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);
- }
-
-}
\ No newline at end of file
diff --git a/laravel/database/grammar.php b/laravel/database/grammar.php
new file mode 100644
index 00000000..f98a1cf7
--- /dev/null
+++ b/laravel/database/grammar.php
@@ -0,0 +1,108 @@
+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.
+ *
+ *
+ * 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')));
+ *
+ *
+ * @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.
+ *
+ *
+ * // 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'));
+ *
+ *
+ * @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.
+ *
+ *
+ * // Returns ""Taylor", "Otwell"" when the identifier is quotes
+ * $columns = $grammar->columnize(array('Taylor', 'Otwell'));
+ *
+ *
+ * @param array $columns
+ * @return string
+ */
+ final public function columnize($columns)
+ {
+ return implode(', ', array_map(array($this, 'wrap'), $columns));
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/query.php b/laravel/database/query.php
index 93364675..fabeb4dd 100644
--- a/laravel/database/query.php
+++ b/laravel/database/query.php
@@ -1,4 +1,10 @@
-from = $table;
$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
*/
@@ -203,8 +216,16 @@ public function raw_or_where($where, $bindings = array())
* @param string $connector
* @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';
$this->wheres[] = compact('type', 'column', 'operator', 'value', 'connector');
@@ -222,7 +243,7 @@ public function where($column, $operator, $value, $connector = 'AND')
* @param mixed $value
* @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');
}
@@ -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.
- * They provide a convenient, expressive API for building simple conditions.
+ * @param Closure $callback
+ * @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 array $parameters
@@ -358,11 +404,11 @@ public function or_where_not_null($column)
*/
private function dynamic_where($method, $parameters)
{
- // Strip the "where_" off of the method.
$finder = substr($method, 6);
- // Split the column names from the connectors.
- $segments = preg_split('/(_and_|_or_)/i', $finder, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $flags = PREG_SPLIT_DELIM_CAPTURE;
+
+ $segments = preg_split('/(_and_|_or_)/i', $finder, -1, $flags);
// The connector variable will determine which connector will be
// 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)
{
+ // 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_')
{
$this->where($segment, '=', $parameters[$index], $connector);
@@ -392,6 +445,18 @@ private function dynamic_where($method, $parameters)
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.
*
@@ -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 $per_page
@@ -461,16 +526,14 @@ public function find($id, $columns = array('*'))
*/
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.
*
- * If a single column is selected from the database, only the value of that column will be returned.
- *
* @param array $columns
* @return mixed
*/
@@ -478,7 +541,51 @@ public function first($columns = array('*'))
{
$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);
- $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
// the same instance. This is helpful for getting aggregates and
- // then getting actual results.
+ // then getting actual results from the query.
$this->selects = null;
return $results;
@@ -512,11 +633,13 @@ private function aggregate($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
// 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;
return $result;
@@ -531,17 +654,23 @@ private function aggregate($aggregator, $column)
*/
public function paginate($per_page = 20, $columns = array('*'))
{
- // Because some database engines may throw errors if we leave
- // orderings on the query when retrieving the total number
- // of records, we will remove all of the ordreings and put
- // them back on the query after we have the count.
+ // Because some database engines may throw errors if we leave orderings
+ // on the query when retrieving the total number of records, we will
+ // remove all of the ordreings and put them back on the query after
+ // we have the count.
list($orderings, $this->orderings) = array($this->orderings, null);
$page = Paginator::page($total = $this->count(), $per_page);
$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();
+ // 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)
{
$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
- * return the value of the ID column.
+ * Insert an array of values into the database table and return the ID.
*
* @param array $values
* @param string $sequence
@@ -577,8 +710,13 @@ public function insert($values)
*/
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);
}
@@ -616,7 +754,10 @@ public function decrement($column, $amount = 1)
*/
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));
}
@@ -629,9 +770,15 @@ protected function adjust($column, $amount, $operator)
*/
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);
- 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)
{
- 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.
*
- * This method handles all calls to aggregate functions as well
- * as the construction of dynamic where clauses.
+ * This method handles calls to aggregates as well as dynamic where clauses.
*/
public function __call($method, $parameters)
{
@@ -662,7 +816,7 @@ public function __call($method, $parameters)
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')
{
@@ -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.");
}
}
diff --git a/laravel/database/grammars/grammar.php b/laravel/database/query/grammars/grammar.php
similarity index 52%
rename from laravel/database/grammars/grammar.php
rename to laravel/database/query/grammars/grammar.php
index 54b50616..3f01fbf4 100644
--- a/laravel/database/grammars/grammar.php
+++ b/laravel/database/query/grammars/grammar.php
@@ -1,61 +1,70 @@
-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)
{
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.
*
- * 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
* @return string
*/
@@ -92,9 +96,6 @@ protected function aggregate(Query $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
* @return string
*/
@@ -111,8 +112,13 @@ protected function from(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)
{
$table = $this->wrap($join['table']);
@@ -121,7 +127,7 @@ protected function joins(Query $query)
$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);
@@ -136,26 +142,44 @@ protected function joins(Query $query)
final protected function wheres(Query $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
- // iterate through the where clauses and call the appropriate compiler
- // for each clause.
+ // builder, and each type has its own compiler function. We will call
+ // the appropriate compiler for each where clause in the query.
+ //
+ // 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)
{
$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.
*
- * 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
* @return string
*/
@@ -220,11 +244,22 @@ protected function where_not_null($where)
* @param array $where
* @return string
*/
- protected function where_raw($where)
+ final protected function where_raw($where)
{
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.
*
@@ -235,7 +270,9 @@ protected function orderings(Query $query)
{
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);
@@ -266,7 +303,7 @@ protected function offset(Query $query)
/**
* 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 array $values
@@ -274,14 +311,16 @@ protected function offset(Query $query)
*/
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
// the values as if it is an array containing multiple inserts.
if ( ! is_array(reset($values))) $values = array($values);
// Since we only care about the column names, we can pass any of the insert
- // arrays into the "columnize" method. The names should be the same for
- // every insert to the table.
+ // arrays into the "columnize" method. The columns should be the same for
+ // every insert to the table so we can just use the first record.
$columns = $this->columnize(array_keys(reset($values)));
// 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.
$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.
*
- * 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 array $values
* @return string
*/
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)
{
$columns[] = $this->wrap($column).' = '.$this->parameter($value);
@@ -314,7 +355,11 @@ public function update(Query $query, $values)
$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)
{
- return trim('DELETE FROM '.$this->wrap($query->from).' '.$this->wheres($query));
- }
+ $table = $this->wrap($query->from);
- /**
- * The following functions primarily serve as utility functions for
- * the grammar. They perform tasks such as wrapping values in keyword
- * identifiers or creating variable lists of bindings.
- */
-
- /**
- * 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() : '?';
+ // Like the UPDATE statement, the DELETE statement is constrained
+ // by WHERE clauses, so we'll need to run the "wheres" method to
+ // make the WHERE clauses for the query. The "wheres" method
+ // encapsulates the logic to create the full WHERE clause.
+ return trim("DELETE FROM {$table} ".$this->wheres($query));
}
}
\ No newline at end of file
diff --git a/laravel/database/grammars/mysql.php b/laravel/database/query/grammars/mysql.php
similarity index 59%
rename from laravel/database/grammars/mysql.php
rename to laravel/database/query/grammars/mysql.php
index 8c45550f..37692e0a 100644
--- a/laravel/database/grammars/mysql.php
+++ b/laravel/database/query/grammars/mysql.php
@@ -1,4 +1,4 @@
-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;
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/schema.php b/laravel/database/schema.php
new file mode 100644
index 00000000..84de3eb0
--- /dev/null
+++ b/laravel/database/schema.php
@@ -0,0 +1,119 @@
+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].");
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/schema/grammars/grammar.php b/laravel/database/schema/grammars/grammar.php
new file mode 100644
index 00000000..e09d2986
--- /dev/null
+++ b/laravel/database/schema/grammars/grammar.php
@@ -0,0 +1,38 @@
+{'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);
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/schema/grammars/mysql.php b/laravel/database/schema/grammars/mysql.php
new file mode 100644
index 00000000..89bd1b95
--- /dev/null
+++ b/laravel/database/schema/grammars/mysql.php
@@ -0,0 +1,386 @@
+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';
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/schema/grammars/postgres.php b/laravel/database/schema/grammars/postgres.php
new file mode 100644
index 00000000..1fb6d21a
--- /dev/null
+++ b/laravel/database/schema/grammars/postgres.php
@@ -0,0 +1,381 @@
+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';
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/schema/grammars/sqlite.php b/laravel/database/schema/grammars/sqlite.php
new file mode 100644
index 00000000..b86b9fb2
--- /dev/null
+++ b/laravel/database/schema/grammars/sqlite.php
@@ -0,0 +1,344 @@
+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';
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/schema/grammars/sqlserver.php b/laravel/database/schema/grammars/sqlserver.php
new file mode 100644
index 00000000..b4b5342c
--- /dev/null
+++ b/laravel/database/schema/grammars/sqlserver.php
@@ -0,0 +1,402 @@
+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)';
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/database/schema/table.php b/laravel/database/schema/table.php
new file mode 100644
index 00000000..ef588833
--- /dev/null
+++ b/laravel/database/schema/table.php
@@ -0,0 +1,371 @@
+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);
+ }
+
+}
\ No newline at end of file
diff --git a/laravel/error.php b/laravel/error.php
new file mode 100644
index 00000000..d82a7d13
--- /dev/null
+++ b/laravel/error.php
@@ -0,0 +1,96 @@
+
".$exception->getMessage()."+
".$exception->getFile()." on line ".$exception->getLine()."+
".$exception->getTraceAsString()."