the whole shebang

This commit is contained in:
2014-11-25 16:42:40 +01:00
parent 7f74c0613e
commit ab1334c0cf
3686 changed files with 496409 additions and 1 deletions

2
vendor/laravel/framework/.gitattributes vendored Executable file
View File

@@ -0,0 +1,2 @@
/build export-ignore
/tests export-ignore

4
vendor/laravel/framework/.gitignore vendored Executable file
View File

@@ -0,0 +1,4 @@
/vendor
composer.phar
composer.lock
.DS_Store

12
vendor/laravel/framework/.travis.yml vendored Executable file
View File

@@ -0,0 +1,12 @@
language: php
php:
- 5.3
- 5.4
- 5.5
before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --prefer-source --no-interaction --dev
script: phpunit

View File

@@ -0,0 +1,26 @@
# Laravel Contribution Guide
This page contains guidelines for contributing to the Laravel framework. Please review these guidelines before submitting any pull requests to the framework.
## Which Branch?
**ALL** bug fixes should be made to the 4.x branch which they belong. Bug fixes should never be sent to the `master` branch unless they fix features that exist only in the upcoming release.
## Pull Requests
The pull request process differs for new features and bugs. Before sending a pull request for a new feature, you should first create an issue with `[Proposal]` in the title. The proposal should describe the new feature, as well as implementation ideas. The proposal will then be reviewed and either approved or denied. Once a proposal is approved, a pull request may be created implementing the new feature. Pull requests which do not follow this guideline will be closed immediately.
Pull requests for bugs may be sent without creating any proposal issue. If you believe that you know of a solution for a bug that has been filed on Github, please leave a comment detailing your proposed fix.
### Feature Requests
If you have an idea for a new feature you would like to see added to Laravel, you may create an issue on Github with `[Request]` in the title. The feature request will then be reviewed by a core contributor.
## Coding Guidelines
Laravel follows the [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) and [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) coding standards. In addition to these standards, below is a list of other coding standards that should be followed:
- Namespace declarations should be on the same line as `<?php`.
- Class opening `{` should be on the same line as the class name.
- Function and control structure opening `{` should be on a separate line.
- Interface names are suffixed with `Interface` (`FooInterface`)

89
vendor/laravel/framework/composer.json vendored Executable file
View File

@@ -0,0 +1,89 @@
{
"name": "laravel/framework",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"classpreloader/classpreloader": "1.0.*",
"doctrine/dbal": "2.4.x",
"ircmaxell/password-compat": "1.0.*",
"filp/whoops": "1.0.7",
"monolog/monolog": "1.6.*",
"nesbot/carbon": "1.*",
"patchwork/utf8": "1.1.*",
"predis/predis": "0.8.*",
"swiftmailer/swiftmailer": "5.0.*",
"symfony/browser-kit": "2.3.*",
"symfony/console": "2.3.*",
"symfony/css-selector": "2.3.*",
"symfony/debug": "2.3.*",
"symfony/dom-crawler": "2.3.*",
"symfony/event-dispatcher": "2.3.*",
"symfony/finder": "2.3.*",
"symfony/http-foundation": "2.3.*",
"symfony/http-kernel": "2.3.*",
"symfony/process": "2.3.*",
"symfony/routing": "2.3.*",
"symfony/translation": "2.3.*"
},
"replace": {
"illuminate/auth": "self.version",
"illuminate/cache": "self.version",
"illuminate/config": "self.version",
"illuminate/console": "self.version",
"illuminate/container": "self.version",
"illuminate/cookie": "self.version",
"illuminate/database": "self.version",
"illuminate/encryption": "self.version",
"illuminate/events": "self.version",
"illuminate/exception": "self.version",
"illuminate/filesystem": "self.version",
"illuminate/foundation": "self.version",
"illuminate/hashing": "self.version",
"illuminate/http": "self.version",
"illuminate/html": "self.version",
"illuminate/log": "self.version",
"illuminate/mail": "self.version",
"illuminate/pagination": "self.version",
"illuminate/queue": "self.version",
"illuminate/redis": "self.version",
"illuminate/routing": "self.version",
"illuminate/session": "self.version",
"illuminate/support": "self.version",
"illuminate/translation": "self.version",
"illuminate/validation": "self.version",
"illuminate/view": "self.version",
"illuminate/workbench": "self.version"
},
"require-dev": {
"aws/aws-sdk-php": "2.2.*",
"iron-io/iron_mq": "1.4.4",
"pda/pheanstalk": "2.0.*",
"mockery/mockery": "0.7.2",
"phpunit/phpunit": "3.7.*"
},
"autoload": {
"classmap": [
["src/Illuminate/Queue/IlluminateQueueClosure.php"]
],
"files": [
"src/Illuminate/Support/helpers.php"
],
"psr-0": {
"Illuminate": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"minimum-stability": "dev"
}

28
vendor/laravel/framework/phpunit.php vendored Executable file
View File

@@ -0,0 +1,28 @@
<?php
/*
|--------------------------------------------------------------------------
| Register The Composer Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__.'/vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Set The Default Timezone
|--------------------------------------------------------------------------
|
| Here we will set the default timezone for PHP. PHP is notoriously mean
| if the timezone is not explicitly set. This will be used by each of
| the PHP date and date-time functions throughout the application.
|
*/
date_default_timezone_set('UTC');

27
vendor/laravel/framework/phpunit.xml vendored Executable file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="phpunit.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Laravel Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="false">
<directory suffix=".php">src</directory>
<exclude>
<directory suffix=".php">vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

5
vendor/laravel/framework/readme.md vendored Executable file
View File

@@ -0,0 +1,5 @@
## Laravel Framework (Core)
[![Latest Stable Version](https://poser.pugx.org/laravel/framework/version.png)](https://packagist.org/packages/laravel/framework) [![Total Downloads](https://poser.pugx.org/laravel/framework/d/total.png)](https://packagist.org/packages/laravel/framework) [![Build Status](https://travis-ci.org/laravel/framework.png)](https://travis-ci.org/laravel/framework)
This repository contains the core code of the Laravel framework. If you want to build an application using Laravel 4, visit the main [Laravel repository](https://github.com/laravel/laravel).

View File

@@ -0,0 +1,105 @@
<?php namespace Illuminate\Auth;
use Illuminate\Support\Manager;
class AuthManager extends Manager {
/**
* Create a new driver instance.
*
* @param string $driver
* @return mixed
*/
protected function createDriver($driver)
{
$guard = parent::createDriver($driver);
// When using the remember me functionality of the authentication services we
// will need to be set the encryption instance of the guard, which allows
// secure, encrypted cookie values to get generated for those cookies.
$guard->setCookieJar($this->app['cookie']);
$guard->setDispatcher($this->app['events']);
return $guard->setRequest($this->app['request']);
}
/**
* Call a custom driver creator.
*
* @param string $driver
* @return mixed
*/
protected function callCustomCreator($driver)
{
$custom = parent::callCustomCreator($driver);
if ($custom instanceof Guard) return $custom;
return new Guard($custom, $this->app['session']);
}
/**
* Create an instance of the database driver.
*
* @return \Illuminate\Auth\Guard
*/
protected function createDatabaseDriver()
{
$provider = $this->createDatabaseProvider();
return new Guard($provider, $this->app['session']);
}
/**
* Create an instance of the database user provider.
*
* @return \Illuminate\Auth\DatabaseUserProvider
*/
protected function createDatabaseProvider()
{
$connection = $this->app['db']->connection();
// When using the basic database user provider, we need to inject the table we
// want to use, since this is not an Eloquent model we will have no way to
// know without telling the provider, so we'll inject the config value.
$table = $this->app['config']['auth.table'];
return new DatabaseUserProvider($connection, $this->app['hash'], $table);
}
/**
* Create an instance of the Eloquent driver.
*
* @return \Illuminate\Auth\Guard
*/
public function createEloquentDriver()
{
$provider = $this->createEloquentProvider();
return new Guard($provider, $this->app['session']);
}
/**
* Create an instance of the Eloquent user provider.
*
* @return \Illuminate\Auth\EloquentUserProvider
*/
protected function createEloquentProvider()
{
$model = $this->app['config']['auth.model'];
return new EloquentUserProvider($this->app['hash'], $model);
}
/**
* Get the default authentication driver name.
*
* @return string
*/
protected function getDefaultDriver()
{
return $this->app['config']['auth.driver'];
}
}

View File

@@ -0,0 +1,79 @@
<?php namespace Illuminate\Auth;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
$this->registerAuthEvents();
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app['auth'] = $this->app->share(function($app)
{
// Once the authentication service has actually been requested by the developer
// we will set a variable in the application indicating such. This helps us
// know that we need to set any queued cookies in the after event later.
$app['auth.loaded'] = true;
return new AuthManager($app);
});
}
/**
* Register the events needed for authentication.
*
* @return void
*/
protected function registerAuthEvents()
{
$app = $this->app;
$app->after(function($request, $response) use ($app)
{
// If the authentication service has been used, we'll check for any cookies
// that may be queued by the service. These cookies are all queued until
// they are attached onto Response objects at the end of the requests.
if (isset($app['auth.loaded']))
{
foreach ($app['auth']->getDrivers() as $driver)
{
foreach ($driver->getQueuedCookies() as $cookie)
{
$response->headers->setCookie($cookie);
}
}
}
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array('auth');
}
}

View File

@@ -0,0 +1,96 @@
<?php namespace Illuminate\Auth\Console;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class MakeRemindersCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'auth:reminders';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a migration for the password reminders table';
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* Create a new reminder table command instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @return void
*/
public function __construct(Filesystem $files)
{
parent::__construct();
$this->files = $files;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$fullPath = $this->createBaseMigration();
$this->files->put($fullPath, $this->getMigrationStub());
$this->info('Migration created successfully!');
$this->call('dump-autoload');
}
/**
* Create a base migration file for the reminders.
*
* @return string
*/
protected function createBaseMigration()
{
$name = 'create_password_reminders_table';
$path = $this->laravel['path'].'/database/migrations';
return $this->laravel['migration.creator']->create($name, $path);
}
/**
* Get the contents of the reminder migration stub.
*
* @return string
*/
protected function getMigrationStub()
{
$stub = $this->files->get(__DIR__.'/stubs/reminders.stub');
return str_replace('password_reminders', $this->getTable(), $stub);
}
/**
* Get the password reminder table name.
*
* @return string
*/
protected function getTable()
{
return $this->laravel['config']->get('auth.reminder.table');
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
class CreatePasswordRemindersTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('password_reminders', function($t)
{
$t->string('email');
$t->string('token');
$t->timestamp('created_at');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('password_reminders');
}
}

View File

@@ -0,0 +1,106 @@
<?php namespace Illuminate\Auth;
use Illuminate\Database\Connection;
use Illuminate\Hashing\HasherInterface;
class DatabaseUserProvider implements UserProviderInterface {
/**
* The active database connection.
*
* @param \Illuminate\Database\Connection
*/
protected $conn;
/**
* The hasher implementation.
*
* @var \Illuminate\Hashing\HasherInterface
*/
protected $hasher;
/**
* The table containing the users.
*
* @var string
*/
protected $table;
/**
* Create a new database user provider.
*
* @param \Illuminate\Database\Connection $conn
* @param \Illuminate\Hashing\HasherInterface $hasher
* @param string $table
* @return void
*/
public function __construct(Connection $conn, HasherInterface $hasher, $table)
{
$this->conn = $conn;
$this->table = $table;
$this->hasher = $hasher;
}
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @return \Illuminate\Auth\UserInterface|null
*/
public function retrieveById($identifier)
{
$user = $this->conn->table($this->table)->find($identifier);
if ( ! is_null($user))
{
return new GenericUser((array) $user);
}
}
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Auth\UserInterface|null
*/
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// generic "user" object that will be utilized by the Guard instances.
$query = $this->conn->table($this->table);
foreach ($credentials as $key => $value)
{
if ( ! str_contains($key, 'password'))
{
$query->where($key, $value);
}
}
// Now we are ready to execute the query to see if we have an user matching
// the given credentials. If not, we will just return nulls and indicate
// that there are no matching users for these given credential arrays.
$user = $query->first();
if ( ! is_null($user))
{
return new GenericUser((array) $user);
}
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Auth\UserInterface $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserInterface $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
}

View File

@@ -0,0 +1,92 @@
<?php namespace Illuminate\Auth;
use Illuminate\Hashing\HasherInterface;
class EloquentUserProvider implements UserProviderInterface {
/**
* The hasher implementation.
*
* @var \Illuminate\Hashing\HasherInterface
*/
protected $hasher;
/**
* The Eloquent user model.
*
* @var string
*/
protected $model;
/**
* Create a new database user provider.
*
* @param \Illuminate\Hashing\HasherInterface $hasher
* @param string $model
* @return void
*/
public function __construct(HasherInterface $hasher, $model)
{
$this->model = $model;
$this->hasher = $hasher;
}
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @return \Illuminate\Auth\UserInterface|null
*/
public function retrieveById($identifier)
{
return $this->createModel()->newQuery()->find($identifier);
}
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Auth\UserInterface|null
*/
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value)
{
if ( ! str_contains($key, 'password')) $query->where($key, $value);
}
return $query->first();
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Auth\UserInterface $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserInterface $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
/**
* Create a new instance of the model.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function createModel()
{
$class = '\\'.ltrim($this->model, '\\');
return new $class;
}
}

View File

@@ -0,0 +1,86 @@
<?php namespace Illuminate\Auth;
class GenericUser implements UserInterface {
/**
* All of the user's attributes.
*
* @var array
*/
protected $attributes;
/**
* Create a new generic User object.
*
* @param array $attributes
* @return void
*/
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->attributes['id'];
}
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
return $this->attributes['password'];
}
/**
* Dynamically access the user's attributes.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->attributes[$key];
}
/**
* Dynamically set an attribute on the user.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function __set($key, $value)
{
$this->attributes[$key] = $value;
}
/**
* Dynamically check if a value is set on the user.
*
* @return bool
*/
public function __isset($key)
{
return isset($this->attributes[$key]);
}
/**
* Dynamically unset a value on the user.
*
* @return bool
*/
public function __unset($key)
{
unset($this->attributes[$key]);
}
}

View File

@@ -0,0 +1,589 @@
<?php namespace Illuminate\Auth;
use Illuminate\Cookie\CookieJar;
use Illuminate\Events\Dispatcher;
use Illuminate\Encryption\Encrypter;
use Symfony\Component\HttpFoundation\Request;
use Illuminate\Session\Store as SessionStore;
use Symfony\Component\HttpFoundation\Response;
class Guard {
/**
* The currently authenticated user.
*
* @var UserInterface
*/
protected $user;
/**
* The user provider implementation.
*
* @var \Illuminate\Auth\UserProviderInterface
*/
protected $provider;
/**
* The session store used by the guard.
*
* @var \Illuminate\Session\Store
*/
protected $session;
/**
* The Illuminate cookie creator service.
*
* @var \Illuminate\Cookie\CookieJar
*/
protected $cookie;
/**
* The request instance.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The cookies queued by the guards.
*
* @var array
*/
protected $queuedCookies = array();
/**
* The event dispatcher instance.
*
* @var \Illuminate\Events\Dispatcher
*/
protected $events;
/**
* Indicates if the logout method has been called.
*
* @var bool
*/
protected $loggedOut = false;
/**
* Create a new authentication guard.
*
* @param \Illuminate\Auth\UserProviderInterface $provider
* @param \Illuminate\Session\Store $session
* @return void
*/
public function __construct(UserProviderInterface $provider,
SessionStore $session)
{
$this->session = $session;
$this->provider = $provider;
}
/**
* Determine if the current user is authenticated.
*
* @return bool
*/
public function check()
{
return ! is_null($this->user());
}
/**
* Determine if the current user is a guest.
*
* @return bool
*/
public function guest()
{
return is_null($this->user());
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Auth\UserInterface|null
*/
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method becaue that would tremendously slow the app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) and ! is_null($recaller))
{
$user = $this->provider->retrieveByID($recaller);
}
return $this->user = $user;
}
/**
* Get the decrypted recaller cookie for the request.
*
* @return string|null
*/
protected function getRecaller()
{
if (isset($this->cookie))
{
return $this->getCookieJar()->get($this->getRecallerName());
}
}
/**
* Log a user into the application without sessions or cookies.
*
* @param array $credentials
* @return bool
*/
public function once(array $credentials = array())
{
if ($this->validate($credentials))
{
$this->setUser($this->provider->retrieveByCredentials($credentials));
return true;
}
return false;
}
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = array())
{
return $this->attempt($credentials, false, false);
}
/**
* Attempt to authenticate using HTTP Basic Auth.
*
* @param string $field
* @param \Symfony\Component\HttpFoundation\Request $request
* @return \Symfony\Component\HttpFoundation\Response|null
*/
public function basic($field = 'email', Request $request = null)
{
if ($this->check()) return;
$request = $request ?: $this->getRequest();
// If a username is set on the HTTP basic request, we will return out without
// interrupting the request lifecycle. Otherwise, we'll need to generate a
// request indicating that the given credentials were invalid for login.
if ($this->attemptBasic($request, $field)) return;
return $this->getBasicResponse();
}
/**
* Perform a stateless HTTP Basic login attempt.
*
* @param string $field
* @param \Symfony\Component\HttpFoundation\Request $request
* @return \Symfony\Component\HttpFoundation\Response|null
*/
public function onceBasic($field = 'email', Request $request = null)
{
$request = $request ?: $this->getRequest();
if ( ! $this->once($this->getBasicCredentials($request, $field)))
{
return $this->getBasicResponse();
}
}
/**
* Attempt to authenticate using basic authentication.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param string $field
* @return bool
*/
protected function attemptBasic(Request $request, $field)
{
if ( ! $request->getUser()) return false;
return $this->attempt($this->getBasicCredentials($request, $field));
}
/**
* Get the credential array for a HTTP Basic request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param string $field
* @return array
*/
protected function getBasicCredentials(Request $request, $field)
{
return array($field => $request->getUser(), 'password' => $request->getPassword());
}
/**
* Get the response for basic authentication.
*
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function getBasicResponse()
{
$headers = array('WWW-Authenticate' => 'Basic');
return new Response('Invalid credentials.', 401, $headers);
}
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @param bool $login
* @return bool
*/
public function attempt(array $credentials = array(), $remember = false, $login = true)
{
$this->fireAttemptEvent($credentials, $remember, $login);
$user = $this->provider->retrieveByCredentials($credentials);
// If an implementation of UserInterface was returned, we'll ask the provider
// to validate the user against the given credentials, and if they are in
// fact valid we'll log the users into the application and return true.
if ($user instanceof UserInterface)
{
if ($this->provider->validateCredentials($user, $credentials))
{
if ($login) $this->login($user, $remember);
return true;
}
}
return false;
}
/**
* Fire the attempt event with the arguments.
*
* @param array $credentials
* @param bool $remember
* @param bool $login
* @return void
*/
protected function fireAttemptEvent(array $credentials, $remember, $login)
{
if ($this->events)
{
$payload = array($credentials, $remember, $login);
$this->events->fire('auth.attempt', $payload);
}
}
/**
* Register an authentication attempt event listener.
*
* @param mixed $callback
* @return void
*/
public function attempting($callback)
{
if ($this->events)
{
$this->events->listen('auth.attempt', $callback);
}
}
/**
* Log a user into the application.
*
* @param \Illuminate\Auth\UserInterface $user
* @param bool $remember
* @return void
*/
public function login(UserInterface $user, $remember = false)
{
$id = $user->getAuthIdentifier();
$this->session->put($this->getName(), $id);
// If the user should be permanently "remembered" by the application we will
// queue a permanent cookie that contains the encrypted copy of the user
// identifier. We will then decrypt this later to retrieve the users.
if ($remember)
{
$this->queuedCookies[] = $this->createRecaller($id);
}
// If we have an event dispatcher instance set we will fire an event so that
// any listeners will hook into the authentication events and run actions
// based on the login and logout events fired from the guard instances.
if (isset($this->events))
{
$this->events->fire('auth.login', array($user, $remember));
}
$this->setUser($user);
}
/**
* Log the given user ID into the application.
*
* @param mixed $id
* @param bool $remember
* @return \Illuminate\Auth\UserInterface
*/
public function loginUsingId($id, $remember = false)
{
$this->session->put($this->getName(), $id);
return $this->login($this->provider->retrieveById($id), $remember);
}
/**
* Log the given user ID into the application without sessions or cookies.
*
* @param mixed $id
* @return bool
*/
public function onceUsingId($id)
{
$this->setUser($this->provider->retrieveById($id));
return $this->user instanceof UserInterface;
}
/**
* Create a remember me cookie for a given ID.
*
* @param mixed $id
* @return \Symfony\Component\HttpFoundation\Cookie
*/
protected function createRecaller($id)
{
return $this->getCookieJar()->forever($this->getRecallerName(), $id);
}
/**
* Log the user out of the application.
*
* @return void
*/
public function logout()
{
$user = $this->user();
// If we have an event dispatcher instance, we can fire off the logout event
// so any further processing can be done. This allows the developer to be
// listening for anytime a user signs out of this application manually.
$this->clearUserDataFromStorage();
if (isset($this->events))
{
$this->events->fire('auth.logout', array($user));
}
// Once we have fired the logout event we will clear the users out of memory
// so they are no longer available as the user is no longer considered as
// being signed into this application and should not be available here.
$this->user = null;
$this->loggedOut = true;
}
/**
* Remove the user data from the session and cookies.
*
* @return void
*/
protected function clearUserDataFromStorage()
{
$this->session->forget($this->getName());
$recaller = $this->getRecallerName();
$this->queuedCookies[] = $this->getCookieJar()->forget($recaller);
}
/**
* Get the cookies queued by the guard.
*
* @return array
*/
public function getQueuedCookies()
{
return $this->queuedCookies;
}
/**
* Get the cookie creator instance used by the guard.
*
* @return \Illuminate\Cookie\CookieJar
*/
public function getCookieJar()
{
if ( ! isset($this->cookie))
{
throw new \RuntimeException("Cookie jar has not been set.");
}
return $this->cookie;
}
/**
* Set the cookie creator instance used by the guard.
*
* @param \Illuminate\Cookie\CookieJar $cookie
* @return void
*/
public function setCookieJar(CookieJar $cookie)
{
$this->cookie = $cookie;
}
/**
* Get the event dispatcher instance.
*
* @return \Illuminate\Events\Dispatcher
*/
public function getDispatcher()
{
return $this->events;
}
/**
* Set the event dispatcher instance.
*
* @param \Illuminate\Events\Dispatcher
*/
public function setDispatcher(Dispatcher $events)
{
$this->events = $events;
}
/**
* Get the session store used by the guard.
*
* @return \Illuminate\Session\Store
*/
public function getSession()
{
return $this->session;
}
/**
* Get the user provider used by the guard.
*
* @return \Illuminate\Auth\UserProviderInterface
*/
public function getProvider()
{
return $this->provider;
}
/**
* Set the user provider used by the guard.
*
* @param \Illuminate\Auth\UserProviderInterface $provider
* @return void
*/
public function setProvider(UserProviderInterface $provider)
{
$this->provider = $provider;
}
/**
* Return the currently cached user of the application.
*
* @return \Illuminate\Auth\UserInterface|null
*/
public function getUser()
{
return $this->user;
}
/**
* Set the current user of the application.
*
* @param \Illuminate\Auth\UserInterface $user
* @return void
*/
public function setUser(UserInterface $user)
{
$this->user = $user;
$this->loggedOut = false;
}
/**
* Get the current request instance.
*
* @return \Symfony\Component\HttpFoundation\Request
*/
public function getRequest()
{
return $this->request ?: Request::createFromGlobals();
}
/**
* Set the current request instance.
*
* @param \Symfony\Component\HttpFoundation\Request
* @return \Illuminate\Auth\Guard
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* Get a unique identifier for the auth session value.
*
* @return string
*/
public function getName()
{
return 'login_'.md5(get_class($this));
}
/**
* Get the name of the cookie used to store the "recaller".
*
* @return string
*/
public function getRecallerName()
{
return 'remember_'.md5(get_class($this));
}
}

View File

@@ -0,0 +1,170 @@
<?php namespace Illuminate\Auth\Reminders;
use DateTime;
use Illuminate\Database\Connection;
class DatabaseReminderRepository implements ReminderRepositoryInterface {
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The reminder database table.
*
* @var string
*/
protected $table;
/**
* The hashing key.
*
* @var string
*/
protected $hashKey;
/**
* The number of seconds a reminder should last.
*
* @var int
*/
protected $expires;
/**
* Create a new reminder repository instance.
*
* @param \Illuminate\Database\Connection $connection
* @param string $table
* @param string $hashKey
* @param int $expires
* @return void
*/
public function __construct(Connection $connection, $table, $hashKey, $expires = 60)
{
$this->table = $table;
$this->hashKey = $hashKey;
$this->expires = $expires * 60;
$this->connection = $connection;
}
/**
* Create a new reminder record and token.
*
* @param \Illuminate\Auth\RemindableInterface $user
* @return string
*/
public function create(RemindableInterface $user)
{
$email = $user->getReminderEmail();
// We will create a new, random token for the user so that we can e-mail them
// a safe link to the password reset form. Then we will insert a record in
// the database so that we can verify the token within the actual reset.
$token = $this->createNewToken($user);
$this->getTable()->insert($this->getPayload($email, $token));
return $token;
}
/**
* Build the record payload for the table.
*
* @param string $email
* @param string $token
* @return array
*/
protected function getPayload($email, $token)
{
return array('email' => $email, 'token' => $token, 'created_at' => new DateTime);
}
/**
* Determine if a reminder record exists and is valid.
*
* @param \Illuminate\Auth\RemindableInterface $user
* @param string $token
* @return bool
*/
public function exists(RemindableInterface $user, $token)
{
$email = $user->getReminderEmail();
$reminder = $this->getTable()->where('email', $email)->where('token', $token)->first();
return $reminder and ! $this->reminderExpired($reminder);
}
/**
* Determine if the reminder has expired.
*
* @param StdClass $reminder
* @return bool
*/
protected function reminderExpired($reminder)
{
$createdPlusHour = strtotime($reminder->created_at) + $this->expires;
return $createdPlusHour < $this->getCurrentTime();
}
/**
* Get the current UNIX timestamp.
*
* @return int
*/
protected function getCurrentTime()
{
return time();
}
/**
* Delete a reminder record by token.
*
* @param string $token
* @return void
*/
public function delete($token)
{
$this->getTable()->where('token', $token)->delete();
}
/**
* Create a new token for the user.
*
* @param \Illuminate\Auth\RemindableInterface $user
* @return string
*/
public function createNewToken(RemindableInterface $user)
{
$email = $user->getReminderEmail();
$value = str_shuffle(sha1($email.spl_object_hash($this).microtime(true)));
return hash_hmac('sha1', $value, $this->hashKey);
}
/**
* Begin a new database query against the table.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function getTable()
{
return $this->connection->table($this->table);
}
/**
* Get the database connection instance.
*
* @return \Illuminate\Database\Connection
*/
public function getConnection()
{
return $this->connection;
}
}

View File

@@ -0,0 +1,262 @@
<?php namespace Illuminate\Auth\Reminders;
use Closure;
use Illuminate\Mail\Mailer;
use Illuminate\Routing\Redirector;
use Illuminate\Auth\UserProviderInterface;
class PasswordBroker {
/**
* The password reminder repository.
*
* @var \Illuminate\Auth\Reminders\ReminderRepositoryInterface $reminders
*/
protected $reminders;
/**
* The user provider implementation.
*
* @var \Illuminate\Auth\UserProviderInterface
*/
protected $users;
/**
* The redirector instance.
*
* @var \Illuminate\Routing\Redirector
*/
protected $redirector;
/**
* The mailer instance.
*
* @var \Illuminate\Mail\Mailer
*/
protected $mailer;
/**
* The view of the password reminder e-mail.
*
* @var string
*/
protected $reminderView;
/**
* Create a new password broker instance.
*
* @param \Illuminate\Auth\Reminders\ReminderRepositoryInterface $reminders
* @param \Illuminate\Auth\UserProviderInterface $users
* @param \Illuminate\Routing\Redirector $redirect
* @param \Illuminate\Mail\Mailer $mailer
* @param string $reminderView
* @return void
*/
public function __construct(ReminderRepositoryInterface $reminders,
UserProviderInterface $users,
Redirector $redirect,
Mailer $mailer,
$reminderView)
{
$this->users = $users;
$this->mailer = $mailer;
$this->redirect = $redirect;
$this->reminders = $reminders;
$this->reminderView = $reminderView;
}
/**
* Send a password reminder to a user.
*
* @param array $credentials
* @param Closure $callback
* @return \Illuminate\Http\RedirectResponse
*/
public function remind(array $credentials, Closure $callback = null)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user))
{
return $this->makeErrorRedirect('user');
}
// Once we have the reminder token, we are ready to send a message out to the
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$token = $this->reminders->create($user);
$this->sendReminder($user, $token, $callback);
return $this->redirect->refresh()->with('success', true);
}
/**
* Send the password reminder e-mail.
*
* @param \Illuminate\Auth\Reminders\RemindableInterface $user
* @param string $token
* @param Closure $callback
* @return void
*/
public function sendReminder(RemindableInterface $user, $token, Closure $callback = null)
{
// We will use the reminder view that was given to the broker to display the
// password reminder e-mail. We'll pass a "token" variable into the views
// so that it may be displayed for an user to click for password reset.
$view = $this->reminderView;
return $this->mailer->send($view, compact('token', 'user'), function($m) use ($user, $callback)
{
$m->to($user->getReminderEmail());
if ( ! is_null($callback)) call_user_func($callback, $m, $user);
});
}
/**
* Reset the password for the given token.
*
* @param array $credentials
* @param Closure $callback
* @return mixed
*/
public function reset(array $credentials, Closure $callback)
{
// If the responses from the validate method is not a user instance, we will
// assume that it is a redirect and simply return it from this method and
// the user is properly redirected having an error message on the post.
$user = $this->validateReset($credentials);
if ( ! $user instanceof RemindableInterface)
{
return $user;
}
$pass = $this->getPassword();
// Once we have called this callback, we will remove this token row from the
// table and return the response from this callback so the user gets sent
// to the destination given by the developers from the callback return.
$response = call_user_func($callback, $user, $pass);
$this->reminders->delete($this->getToken());
return $response;
}
/**
* Validate a password reset for the given credentials.
*
* @param array $credenitals
* @return \Illuminate\Auth\RemindableInterface
*/
protected function validateReset(array $credentials)
{
if (is_null($user = $this->getUser($credentials)))
{
return $this->makeErrorRedirect('user');
}
if ( ! $this->validNewPasswords())
{
return $this->makeErrorRedirect('password');
}
if ( ! $this->reminders->exists($user, $this->getToken()))
{
return $this->makeErrorRedirect('token');
}
return $user;
}
/**
* Determine if the passwords match for the request.
*
* @return bool
*/
protected function validNewPasswords()
{
$password = $this->getPassword();
$confirm = $this->getConfirmedPassword();
return $password and strlen($password) >= 6 and $password == $confirm;
}
/**
* Make an error redirect response.
*
* @param string $reason
* @return \Illuminate\Http\RedirectResponse
*/
protected function makeErrorRedirect($reason = '')
{
if ($reason != '') $reason = 'reminders.'.$reason;
return $this->redirect->refresh()->with('error', true)->with('reason', $reason);
}
/**
* Get the user for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Auth\Reminders\RemindableInterface
*/
public function getUser(array $credentials)
{
$user = $this->users->retrieveByCredentials($credentials);
if ($user and ! $user instanceof RemindableInterface)
{
throw new \UnexpectedValueException("User must implement Remindable interface.");
}
return $user;
}
/**
* Get the current request object.
*
* @return \Illuminate\Http\Request
*/
protected function getRequest()
{
return $this->redirect->getUrlGenerator()->getRequest();
}
/**
* Get the reset token for the current request.
*
* @return string
*/
protected function getToken()
{
return $this->getRequest()->input('token');
}
/**
* Get the password for the current request.
*
* @return string
*/
protected function getPassword()
{
return $this->getRequest()->input('password');
}
/**
* Get the confirmed password.
*
* @return string
*/
protected function getConfirmedPassword()
{
return $this->getRequest()->input('password_confirmation');
}
}

View File

@@ -0,0 +1,12 @@
<?php namespace Illuminate\Auth\Reminders;
interface RemindableInterface {
/**
* Get the e-mail address where password reminders are sent.
*
* @return string
*/
public function getReminderEmail();
}

View File

@@ -0,0 +1,30 @@
<?php namespace Illuminate\Auth\Reminders;
interface ReminderRepositoryInterface {
/**
* Create a new reminder record and token.
*
* @param \Illuminate\Auth\RemindableInterface $user
* @return string
*/
public function create(RemindableInterface $user);
/**
* Determine if a reminder record exists and is valid.
*
* @param \Illuminate\Auth\RemindableInterface $user
* @param string $token
* @return bool
*/
public function exists(RemindableInterface $user, $token);
/**
* Delete a reminder record by token.
*
* @param string $token
* @return void
*/
public function delete($token);
}

View File

@@ -0,0 +1,112 @@
<?php namespace Illuminate\Auth\Reminders;
use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\Console\MakeRemindersCommand;
use Illuminate\Auth\Reminders\DatabaseReminderRepository as DbRepository;
class ReminderServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerPasswordBroker();
$this->registerReminderRepository();
$this->registerCommands();
}
/**
* Register the password broker instance.
*
* @return void
*/
protected function registerPasswordBroker()
{
$this->app['auth.reminder'] = $this->app->share(function($app)
{
// The reminder repository is responsible for storing the user e-mail addresses
// and password reset tokens. It will be used to verify the tokens are valid
// for the given e-mail addresses. We will resolve an implementation here.
$reminders = $app['auth.reminder.repository'];
$users = $app['auth']->driver()->getProvider();
$view = $app['config']['auth.reminder.email'];
// The password broker uses the reminder repository to validate tokens and send
// reminder e-mails, as well as validating that password reset process as an
// aggregate service of sorts providing a convenient interface for resets.
return new PasswordBroker(
$reminders, $users, $app['redirect'], $app['mailer'], $view
);
});
}
/**
* Register the reminder repository implementation.
*
* @return void
*/
protected function registerReminderRepository()
{
$app = $this->app;
$app['auth.reminder.repository'] = $app->share(function($app)
{
$connection = $app['db']->connection();
// The database reminder repository is an implementation of the reminder repo
// interface, and is responsible for the actual storing of auth tokens and
// their e-mail addresses. We will inject this table and hash key to it.
$table = $app['config']['auth.reminder.table'];
$key = $app['config']['app.key'];
$expire = $app['config']->get('auth.reminder.expire', 60);
return new DbRepository($connection, $table, $key, $expire);
});
}
/**
* Register the auth related console commands.
*
* @return void
*/
protected function registerCommands()
{
$app = $this->app;
$app['command.auth.reminders'] = $app->share(function($app)
{
return new MakeRemindersCommand($app['files']);
});
$this->commands('command.auth.reminders');
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array('auth.reminder', 'auth.reminder.repository', 'command.auth.reminders');
}
}

View File

@@ -0,0 +1,19 @@
<?php namespace Illuminate\Auth;
interface UserInterface {
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier();
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword();
}

View File

@@ -0,0 +1,30 @@
<?php namespace Illuminate\Auth;
interface UserProviderInterface {
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @return \Illuminate\Auth\UserInterface|null
*/
public function retrieveById($identifier);
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Auth\UserInterface|null
*/
public function retrieveByCredentials(array $credentials);
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Auth\UserInterface $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserInterface $user, array $credentials);
}

View File

@@ -0,0 +1,35 @@
{
"name": "illuminate/auth",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"illuminate/cookie": "4.0.x",
"illuminate/encryption": "4.0.x",
"illuminate/events": "4.0.x",
"illuminate/hashing": "4.0.x",
"illuminate/http": "4.0.x",
"illuminate/session": "4.0.x",
"illuminate/support": "4.0.x"
},
"require-dev": {
"illuminate/console": "4.0.x",
"illuminate/routing": "4.0.x",
"phpunit/phpunit": "3.7.*"
},
"autoload": {
"psr-0": {"Illuminate\\Auth": ""}
},
"target-dir": "Illuminate/Auth",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,139 @@
<?php namespace Illuminate\Cache;
class ApcStore implements StoreInterface {
/**
* The APC wrapper instance.
*
* @var \Illuminate\Cache\ApcWrapper
*/
protected $apc;
/**
* A string that should be prepended to keys.
*
* @var string
*/
protected $prefix;
/**
* Create a new APC store.
*
* @param \Illuminate\Cache\ApcWrapper $apc
* @param string $prefix
* @return void
*/
public function __construct(ApcWrapper $apc, $prefix = '')
{
$this->apc = $apc;
$this->prefix = $prefix;
}
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
$value = $this->apc->get($this->prefix.$key);
if ($value !== false)
{
return $value;
}
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return array|bool
*/
public function put($key, $value, $minutes)
{
$this->apc->put($this->prefix.$key, $value, $minutes * 60);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return array|bool
*/
public function increment($key, $value = 1)
{
return $this->apc->increment($this->prefix.$key, $value);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return array|bool
*/
public function decrement($key, $value = 1)
{
return $this->apc->decrement($this->prefix.$key, $value);
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return array|bool
*/
public function forever($key, $value)
{
return $this->put($key, $value, 0);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return array|bool
*/
public function forget($key)
{
$this->apc->delete($this->prefix.$key);
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->apc->flush();
}
/**
* Begin executing a new section operation.
*
* @param string $name
* @return \Illuminate\Cache\Section
*/
public function section($name)
{
return new Section($this, $name);
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
}

View File

@@ -0,0 +1,91 @@
<?php namespace Illuminate\Cache;
class ApcWrapper {
/**
* Indicates if APCu is supported.
*
* @var bool
*/
protected $apcu = false;
/**
* Create a new APC wrapper instance.
*
* @return void
*/
public function __construct()
{
$this->apcu = function_exists('apcu_fetch');
}
/**
* Get an item from the cache.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
return $this->apcu ? apcu_fetch($key) : apc_fetch($key);
}
/**
* Store an item in the cache.
*
* @param string $key
* @param mixed $value
* @param int $seconds
* @return array|bool
*/
public function put($key, $value, $seconds)
{
return $this->apcu ? apcu_store($key, $value, $seconds) : apc_store($key, $value, $seconds);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return array|bool
*/
public function increment($key, $value)
{
return $this->apcu ? apcu_inc($key, $value) : apc_inc($key, $value);
}
/**
* Decremenet the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return array|bool
*/
public function decrement($key, $value)
{
return $this->apcu ? apcu_dec($key, $value) : apc_dec($key, $value);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return array|bool
*/
public function delete($key)
{
return $this->apcu ? apcu_delete($key) : apc_delete($key);
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->apcu ? apcu_clear_cache() : apc_clear_cache('user');
}
}

View File

@@ -0,0 +1,121 @@
<?php namespace Illuminate\Cache;
class ArrayStore implements StoreInterface {
/**
* The array of stored values.
*
* @var array
*/
protected $storage = array();
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
if (array_key_exists($key, $this->storage))
{
return $this->storage[$key];
}
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$this->storage[$key] = $value;
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
$this->storage[$key] = $this->storage[$key] + $value;
return $this->storage[$key];
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
$this->storage[$key] = $this->storage[$key] - $value;
return $this->storage[$key];
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
return $this->put($key, $value, 0);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
unset($this->storage[$key]);
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->storage = array();
}
/**
* Begin executing a new section operation.
*
* @param string $name
* @return \Illuminate\Cache\Section
*/
public function section($name)
{
return new Section($this, $name);
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return '';
}
}

View File

@@ -0,0 +1,139 @@
<?php namespace Illuminate\Cache;
use Illuminate\Support\Manager;
class CacheManager extends Manager {
/**
* Create an instance of the APC cache driver.
*
* @return \Illuminate\Cache\ApcStore
*/
protected function createApcDriver()
{
return $this->repository(new ApcStore(new ApcWrapper, $this->getPrefix()));
}
/**
* Create an instance of the array cache driver.
*
* @return \Illuminate\Cache\ArrayStore
*/
protected function createArrayDriver()
{
return $this->repository(new ArrayStore);
}
/**
* Create an instance of the file cache driver.
*
* @return \Illuminate\Cache\FileStore
*/
protected function createFileDriver()
{
$path = $this->app['config']['cache.path'];
return $this->repository(new FileStore($this->app['files'], $path));
}
/**
* Create an instance of the Memcached cache driver.
*
* @return \Illuminate\Cache\MemcachedStore
*/
protected function createMemcachedDriver()
{
$servers = $this->app['config']['cache.memcached'];
$memcached = $this->app['memcached.connector']->connect($servers);
return $this->repository(new MemcachedStore($memcached, $this->getPrefix()));
}
/**
* Create an instance of the WinCache cache driver.
*
* @return \Illuminate\Cache\WinCacheStore
*/
protected function createWincacheDriver()
{
return $this->repository(new WinCacheStore($this->getPrefix()));
}
/**
* Create an instance of the Redis cache driver.
*
* @return \Illuminate\Cache\RedisStore
*/
protected function createRedisDriver()
{
$redis = $this->app['redis'];
return $this->repository(new RedisStore($redis, $this->getPrefix()));
}
/**
* Create an instance of the database cache driver.
*
* @return \Illuminate\Cache\DatabaseStore
*/
protected function createDatabaseDriver()
{
$connection = $this->getDatabaseConnection();
$encrypter = $this->app['encrypter'];
// We allow the developer to specify which connection and table should be used
// to store the cached items. We also need to grab a prefix in case a table
// is being used by multiple applications although this is very unlikely.
$table = $this->app['config']['cache.table'];
$prefix = $this->getPrefix();
return $this->repository(new DatabaseStore($connection, $encrypter, $table, $prefix));
}
/**
* Get the database connection for the database driver.
*
* @return \Illuminate\Database\Connection
*/
protected function getDatabaseConnection()
{
$connection = $this->app['config']['cache.connection'];
return $this->app['db']->connection($connection);
}
/**
* Get the cache "prefix" value.
*
* @return string
*/
public function getPrefix()
{
return $this->app['config']['cache.prefix'];
}
/**
* Create a new cache repository with the given implementation.
*
* @param \Illuminate\Cache\StoreInterface $store
* @return \Illuminate\Cache\Repository
*/
protected function repository(StoreInterface $store)
{
return new Repository($store);
}
/**
* Get the default cache driver name.
*
* @return string
*/
protected function getDefaultDriver()
{
return $this->app['config']['cache.driver'];
}
}

View File

@@ -0,0 +1,59 @@
<?php namespace Illuminate\Cache;
use Illuminate\Support\ServiceProvider;
class CacheServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app['cache'] = $this->app->share(function($app)
{
return new CacheManager($app);
});
$this->app['memcached.connector'] = $this->app->share(function()
{
return new MemcachedConnector;
});
$this->registerCommands();
}
/**
* Register the cache related console commands.
*
* @return void
*/
public function registerCommands()
{
$this->app['command.cache.clear'] = $this->app->share(function($app)
{
return new Console\ClearCommand($app['cache'], $app['files']);
});
$this->commands('command.cache.clear');
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array('cache', 'memcached.connector', 'command.cache.clear');
}
}

View File

@@ -0,0 +1,66 @@
<?php namespace Illuminate\Cache\Console;
use Illuminate\Console\Command;
use Illuminate\Cache\CacheManager;
use Illuminate\Filesystem\Filesystem;
class ClearCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'cache:clear';
/**
* The console command description.
*
* @var string
*/
protected $description = "Flush the application cache";
/**
* The cache manager instance.
*
* @var \Illuminate\Cache\CacheManager
*/
protected $cache;
/**
* The file system instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* Create a new cache clear command instance.
*
* @param \Illuminate\Cache\CacheManager $cache
* @param \Illuminate\Filesystem\Filesystem $files
* @return void
*/
public function __construct(CacheManager $cache, Filesystem $files)
{
parent::__construct();
$this->cache = $cache;
$this->files = $files;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$this->cache->flush();
$this->files->delete($this->laravel['config']['app.manifest'].'/services.json');
$this->info('Application cache cleared!');
}
}

View File

@@ -0,0 +1,217 @@
<?php namespace Illuminate\Cache;
use Illuminate\Database\Connection;
use Illuminate\Encryption\Encrypter;
class DatabaseStore implements StoreInterface {
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The encrypter instance.
*
* @var \Illuminate\Encryption\Encrypter
*/
protected $encrypter;
/**
* The name of the cache table.
*
* @var string
*/
protected $table;
/**
* A string that should be prepended to keys.
*
* @var string
*/
protected $prefix;
/**
* Create a new database store.
*
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Encryption\Encrypter $encrypter
* @param string $table
* @param string $prefix
* @return void
*/
public function __construct(Connection $connection, Encrypter $encrypter, $table, $prefix = '')
{
$this->table = $table;
$this->prefix = $prefix;
$this->encrypter = $encrypter;
$this->connection = $connection;
}
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
$prefixed = $this->prefix.$key;
$cache = $this->table()->where('key', $prefixed)->first();
// If we have a cache record we will check the expiration time against current
// time on the system and see if the record has expired. If it has, we will
// remove the records from the database table so it isn't returned again.
if ( ! is_null($cache))
{
if (is_array($cache)) $cache = (object) $cache;
if (time() >= $cache->expiration)
{
return $this->forget($key);
}
return $this->encrypter->decrypt($cache->value);
}
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$key = $this->prefix.$key;
// All of the cached values in the database are encrypted in case this is used
// as a session data store by the consumer. We'll also calculate the expire
// time and place that on the table so we will check it on our retrieval.
$value = $this->encrypter->encrypt($value);
$expiration = $this->getTime() + ($minutes * 60);
try
{
$this->table()->insert(compact('key', 'value', 'expiration'));
}
catch (\Exception $e)
{
$this->table()->where('key', $key)->update(compact('value', 'expiration'));
}
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
throw new \LogicException("Increment operations not supported by this driver.");
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
throw new \LogicException("Increment operations not supported by this driver.");
}
/**
* Get the current system time.
*
* @return int
*/
protected function getTime()
{
return time();
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
return $this->put($key, $value, 5256000);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
$this->table()->where('key', $this->prefix.$key)->delete();
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->table()->delete();
}
/**
* Get a query builder for the cache table.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function table()
{
return $this->connection->table($this->table);
}
/**
* Get the underlying database connection.
*
* @return \Illuminate\Database\Connection
*/
public function getConnection()
{
return $this->connection;
}
/**
* Get the encrypter instance.
*
* @return \Illuminate\Encryption\Encrypter
*/
public function getEncrypter()
{
return $this->encrypter;
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
}

View File

@@ -0,0 +1,212 @@
<?php namespace Illuminate\Cache;
use Illuminate\Filesystem\Filesystem;
class FileStore implements StoreInterface {
/**
* The Illuminate Filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The file cache directory
*
* @var string
*/
protected $directory;
/**
* Create a new file cache store instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $directory
* @return void
*/
public function __construct(Filesystem $files, $directory)
{
$this->files = $files;
$this->directory = $directory;
}
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
$path = $this->path($key);
// If the file doesn't exists, we obviously can't return the cache so we will
// just return null. Otherwise, we'll get the contents of the file and get
// the expiration UNIX timestamps from the start of the file's contents.
if ( ! $this->files->exists($path))
{
return null;
}
$expire = substr($contents = $this->files->get($path), 0, 10);
// If the current time is greater than expiration timestamps we will delete
// the file and return null. This helps clean up the old files and keeps
// this directory much cleaner for us as old files aren't hanging out.
if (time() >= $expire)
{
return $this->forget($key);
}
return unserialize(substr($contents, 10));
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$value = $this->expiration($minutes).serialize($value);
$this->createCacheDirectory($path = $this->path($key));
$this->files->put($path, $value);
}
/**
* Create the file cache directory if necessary.
*
* @param string $path
* @return void
*/
protected function createCacheDirectory($path)
{
if ( ! $this->files->isDirectory($directory = dirname($path)))
{
$this->files->makeDirectory($directory, 0777, true);
}
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
throw new \LogicException("Increment operations not supported by this driver.");
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
throw new \LogicException("Increment operations not supported by this driver.");
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
return $this->put($key, $value, 0);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
$this->files->delete($this->path($key));
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
foreach ($this->files->directories($this->directory) as $directory)
{
$this->files->deleteDirectory($directory);
}
}
/**
* Get the full path for the given cache key.
*
* @param string $key
* @return string
*/
protected function path($key)
{
$parts = array_slice(str_split($hash = md5($key), 2), 0, 2);
return $this->directory.'/'.join('/', $parts).'/'.$hash;
}
/**
* Get the expiration time based on the given minutes.
*
* @param int $minutes
* @return int
*/
protected function expiration($minutes)
{
if ($minutes === 0) return 9999999999;
return time() + ($minutes * 60);
}
/**
* Get the Filesystem instance.
*
* @return \Illuminate\Filesystem\Filesystem
*/
public function getFilesystem()
{
return $this->files;
}
/**
* Get the working directory of the cache.
*
* @return string
*/
public function getDirectory()
{
return $this->directory;
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return '';
}
}

View File

@@ -0,0 +1,43 @@
<?php namespace Illuminate\Cache;
use Memcached;
class MemcachedConnector {
/**
* Create a new Memcached connection.
*
* @param array $servers
* @return \Memcached
*/
public function connect(array $servers)
{
$memcached = $this->getMemcached();
// For each server in the array, we'll just extract the configuration and add
// the server to the Memcached connection. Once we have added all of these
// servers we'll verify the connection is successful and return it back.
foreach ($servers as $server)
{
$memcached->addServer($server['host'], $server['port'], $server['weight']);
}
if ($memcached->getVersion() === false)
{
throw new \RuntimeException("Could not establish Memcached connection.");
}
return $memcached;
}
/**
* Get a new Memcached instance.
*
* @return \Memcached
*/
protected function getMemcached()
{
return new Memcached;
}
}

View File

@@ -0,0 +1,151 @@
<?php namespace Illuminate\Cache;
use Memcached;
class MemcachedStore implements StoreInterface {
/**
* The Memcached instance.
*
* @var \Memcached
*/
protected $memcached;
/**
* A string that should be prepended to keys.
*
* @var string
*/
protected $prefix;
/**
* Create a new Memcached store.
*
* @param \Memcached $memcached
* @param string $prefix
* @return void
*/
public function __construct(Memcached $memcached, $prefix = '')
{
$this->memcached = $memcached;
$this->prefix = strlen($prefix) > 0 ? $prefix.':' : '';
}
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
$value = $this->memcached->get($this->prefix.$key);
if ($this->memcached->getResultCode() == 0)
{
return $value;
}
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$this->memcached->set($this->prefix.$key, $value, $minutes * 60);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
return $this->memcached->increment($this->prefix.$key, $value);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
return $this->memcached->decrement($this->prefix.$key, $value);
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
return $this->put($key, $value, 0);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
$this->memcached->delete($this->prefix.$key);
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->memcached->flush();
}
/**
* Begin executing a new section operation.
*
* @param string $name
* @return \Illuminate\Cache\Section
*/
public function section($name)
{
return new Section($this, $name);
}
/**
* Get the underlying Memcached connection.
*
* @return \Memcached
*/
public function getMemcached()
{
return $this->memcached;
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
}

View File

@@ -0,0 +1,84 @@
<?php namespace Illuminate\Cache;
class RedisSection extends Section {
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
$this->store->connection()->lpush($this->foreverKey(), $key);
$this->store->forever($this->sectionItemKey($key), $value);
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->deleteForeverKeys();
$this->store->connection()->del($this->foreverKey());
$this->store->increment($this->sectionKey());
}
/**
* Delete all of the keys that have been stored forever.
*
* @return void
*/
protected function deleteForeverKeys()
{
$forever = $this->getForeverKeys();
if (count($forever) > 0)
{
call_user_func_array(array($this->store->connection(), 'del'), $forever);
}
}
/**
* Get the keys that have been stored forever.
*
* @return array
*/
protected function getForeverKeys()
{
$me = $this;
return array_map(function($x) use ($me)
{
return $me->getPrefix().$me->sectionItemKey($x);
}, array_unique($this->store->connection()->lrange($this->foreverKey(), 0, -1)));
}
/**
* Get the forever list identifier.
*
* @return string
*/
protected function foreverKey()
{
return $this->sectionKey().':forever';
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->store->getPrefix();
}
}

View File

@@ -0,0 +1,185 @@
<?php namespace Illuminate\Cache;
use Illuminate\Redis\Database as Redis;
class RedisStore implements StoreInterface {
/**
* The Redis database connection.
*
* @var \Illuminate\Redis\Database
*/
protected $redis;
/**
* A string that should be prepended to keys.
*
* @var string
*/
protected $prefix;
/**
* The Redis connection that should be used.
*
* @var string
*/
protected $connection;
/**
* Create a new Redis store.
*
* @param \Illuminate\Redis\Database $redis
* @param string $prefix
* @param string $connection
* @return void
*/
public function __construct(Redis $redis, $prefix = '', $connection = 'default')
{
$this->redis = $redis;
$this->prefix = $prefix.':';
$this->connection = $connection;
}
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
if ( ! is_null($value = $this->connection()->get($this->prefix.$key)))
{
return is_numeric($value) ? $value : unserialize($value);
}
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
$value = is_numeric($value) ? $value : serialize($value);
$this->connection()->set($this->prefix.$key, $value);
$this->connection()->expire($this->prefix.$key, $minutes * 60);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
return $this->connection()->incrby($this->prefix.$key, $value);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
return $this->connection()->decrby($this->prefix.$key, $value);
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
$value = is_numeric($value) ? $value : serialize($value);
$this->connection()->set($this->prefix.$key, $value);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
$this->connection()->del($this->prefix.$key);
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->connection()->flushdb();
}
/**
* Begin executing a new section operation.
*
* @param string $name
* @return \Illuminate\Cache\Section
*/
public function section($name)
{
return new RedisSection($this, $name);
}
/**
* Get the Redis connection instance.
*
* @return \Predis\Connection\SingleConnectionInterface
*/
public function connection()
{
return $this->redis->connection($this->connection);
}
/**
* Set the connection name to be used.
*
* @param string $connection
* @return void
*/
public function setConnection($connection)
{
$this->connection = $connection;
}
/**
* Get the Redis database instance.
*
* @return \Illuminate\Redis\Database
*/
public function getRedis()
{
return $this->redis;
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
}

View File

@@ -0,0 +1,209 @@
<?php namespace Illuminate\Cache; use Closure, ArrayAccess;
class Repository implements ArrayAccess {
/**
* The cache store implementation.
*
* @var \Illuminate\Cache\StoreInterface
*/
protected $store;
/**
* The default number of minutes to store items.
*
* @var int
*/
protected $default = 60;
/**
* Create a new cache repository instance.
*
* @param \Illuminate\Cache\StoreInterface $store
*/
public function __construct(StoreInterface $store)
{
$this->store = $store;
}
/**
* 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 by key.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
$value = $this->store->get($key);
return ! is_null($value) ? $value : value($default);
}
/**
* Store an item in the cache if the key does not exist.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function add($key, $value, $minutes)
{
if (is_null($this->get($key))) $this->put($key, $value, $minutes);
}
/**
* Get an item from the cache, or store the default value.
*
* @param string $key
* @param int $minutes
* @param Closure $callback
* @return mixed
*/
public function remember($key, $minutes, Closure $callback)
{
// If the item exists in the cache we will just return this immediately
// otherwise we will execute the given Closure and cache the result
// of that execution for the given number of minutes in storage.
if ($this->has($key)) return $this->get($key);
$this->put($key, $value = $callback(), $minutes);
return $value;
}
/**
* Get an item from the cache, or store the default value forever.
*
* @param string $key
* @param Closure $callback
* @return mixed
*/
public function sear($key, Closure $callback)
{
return $this->rememberForever($key, $callback);
}
/**
* Get an item from the cache, or store the default value forever.
*
* @param string $key
* @param Closure $callback
* @return mixed
*/
public function rememberForever($key, Closure $callback)
{
// If the item exists in the cache we will just return this immediately
// otherwise we will execute the given Closure and cache the result
// of that execution for the given number of minutes. It's easy.
if ( ! is_null($value = $this->get($key)))
{
return $value;
}
$this->forever($key, $value = $callback());
return $value;
}
/**
* Get the default cache time.
*
* @return int
*/
public function getDefaultCacheTime()
{
return $this->default;
}
/**
* Set the default cache time in minutes.
*
* @param int $minutes
* @return void
*/
public function setDefaultCacheTime($minutes)
{
$this->default = $minutes;
}
/**
* Get the cache store implementation.
*
* @return \Illuminate\Cache\StoreInterface
*/
public function getStore()
{
return $this->store;
}
/**
* Determine if a cached value exists.
*
* @param string $key
* @return bool
*/
public function offsetExists($key)
{
return $this->has($key);
}
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->get($key);
}
/**
* Store an item in the cache for the default time.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function offsetSet($key, $value)
{
$this->put($key, $value, $this->default);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function offsetUnset($key)
{
return $this->forget($key);
}
/**
* Dynamically pass missing methods to the store.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return call_user_func_array(array($this->store, $method), $parameters);
}
}

View File

@@ -0,0 +1,230 @@
<?php namespace Illuminate\Cache;
use Closure;
class Section {
/**
* The cache store implementation.
*
* @var \Illuminate\Cache\StoreInterface
*/
protected $store;
/**
* The section name.
*
* @var string
*/
protected $name;
/**
* Create a new section instance.
*
* @param \Illuminate\Cache\StoreInterface $store
* @param string $name
* @return void
*/
public function __construct(StoreInterface $store, $name)
{
$this->name = $name;
$this->store = $store;
}
/**
* 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 by key.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
$value = $this->store->get($this->sectionItemKey($key));
return ! is_null($value) ? $value : value($default);
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
return $this->store->put($this->sectionItemKey($key), $value, $minutes);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
$this->store->increment($this->sectionItemKey($key), $value);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
$this->store->decrement($this->sectionItemKey($key), $value);
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
$this->store->forever($this->sectionItemKey($key), $value);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
$this->store->forget($this->sectionItemKey($key));
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
$this->reset();
}
/**
* Get an item from the cache, or store the default value.
*
* @param string $key
* @param int $minutes
* @param Closure $callback
* @return mixed
*/
public function remember($key, $minutes, Closure $callback)
{
// If the item exists in the cache we will just return this immediately
// otherwise we will execute the given Closure and cache the result
// of that execution for the given number of minutes in storage.
if ($this->has($key)) return $this->get($key);
$this->put($key, $value = $callback(), $minutes);
return $value;
}
/**
* Get an item from the cache, or store the default value forever.
*
* @param string $key
* @param Closure $callback
* @return mixed
*/
public function sear($key, Closure $callback)
{
return $this->rememberForever($key, $callback);
}
/**
* Get an item from the cache, or store the default value forever.
*
* @param string $key
* @param Closure $callback
* @return mixed
*/
public function rememberForever($key, Closure $callback)
{
// If the item exists in the cache we will just return this immediately
// otherwise we will execute the given Closure and cache the result
// of that execution for the given number of minutes. It's easy.
if ($this->has($key)) return $this->get($key);
$this->forever($key, $value = $callback());
return $value;
}
/**
* Get a fully qualfied section item key.
*
* @param string $key
* @return string
*/
public function sectionItemKey($key)
{
return $this->name.':'.$this->sectionId().':'.$key;
}
/**
* Reset the section, returning a new section identifier
*
* @return string
*/
protected function reset()
{
$this->store->forever($this->sectionKey(), $id = uniqid());
return $id;
}
/**
* Get the unique section identifier.
*
* @return string
*/
protected function sectionId()
{
$id = $this->store->get($this->sectionKey());
if (is_null($id))
{
$id = $this->reset();
}
return $id;
}
/**
* Get the section identifier key.
*
* @return string
*/
protected function sectionKey()
{
return 'section:'.$this->name.':key';
}
}

View File

@@ -0,0 +1,72 @@
<?php namespace Illuminate\Cache;
interface StoreInterface {
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key);
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes);
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1);
/**
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1);
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value);
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key);
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush();
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix();
}

View File

@@ -0,0 +1,130 @@
<?php namespace Illuminate\Cache;
class WinCacheStore implements StoreInterface {
/**
* A string that should be prepended to keys.
*
* @var string
*/
protected $prefix;
/**
* Create a new WinCache store.
*
* @param string $prefix
* @return void
*/
public function __construct($prefix = '')
{
$this->prefix = $prefix;
}
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key)
{
$value = wincache_ucache_get($this->prefix.$key);
if ($value !== false)
{
return $value;
}
}
/**
* Store an item in the cache for a given number of minutes.
*
* @param string $key
* @param mixed $value
* @param int $minutes
* @return void
*/
public function put($key, $value, $minutes)
{
wincache_ucache_add($this->prefix.$key, $value, $minutes * 60);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function increment($key, $value = 1)
{
return wincache_ucache_inc($this->prefix.$key, $value);
}
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function decrement($key, $value = 1)
{
return wincache_ucache_dec($this->prefix.$key, $value);
}
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function forever($key, $value)
{
return $this->put($key, $value, 0);
}
/**
* Remove an item from the cache.
*
* @param string $key
* @return void
*/
public function forget($key)
{
wincache_ucache_delete($this->prefix.$key);
}
/**
* Remove all items from the cache.
*
* @return void
*/
public function flush()
{
wincache_ucache_clear();
}
/**
* Begin executing a new section operation.
*
* @param string $name
* @return \Illuminate\Cache\Section
*/
public function section($name)
{
return new Section($this, $name);
}
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
}

View File

@@ -0,0 +1,31 @@
{
"name": "illuminate/cache",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"illuminate/support": "4.0.x"
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
"illuminate/database": "4.0.x",
"illuminate/encryption": "4.0.x",
"illuminate/filesystem": "4.0.x",
"illuminate/redis": "4.0.x"
},
"autoload": {
"psr-0": {"Illuminate\\Cache": ""}
},
"target-dir": "Illuminate/Cache",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,255 @@
<?php namespace Illuminate\Config;
use Illuminate\Filesystem\Filesystem;
class FileLoader implements LoaderInterface {
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The default configuration path.
*
* @var string
*/
protected $defaultPath;
/**
* All of the named path hints.
*
* @var array
*/
protected $hints = array();
/**
* A cache of whether namespaces and groups exists.
*
* @var array
*/
protected $exists = array();
/**
* Create a new file configuration loader.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $defaultPath
* @return void
*/
public function __construct(Filesystem $files, $defaultPath)
{
$this->files = $files;
$this->defaultPath = $defaultPath;
}
/**
* Load the given configuration group.
*
* @param string $environment
* @param string $group
* @param string $namespace
* @return array
*/
public function load($environment, $group, $namespace = null)
{
$items = array();
// First we'll get the root configuration path for the environment which is
// where all of the configuration files live for that namespace, as well
// as any environment folders with their specific configuration items.
$path = $this->getPath($namespace);
if (is_null($path))
{
return $items;
}
// First we'll get the main configuration file for the groups. Once we have
// that we can check for any environment specific files, which will get
// merged on top of the main arrays to make the environments cascade.
$file = "{$path}/{$group}.php";
if ($this->files->exists($file))
{
$items = $this->files->getRequire($file);
}
// Finally we're ready to check for the environment specific configuration
// file which will be merged on top of the main arrays so that they get
// precedence over them if we are currently in an environments setup.
$file = "{$path}/{$environment}/{$group}.php";
if ($this->files->exists($file))
{
$items = $this->mergeEnvironment($items, $file);
}
return $items;
}
/**
* Merge the items in the given file into the items.
*
* @param array $items
* @param string $file
* @return array
*/
protected function mergeEnvironment(array $items, $file)
{
return array_replace_recursive($items, $this->files->getRequire($file));
}
/**
* Determine if the given group exists.
*
* @param string $group
* @param string $namespace
* @return bool
*/
public function exists($group, $namespace = null)
{
$key = $group.$namespace;
// We'll first check to see if we have determined if this namespace and
// group combination have been checked before. If they have, we will
// just return the cached result so we don't have to hit the disk.
if (isset($this->exists[$key]))
{
return $this->exists[$key];
}
$path = $this->getPath($namespace);
// To check if a group exists, we will simply get the path based on the
// namespace, and then check to see if this files exists within that
// namespace. False is returned if no path exists for a namespace.
if (is_null($path))
{
return $this->exists[$key] = false;
}
$file = "{$path}/{$group}.php";
// Finally, we can simply check if this file exists. We will also cache
// the value in an array so we don't have to go through this process
// again on subsequent checks for the existing of the config file.
$exists = $this->files->exists($file);
return $this->exists[$key] = $exists;
}
/**
* Apply any cascades to an array of package options.
*
* @param string $env
* @param string $package
* @param string $group
* @param array $items
* @return array
*/
public function cascadePackage($env, $package, $group, $items)
{
// First we will look for a configuration file in the packages configuration
// folder. If it exists, we will load it and merge it with these original
// options so that we will easily "cascade" a package's configurations.
$file = "packages/{$package}/{$group}.php";
if ($this->files->exists($path = $this->defaultPath.'/'.$file))
{
$items = array_merge($items, $this->getRequire($path));
}
// Once we have merged the regular package configuration we need to look for
// an environment specific configuration file. If one exists, we will get
// the contents and merge them on top of this array of options we have.
$path = $this->getPackagePath($env, $package, $group);
if ($this->files->exists($path))
{
$items = array_merge($items, $this->getRequire($path));
}
return $items;
}
/**
* Get the package path for an environment and group.
*
* @param string $env
* @param string $package
* @param string $group
* @return string
*/
protected function getPackagePath($env, $package, $group)
{
$file = "packages/{$package}/{$env}/{$group}.php";
return $this->defaultPath.'/'.$file;
}
/**
* Get the configuration path for a namespace.
*
* @param string $namespace
* @return string
*/
protected function getPath($namespace)
{
if (is_null($namespace))
{
return $this->defaultPath;
}
elseif (isset($this->hints[$namespace]))
{
return $this->hints[$namespace];
}
}
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string $hint
* @return void
*/
public function addNamespace($namespace, $hint)
{
$this->hints[$namespace] = $hint;
}
/**
* Returns all registered namespaces with the config
* loader.
*
* @return array
*/
public function getNamespaces()
{
return $this->hints;
}
/**
* Get a file's contents by requiring it.
*
* @param string $path
* @return mixed
*/
protected function getRequire($path)
{
return $this->files->getRequire($path);
}
/**
* Get the Filesystem instance.
*
* @return \Illuminate\Filesystem\Filesystem
*/
public function getFilesystem()
{
return $this->files;
}
}

View File

@@ -0,0 +1,52 @@
<?php namespace Illuminate\Config;
interface LoaderInterface {
/**
* Load the given configuration group.
*
* @param string $environment
* @param string $group
* @param string $namespace
* @return array
*/
public function load($environment, $group, $namespace = null);
/**
* Determine if the given configuration group exists.
*
* @param string $group
* @param string $namespace
* @return bool
*/
public function exists($group, $namespace = null);
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string $hint
* @return void
*/
public function addNamespace($namespace, $hint);
/**
* Returns all registered namespaces with the config
* loader.
*
* @return array
*/
public function getNamespaces();
/**
* Apply any cascades to an array of package options.
*
* @param string $environment
* @param string $package
* @param string $group
* @param array $items
* @return array
*/
public function cascadePackage($environment, $package, $group, $items);
}

View File

@@ -0,0 +1,414 @@
<?php namespace Illuminate\Config;
use Closure;
use ArrayAccess;
use Illuminate\Support\NamespacedItemResolver;
class Repository extends NamespacedItemResolver implements ArrayAccess {
/**
* The loader implementation.
*
* @var \Illuminate\Config\LoaderInterface
*/
protected $loader;
/**
* The current environment.
*
* @var string
*/
protected $environment;
/**
* All of the configuration items.
*
* @var array
*/
protected $items = array();
/**
* All of the registered packages.
*
* @var array
*/
protected $packages = array();
/**
* The after load callbacks for namespaces.
*
* @var array
*/
protected $afterLoad = array();
/**
* Create a new configuration repository.
*
* @param \Illuminate\Config\LoaderInterface $loader
* @param string $environment
* @return void
*/
public function __construct(LoaderInterface $loader, $environment)
{
$this->loader = $loader;
$this->environment = $environment;
}
/**
* Determine if the given configuration value exists.
*
* @param string $key
* @return bool
*/
public function has($key)
{
$default = microtime(true);
return $this->get($key, $default) !== $default;
}
/**
* Determine if a configuration group exists.
*
* @param string $key
* @return bool
*/
public function hasGroup($key)
{
list($namespace, $group, $item) = $this->parseKey($key);
return $this->loader->exists($group, $namespace);
}
/**
* Get the specified configuration value.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
list($namespace, $group, $item) = $this->parseKey($key);
// Configuration items are actually keyed by "collection", which is simply a
// combination of each namespace and groups, which allows a unique way to
// identify the arrays of configuration items for the particular files.
$collection = $this->getCollection($group, $namespace);
$this->load($group, $namespace, $collection);
return array_get($this->items[$collection], $item, $default);
}
/**
* Set a given configuration value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function set($key, $value)
{
list($namespace, $group, $item) = $this->parseKey($key);
$collection = $this->getCollection($group, $namespace);
// We'll need to go ahead and lazy load each configuration groups even when
// we're just setting a configuration item so that the set item does not
// get overwritten if a different item in the group is requested later.
$this->load($group, $namespace, $collection);
if (is_null($item))
{
$this->items[$collection] = $value;
}
else
{
array_set($this->items[$collection], $item, $value);
}
}
/**
* Load the configuration group for the key.
*
* @param string $key
* @param string $namespace
* @param string $collection
* @return void
*/
protected function load($group, $namespace, $collection)
{
$env = $this->environment;
// If we've already loaded this collection, we will just bail out since we do
// not want to load it again. Once items are loaded a first time they will
// stay kept in memory within this class and not loaded from disk again.
if (isset($this->items[$collection]))
{
return;
}
$items = $this->loader->load($env, $group, $namespace);
// If we've already loaded this collection, we will just bail out since we do
// not want to load it again. Once items are loaded a first time they will
// stay kept in memory within this class and not loaded from disk again.
if (isset($this->afterLoad[$namespace]))
{
$items = $this->callAfterLoad($namespace, $group, $items);
}
$this->items[$collection] = $items;
}
/**
* Call the after load callback for a namespace.
*
* @param string $namespace
* @param string $group
* @param array $items
* @return array
*/
protected function callAfterLoad($namespace, $group, $items)
{
$callback = $this->afterLoad[$namespace];
return call_user_func($callback, $this, $group, $items);
}
/**
* Parse an array of namespaced segments.
*
* @param string $key
* @return array
*/
protected function parseNamespacedSegments($key)
{
list($namespace, $item) = explode('::', $key);
// If the namespace is registered as a package, we will just assume the group
// is equal to the namespace since all packages cascade in this way having
// a single file per package, otherwise we'll just parse them as normal.
if (in_array($namespace, $this->packages))
{
return $this->parsePackageSegments($key, $namespace, $item);
}
return parent::parseNamespacedSegments($key);
}
/**
* Parse the segments of a package namespace.
*
* @param string $namespace
* @param string $item
* @return array
*/
protected function parsePackageSegments($key, $namespace, $item)
{
$itemSegments = explode('.', $item);
// If the configuration file doesn't exist for the given package group we can
// assume that we should implicitly use the config file matching the name
// of the namespace. Generally packages should use one type or another.
if ( ! $this->loader->exists($itemSegments[0], $namespace))
{
return array($namespace, 'config', $item);
}
return parent::parseNamespacedSegments($key);
}
/**
* Register a package for cascading configuration.
*
* @param string $package
* @param string $hint
* @param string $namespace
* @return void
*/
public function package($package, $hint, $namespace = null)
{
$namespace = $this->getPackageNamespace($package, $namespace);
$this->packages[] = $namespace;
// First we will simply register the namespace with the repository so that it
// can be loaded. Once we have done that we'll register an after namespace
// callback so that we can cascade an application package configuration.
$this->addNamespace($namespace, $hint);
$this->afterLoading($namespace, function($me, $group, $items) use ($package)
{
$env = $me->getEnvironment();
$loader = $me->getLoader();
return $loader->cascadePackage($env, $package, $group, $items);
});
}
/**
* Get the configuration namespace for a package.
*
* @param string $package
* @param string $namespace
* @return string
*/
protected function getPackageNamespace($package, $namespace)
{
if (is_null($namespace))
{
list($vendor, $namespace) = explode('/', $package);
}
return $namespace;
}
/**
* Register an after load callback for a given namespace.
*
* @param string $namespace
* @param Closure $callback
* @return void
*/
public function afterLoading($namespace, Closure $callback)
{
$this->afterLoad[$namespace] = $callback;
}
/**
* Get the collection identifier.
*
* @param string $group
* @param string $namespace
* @return string
*/
protected function getCollection($group, $namespace = null)
{
$namespace = $namespace ?: '*';
return $namespace.'::'.$group;
}
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string $hint
* @return void
*/
public function addNamespace($namespace, $hint)
{
return $this->loader->addNamespace($namespace, $hint);
}
/**
* Returns all registered namespaces with the config
* loader.
*
* @return array
*/
public function getNamespaces()
{
return $this->loader->getNamespaces();
}
/**
* Get the loader implementation.
*
* @return \Illuminate\Config\LoaderInterface
*/
public function getLoader()
{
return $this->loader;
}
/**
* Set the loader implementation.
*
* @param \Illuminate\Config\LoaderInterface $loader
* @return void
*/
public function setLoader(LoaderInterface $loader)
{
$this->loader = $loader;
}
/**
* Get the current configuration environment.
*
* @return string
*/
public function getEnvironment()
{
return $this->environment;
}
/**
* Get the after load callback array.
*
* @return array
*/
public function getAfterLoadCallbacks()
{
return $this->afterLoad;
}
/**
* Get all of the configuration items.
*
* @return array
*/
public function getItems()
{
return $this->items;
}
/**
* Determine if the given configuration option exists.
*
* @param string $key
* @return bool
*/
public function offsetExists($key)
{
return $this->has($key);
}
/**
* Get a configuration option.
*
* @param string $key
* @return bool
*/
public function offsetGet($key)
{
return $this->get($key);
}
/**
* Set a configuration option.
*
* @param string $key
* @param string $value
* @return void
*/
public function offsetSet($key, $value)
{
$this->set($key, $value);
}
/**
* Unset a configuration option.
*
* @param string $key
* @return void
*/
public function offsetUnset($key)
{
$this->set($key, null);
}
}

View File

@@ -0,0 +1,28 @@
{
"name": "illuminate/config",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"illuminate/filesystem": "4.0.x",
"illuminate/support": "4.0.x"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"autoload": {
"psr-0": {"Illuminate\\Config": ""}
},
"target-dir": "Illuminate/Config",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,168 @@
<?php namespace Illuminate\Console;
use Illuminate\Container\Container;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
class Application extends \Symfony\Component\Console\Application {
/**
* The exception handler instance.
*
* @var \Illuminate\Exception\Handler
*/
protected $exceptionHandler;
/**
* The Laravel application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $laravel;
/**
* Start a new Console application.
*
* @param \Illuminate\Foundation\Application $app
* @return \Illuminate\Console\Application
*/
public static function start($app)
{
$artisan = require __DIR__.'/start.php';
$artisan->setAutoExit(false);
// If the event dispatcher is set on the application, we will fire an event
// with the Artisan instance to provide each listener the opportunity to
// register their commands on this application before it gets started.
if (isset($app['events']))
{
$app['events']->fire('artisan.start', array($artisan));
}
return $artisan;
}
/**
* Add a command to the console.
*
* @param \Symfony\Component\Console\Command\Command $command
* @return \Symfony\Component\Console\Command\Command
*/
public function add(SymfonyCommand $command)
{
if ($command instanceof Command)
{
$command->setLaravel($this->laravel);
}
return $this->addToParent($command);
}
/**
* Add the command to the parent instance.
*
* @param \Symfony\Component\Console\Command $command
* @return \Symfony\Component\Console\Command
*/
protected function addToParent($command)
{
return parent::add($command);
}
/**
* Add a command, resolving through the application.
*
* @param string $command
* @return void
*/
public function resolve($command)
{
return $this->add($this->laravel[$command]);
}
/**
* Resolve an array of commands through the application.
*
* @param array|dynamic $commands
* @return void
*/
public function resolveCommands($commands)
{
$commands = is_array($commands) ? $commands : func_get_args();
foreach ($commands as $command)
{
$this->resolve($command);
}
}
/**
* Get the default input definitions for the applications.
*
* @return \Symfony\Component\Console\Input\InputDefinition
*/
protected function getDefaultInputDefinition()
{
$definition = parent::getDefaultInputDefinition();
$definition->addOption($this->getEnvironmentOption());
return $definition;
}
/**
* Get the global environment option for the definition.
*
* @return \Symfony\Component\Console\Input\InputOption
*/
protected function getEnvironmentOption()
{
$message = 'The environment the command should run under.';
return new InputOption('--env', null, InputOption::VALUE_OPTIONAL, $message);
}
/**
* Render the given exception.
*
* @param Exception $e
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
public function renderException($e, $output)
{
// If we have an exception handler instance, we will call that first in case
// it has some handlers that need to be run first. We will pass "true" as
// the second parameter to indicate that it's handling a console error.
if (isset($this->exceptionHandler))
{
$this->exceptionHandler->handleConsole($e);
}
return parent::renderException($e, $output);
}
/**
* Set the exception handler instance.
*
* @param \Illuminate\Exception\Handler $handler
* @return void
*/
public function setExceptionHandler($handler)
{
$this->exceptionHandler = $handler;
}
/**
* Set the Laravel application instance.
*
* @param \Illuminate\Foundation\Application $laravel
* @return void
*/
public function setLaravel($laravel)
{
$this->laravel = $laravel;
}
}

View File

@@ -0,0 +1,317 @@
<?php namespace Illuminate\Console;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Command extends \Symfony\Component\Console\Command\Command {
/**
* The Laravel application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $laravel;
/**
* The input interface implementation.
*
* @var Symfony\Component\Console\Input\InputInterface
*/
protected $input;
/**
* The output interface implementation.
*
* @var Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* The console command name.
*
* @var string
*/
protected $name;
/**
* The console command description.
*
* @var string
*/
protected $description;
/**
* Create a new console command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct($this->name);
// We will go ahead and set the name, description, and parameters on console
// commands just to make things a little easier on the developer. This is
// so they don't have to all be manually specified in the constructors.
$this->setDescription($this->description);
$this->specifyParameters();
}
/**
* Specify the arguments and options on the command.
*
* @return void
*/
protected function specifyParameters()
{
// We will loop through all of the arguments and options for the command and
// set them all on the base command instance. This specifies what can get
// passed into these commands as "parameters" to control the execution.
foreach ($this->getArguments() as $arguments)
{
call_user_func_array(array($this, 'addArgument'), $arguments);
}
foreach ($this->getOptions() as $options)
{
call_user_func_array(array($this, 'addOption'), $options);
}
}
/**
* Run the console command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return integer
*/
public function run(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->output = $output;
return parent::run($input, $output);
}
/**
* Execute the console command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return mixed
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
return $this->fire();
}
/**
* Call another console command.
*
* @param string $command
* @param array $arguments
* @return integer
*/
public function call($command, array $arguments = array())
{
$instance = $this->getApplication()->find($command);
$arguments['command'] = $command;
return $instance->run(new ArrayInput($arguments), $this->output);
}
/**
* Call another console command silently.
*
* @param string $command
* @param array $arguments
* @return integer
*/
public function callSilent($command, array $arguments = array())
{
$instance = $this->getApplication()->find($command);
$arguments['command'] = $command;
return $instance->run(new ArrayInput($arguments), new NullOutput);
}
/**
* Get the value of a command argument.
*
* @param string $key
* @return string|array
*/
public function argument($key = null)
{
if (is_null($key)) return $this->input->getArguments();
return $this->input->getArgument($key);
}
/**
* Get the value of a command option.
*
* @param string $key
* @return string|array
*/
public function option($key = null)
{
if (is_null($key)) return $this->input->getOptions();
return $this->input->getOption($key);
}
/**
* Confirm a question with the user.
*
* @param string $question
* @param bool $default
* @return bool
*/
public function confirm($question, $default = true)
{
$dialog = $this->getHelperSet()->get('dialog');
return $dialog->askConfirmation($this->output, "<question>$question</question>", $default);
}
/**
* Prompt the user for input.
*
* @param string $question
* @param string $default
* @return string
*/
public function ask($question, $default = null)
{
$dialog = $this->getHelperSet()->get('dialog');
return $dialog->ask($this->output, "<question>$question</question>", $default);
}
/**
* Prompt the user for input but hide the answer from the console.
*
* @param string $question
* @param bool $fallabck
* @return string
*/
public function secret($question, $fallback = true)
{
$dialog = $this->getHelperSet()->get('dialog');
return $dialog->askHiddenResponse($this->output, "<question>$question</question>", $fallback);
}
/**
* Write a string as standard output.
*
* @param string $string
* @return void
*/
public function line($string)
{
$this->output->writeln($string);
}
/**
* Write a string as information output.
*
* @param string $string
* @return void
*/
public function info($string)
{
$this->output->writeln("<info>$string</info>");
}
/**
* Write a string as comment output.
*
* @param string $string
* @return void
*/
public function comment($string)
{
$this->output->writeln("<comment>$string</comment>");
}
/**
* Write a string as question output.
*
* @param string $string
* @return void
*/
public function question($string)
{
$this->output->writeln("<question>$string</question>");
}
/**
* Write a string as error output.
*
* @param string $string
* @return void
*/
public function error($string)
{
$this->output->writeln("<error>$string</error>");
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return array();
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array();
}
/**
* Get the output implementation.
*
* @return \Symfony\Component\Console\Output\OutputInterface
*/
public function getOutput()
{
return $this->output;
}
/**
* Set the Laravel application instance.
*
* @return \Illuminate\Foundation\Application
*/
public function getLaravel()
{
return $this->laravel;
}
/**
* Set the Laravel application instance.
*
* @param \Illuminate\Foundation\Application $laravel
* @return void
*/
public function setLaravel($laravel)
{
$this->laravel = $laravel;
}
}

View File

@@ -0,0 +1,28 @@
{
"name": "illuminate/console",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"require": {
"symfony/console": "2.3.*"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"autoload": {
"psr-0": {
"Illuminate\\Console": ""
}
},
"target-dir": "Illuminate/Console",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,59 @@
<?php
/*
|--------------------------------------------------------------------------
| Create The Artisan Application
|--------------------------------------------------------------------------
|
| Now we're ready to create the Artisan console application, which will
| be responsible for running the appropriate command. The console is
| built on top of the robust, powerful Symfony console components.
|
*/
use Illuminate\Console\Application;
$artisan = new Application('Laravel Framework', $app::VERSION);
$app->instance('artisan', $artisan);
/*
|--------------------------------------------------------------------------
| Set The Laravel Exception Handler
|--------------------------------------------------------------------------
|
| We'll go ahead and set the Laravel exception handler so the console can
| call these handler when an exception is thrown from the CLI. This is
| important since there could be loggers, etc. setup for exceptions.
|
*/
$artisan->setExceptionHandler($app['exception']);
/*
|--------------------------------------------------------------------------
| Set The Laravel Application
|--------------------------------------------------------------------------
|
| When creating the Artisan application, we will set the Laravel app on
| the console so that we can easily access it from our commands when
| necessary, which allows us to quickly access other app services.
|
*/
$artisan->setLaravel($app);
/*
|--------------------------------------------------------------------------
| Register The Artisan Commands
|--------------------------------------------------------------------------
|
| Each available Artisan command must be registered with the console so
| that it is available to be called. We'll register every command so
| the console gets access to each of the command object instances.
|
*/
require $app['path'].'/start/artisan.php';
return $artisan;

View File

@@ -0,0 +1,534 @@
<?php namespace Illuminate\Container; use Closure, ArrayAccess, ReflectionParameter;
class BindingResolutionException extends \Exception {}
class Container implements ArrayAccess {
/**
* The container's bindings.
*
* @var array
*/
protected $bindings = array();
/**
* The container's shared instances.
*
* @var array
*/
protected $instances = array();
/**
* The registered type aliases.
*
* @var array
*/
protected $aliases = array();
/**
* All of the registered resolving callbacks.
*
* @var array
*/
protected $resolvingCallbacks = array();
/**
* Determine if the given abstract type has been bound.
*
* @param string $abstract
* @return bool
*/
public function bound($abstract)
{
return isset($this[$abstract]) or isset($this->instances[$abstract]);
}
/**
* Register a binding with the container.
*
* @param string $abstract
* @param Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
// If the given type is actually an array, we'll assume an alias is being
// defined and will grab the real abstract class name and register the
// alias with the container so it can be used as a short-cut for it.
if (is_array($abstract))
{
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);
}
// If no concrete type was given, we will simply set the concrete type to
// the abstract. This allows concrete types to be registered as shared
// without being made state their classes in both of the parameters.
unset($this->instances[$abstract]);
if (is_null($concrete))
{
$concrete = $abstract;
}
// If the factory is not a Closure, it means it is just a class name that
// is bound into the container to an abstract type and we'll just wrap
// it up in a Closure to make things more convenient when extending.
if ( ! $concrete instanceof Closure)
{
$concrete = function($c) use ($abstract, $concrete)
{
$method = ($abstract == $concrete) ? 'build' : 'make';
return $c->$method($concrete);
};
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
/**
* Register a binding if it hasn't already been registered.
*
* @param string $abstract
* @param Closure|string|null $concrete
* @param bool $shared
* @return bool
*/
public function bindIf($abstract, $concrete = null, $shared = false)
{
if ( ! $this->bound($abstract))
{
$this->bind($abstract, $concrete, $shared);
}
}
/**
* Register a shared binding in the container.
*
* @param string $abstract
* @param Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
return $this->bind($abstract, $concrete, true);
}
/**
* Wrap a Closure such that it is shared.
*
* @param Closure $closure
* @return Closure
*/
public function share(Closure $closure)
{
return function($container) use ($closure)
{
// We'll simply declare a static variable within the Closures and if
// it has not been set we'll execute the given Closure to resolve
// the value and return it back to the consumers of the method.
static $object;
if (is_null($object))
{
$object = $closure($container);
}
return $object;
};
}
/**
* "Extend" an abstract type in the container.
*
* @param string $abstract
* @param Closure $closure
* @return void
*/
public function extend($abstract, Closure $closure)
{
if ( ! isset($this->bindings[$abstract]))
{
throw new \InvalidArgumentException("Type {$abstract} is not bound.");
}
// To "extend" a binding, we will grab the old "resolver" Closure and pass it
// into a new one. The old resolver will be called first and the result is
// handed off to the "new" resolver, along with this container instance.
$resolver = $this->bindings[$abstract]['concrete'];
$this->bind($abstract, function($container) use ($resolver, $closure)
{
return $closure($resolver($container), $container);
}, $this->isShared($abstract));
}
/**
* Register an existing instance as shared in the container.
*
* @param string $abstract
* @param mixed $instance
* @return void
*/
public function instance($abstract, $instance)
{
if (is_array($abstract))
{
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);
}
$this->instances[$abstract] = $instance;
}
/**
* Alias a type to a shorter name.
*
* @param string $abstract
* @param string $alias
* @return void
*/
public function alias($abstract, $alias)
{
$this->aliases[$alias] = $abstract;
}
/**
* Extract the type and alias from a given definition.
*
* @param array $definition
* @return array
*/
protected function extractAlias(array $definition)
{
return array(key($definition), current($definition));
}
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, $parameters = array())
{
$abstract = $this->getAlias($abstract);
// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]))
{
return $this->instances[$abstract];
}
$concrete = $this->getConcrete($abstract);
// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract))
{
$object = $this->build($concrete, $parameters);
}
else
{
$object = $this->make($concrete, $parameters);
}
// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
if ($this->isShared($abstract))
{
$this->instances[$abstract] = $object;
}
$this->fireResolvingCallbacks($object);
return $object;
}
/**
* Get the concrete type for a given abstract.
*
* @param string $abstract
* @return mixed $concrete
*/
protected function getConcrete($abstract)
{
// If we don't have a registered resolver or concrete for the type, we'll just
// assume each type is a concrete name and will attempt to resolve it as is
// since the container should be able to resolve concretes automatically.
if ( ! isset($this->bindings[$abstract]))
{
return $abstract;
}
else
{
return $this->bindings[$abstract]['concrete'];
}
}
/**
* Instantiate a concrete instance of the given type.
*
* @param string $concrete
* @param array $parameters
* @return mixed
*/
public function build($concrete, $parameters = array())
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
if ($concrete instanceof Closure)
{
return $concrete($this, $parameters);
}
$reflector = new \ReflectionClass($concrete);
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface of Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if ( ! $reflector->isInstantiable())
{
$message = "Target [$concrete] is not instantiable.";
throw new BindingResolutionException($message);
}
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
if (is_null($constructor))
{
return new $concrete;
}
$parameters = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
$dependencies = $this->getDependencies($parameters);
return $reflector->newInstanceArgs($dependencies);
}
/**
* Resolve all of the dependencies from the ReflectionParameters.
*
* @param array $parameters
* @return array
*/
protected function getDependencies($parameters)
{
$dependencies = array();
foreach ($parameters as $parameter)
{
$dependency = $parameter->getClass();
// If the class is null, it means the dependency is a string or some other
// primitive type which we can not resolve since it is not a class and
// we'll just bomb out with an error since we have no-where to go.
if (is_null($dependency))
{
$dependencies[] = $this->resolveNonClass($parameter);
}
else
{
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array) $dependencies;
}
/**
* Resolve a non-class hinted dependency.
*
* @param ReflectionParameter $parameter
* @return mixed
*/
protected function resolveNonClass(ReflectionParameter $parameter)
{
if ($parameter->isDefaultValueAvailable())
{
return $parameter->getDefaultValue();
}
else
{
$message = "Unresolvable dependency resolving [$parameter].";
throw new BindingResolutionException($message);
}
}
/**
* Resolve a class based dependency from the container.
*
* @param \ReflectionParameter $parameter
* @return mixed
*/
protected function resolveClass(ReflectionParameter $parameter)
{
try
{
return $this->make($parameter->getClass()->name);
}
// If we can not resolve the class instance, we will check to see if the value
// is optional, and if it is we will return the optional parameter value as
// the value of the dependency, similarly to how we do this with scalars.
catch (BindingResolutionException $e)
{
if ($parameter->isOptional())
{
return $parameter->getDefaultValue();
}
else
{
throw $e;
}
}
}
/**
* Register a new resolving callback.
*
* @param Closure $callback
* @return void
*/
public function resolving(Closure $callback)
{
$this->resolvingCallbacks[] = $callback;
}
/**
* Fire all of the resolving callbacks.
*
* @param mixed $object
* @return void
*/
protected function fireResolvingCallbacks($object)
{
foreach ($this->resolvingCallbacks as $callback)
{
call_user_func($callback, $object);
}
}
/**
* Determine if a given type is shared.
*
* @param string $abstract
* @return bool
*/
protected function isShared($abstract)
{
$set = isset($this->bindings[$abstract]['shared']);
return $set and $this->bindings[$abstract]['shared'] === true;
}
/**
* Determine if the given concrete is buildable.
*
* @param mixed $concrete
* @param string $abstract
* @return bool
*/
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract or $concrete instanceof Closure;
}
/**
* Get the alias for an abstract if available.
*
* @param string $abstract
* @return string
*/
protected function getAlias($abstract)
{
return isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract;
}
/**
* Get the container's bindings.
*
* @return array
*/
public function getBindings()
{
return $this->bindings;
}
/**
* Determine if a given offset exists.
*
* @param string $key
* @return bool
*/
public function offsetExists($key)
{
return isset($this->bindings[$key]);
}
/**
* Get the value at a given offset.
*
* @param string $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->make($key);
}
/**
* Set the value at a given offset.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function offsetSet($key, $value)
{
// If the value is not a Closure, we will make it one. This simply gives
// more "drop-in" replacement functionality for the Pimple which this
// container's simplest functions are base modeled and built after.
if ( ! $value instanceof Closure)
{
$value = function() use ($value)
{
return $value;
};
}
$this->bind($key, $value);
}
/**
* Unset the value at a given offset.
*
* @param string $key
* @return void
*/
public function offsetUnset($key)
{
unset($this->bindings[$key]);
}
}

View File

@@ -0,0 +1,28 @@
{
"name": "illuminate/container",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"autoload": {
"psr-0": {
"Illuminate\\Container": ""
}
},
"target-dir": "Illuminate/Container",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,199 @@
<?php namespace Illuminate\Cookie;
use Closure;
use Illuminate\Encryption\Encrypter;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class CookieJar {
/*
* The current request instance.
*
* @var Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The encrypter instance.
*
* @var \Illuminate\Encryption\Encrypter
*/
protected $encrypter;
/**
* The default path (if specified).
*
* @var string
*/
protected $path = '/';
/**
* The default domain (if specified).
*
* @var string
*/
protected $domain = null;
/**
* Create a new cookie manager instance.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Illuminate\Encryption\Encrypter $encrypter
* @return void
*/
public function __construct(Request $request, Encrypter $encrypter)
{
$this->request = $request;
$this->encrypter = $encrypter;
}
/**
* Determine if a cookie exists and is not null.
*
* @param string $key
* @return bool
*/
public function has($key)
{
return ! is_null($this->get($key));
}
/**
* Get the value of the given cookie.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
$value = $this->request->cookies->get($key);
if ( ! is_null($value))
{
return $this->decrypt($value);
}
return $default instanceof Closure ? $default() : $default;
}
/**
* Decrypt the given cookie value.
*
* @param string $value
* @return mixed|null
*/
protected function decrypt($value)
{
try
{
return $this->encrypter->decrypt($value);
}
catch (\Exception $e)
{
return null;
}
}
/**
* Create a new cookie instance.
*
* @param string $name
* @param string $value
* @param int $minutes
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $httpOnly
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true)
{
list($path, $domain) = $this->getPathAndDomain($path, $domain);
// Once we calculate the time we can encrypt the message. All cookies will be
// encrypted using the Illuminate encryption component and will have a MAC
// assigned to them by the encrypter to make sure they remain authentic.
$time = ($minutes == 0) ? 0 : time() + ($minutes * 60);
$value = $this->encrypter->encrypt($value);
return new Cookie($name, $value, $time, $path, $domain, $secure, $httpOnly);
}
/**
* Create a cookie that lasts "forever" (five years).
*
* @param string $name
* @param string $value
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $httpOnly
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function forever($name, $value, $path = null, $domain = null, $secure = false, $httpOnly = true)
{
return $this->make($name, $value, 2628000, $path, $domain, $secure, $httpOnly);
}
/**
* Expire the given cookie.
*
* @param string $name
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function forget($name)
{
return $this->make($name, null, -2628000);
}
/**
* Get the path and domain, or the default values.
*
* @param string $path
* @param string $domain
* @return array
*/
protected function getPathAndDomain($path, $domain)
{
return array($path ?: $this->path, $domain ?: $this->domain);
}
/**
* Set the default path and domain for the jar.
*
* @param string $path
* @param string $domain
* @return void
*/
public function setDefaultPathAndDomain($path, $domain)
{
list($this->path, $this->domain) = array($path, $domain);
return $this;
}
/**
* Get the request instance.
*
* @return \Symfony\Component\HttpFoundation\Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get the encrypter instance.
*
* @return \Illuminate\Encryption\Encrypter
*/
public function getEncrypter()
{
return $this->encrypter;
}
}

View File

@@ -0,0 +1,24 @@
<?php namespace Illuminate\Cookie;
use Illuminate\Support\ServiceProvider;
class CookieServiceProvider extends ServiceProvider {
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app['cookie'] = $this->app->share(function($app)
{
$cookies = new CookieJar($app['request'], $app['encrypter']);
$config = $app['config']['session'];
return $cookies->setDefaultPathAndDomain($config['path'], $config['domain']);
});
}
}

View File

@@ -0,0 +1,32 @@
{
"name": "illuminate/cookie",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"illuminate/encryption": "4.0.x",
"illuminate/support": "4.0.x",
"symfony/http-foundation": "2.3.*"
},
"require-dev": {
"mockery/mockery": "0.7.2",
"phpunit/phpunit": "3.7.*"
},
"autoload": {
"psr-0": {
"Illuminate\\Cookie": ""
}
},
"target-dir": "Illuminate/Cookie",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,259 @@
<?php namespace Illuminate\Database\Capsule;
use PDO;
use Illuminate\Support\Fluent;
use Illuminate\Events\Dispatcher;
use Illuminate\Cache\CacheManager;
use Illuminate\Container\Container;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Connectors\ConnectionFactory;
class Manager {
/**
* The current globally used instance.
*
* @var \Illuminate\Database\Capsule\Manager
*/
protected static $instance;
/**
* Create a new database capsule manager.
*
* @param \Illuminate\Container\Container $container
* @return void
*/
public function __construct(Container $container = null)
{
$this->setupContainer($container);
// Once we have the container setup, we will setup the default configuration
// options in the container "config" binding. This will make the database
// manager behave correctly since all the correct binding are in place.
$this->setupDefaultConfiguration();
$this->setupManager();
}
/**
* Setup the IoC container instance.
*
* @param \Illuminate\Container\Container $container
* @return void
*/
protected function setupContainer($container)
{
$this->container = $container ?: new Container;
$this->container->instance('config', new Fluent);
}
/**
* Setup the default database configuration options.
*
* @return void
*/
protected function setupDefaultConfiguration()
{
$this->container['config']['database.fetch'] = PDO::FETCH_ASSOC;
$this->container['config']['database.default'] = 'default';
}
/**
* Build the database manager instance.
*
* @return void
*/
protected function setupManager()
{
$factory = new ConnectionFactory($this->container);
$this->manager = new DatabaseManager($this->container, $factory);
}
/**
* Get a connection instance from the global manager.
*
* @param string $connection
* @return \Illuminate\Database\Connection
*/
public static function connection($connection = null)
{
return static::$instance->getConnection($connection);
}
/**
* Get a fluent query builder instance.
*
* @param string $table
* @param string $connection
* @return \Illuminate\Database\Query\Builder
*/
public static function table($table, $connection = null)
{
return static::$instance->connection($connection)->table($table);
}
/**
* Get a schema builder instance.
*
* @param string $connection
* @return \Illuminate\Database\Schema\Builder
*/
public static function schema($connection = null)
{
return static::$instance->connection($connection)->getSchemaBuilder();
}
/**
* Get a registered connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function getConnection($name = null)
{
return $this->manager->connection($name);
}
/**
* Register a connection with the manager.
*
* @param array $config
* @param string $name
* @return void
*/
public function addConnection(array $config, $name = 'default')
{
$connections = $this->container['config']['database.connections'];
$connections[$name] = $config;
$this->container['config']['database.connections'] = $connections;
}
/**
* Bootstrap Eloquent so it is ready for usage.
*
* @return void
*/
public function bootEloquent()
{
Eloquent::setConnectionResolver($this->manager);
// If we have an event dispatcher instance, we will go ahead and register it
// with the Eloquent ORM, allowing for model callbacks while creating and
// updating "model" instances; however, if it not necessary to operate.
if ($dispatcher = $this->getEventDispatcher())
{
Eloquent::setEventDispatcher($dispatcher);
}
}
/**
* Set the fetch mode for the database connections.
*
* @param int $fetchMode
* @return \Illuminate\Database\Capsule\Manager
*/
public function setFetchMode($fetchMode)
{
$this->container['config']['database.fetch'] = $fetchMode;
return $this;
}
/**
* Make this capsule instance available globally.
*
* @return void
*/
public function setAsGlobal()
{
static::$instance = $this;
}
/**
* Get the current event dispatcher instance.
*
* @return \Illuminate\Events\Dispatcher
*/
public function getEventDispatcher()
{
if ($this->container->bound('events'))
{
return $this->container['events'];
}
}
/**
* Set the event dispatcher instance to be used by connections.
*
* @param \Illuminate\Events\Dispatcher $dispatcher
* @return void
*/
public function setEventDispatcher(Dispatcher $dispatcher)
{
$this->container->instance('events', $dispatcher);
}
/**
* Get the current cache manager instance.
*
* @return \Illuminate\Cache\Manager
*/
public function getCacheManager()
{
if ($this->container->bound('cache'))
{
return $this->container['cache'];
}
}
/**
* Set the cache manager to bse used by connections.
*
* @param \Illuminate\Cache\CacheManager $cache
* @return void
*/
public function setCacheManager(CacheManager $cache)
{
$this->container->instance('cache', $cache);
}
/**
* Get the IoC container instance.
*
* @return \Illuminate\Container\Container
*/
public function getContainer()
{
return $this->container;
}
/**
* Set the IoC container instance.
*
* @param \Illuminate\Container\Container $container
* @return void
*/
public function setContainer(Container $container)
{
$this->container = $container;
}
/**
* Dynamically pass methods to the default connection.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
return call_user_func_array(array(static::connection(), $method), $parameters);
}
}

View File

@@ -0,0 +1,946 @@
<?php namespace Illuminate\Database;
use PDO;
use Closure;
use DateTime;
use Illuminate\Cache\CacheManager;
use Illuminate\Database\Query\Processors\Processor;
class Connection implements ConnectionInterface {
/**
* The active PDO connection.
*
* @var PDO
*/
protected $pdo;
/**
* The query grammar implementation.
*
* @var \Illuminate\Database\Query\Grammars\Grammar
*/
protected $queryGrammar;
/**
* The schema grammar implementation.
*
* @var \Illuminate\Database\Schema\Grammars\Grammar
*/
protected $schemaGrammar;
/**
* The query post processor implementation.
*
* @var \Illuminate\Database\Query\Processors\Processor
*/
protected $postProcessor;
/**
* The event dispatcher instance.
*
* @var \Illuminate\Events\Dispatcher
*/
protected $events;
/**
* The paginator environment instance.
*
* @var \Illuminate\Pagination\Paginator
*/
protected $paginator;
/**
* The cache manager instance.
*
* @var \Illuminate\Cache\CacheManger
*/
protected $cache;
/**
* The default fetch mode of the connection.
*
* @var int
*/
protected $fetchMode = PDO::FETCH_ASSOC;
/**
* The number of active transasctions.
*
* @var int
*/
protected $transactions = 0;
/**
* All of the queries run against the connection.
*
* @var array
*/
protected $queryLog = array();
/**
* Indicates whether queries are being logged.
*
* @var bool
*/
protected $loggingQueries = true;
/**
* Indicates if the connection is in a "dry run".
*
* @var bool
*/
protected $pretending = false;
/**
* The name of the connected database.
*
* @var string
*/
protected $database;
/**
* The table prefix for the connection.
*
* @var string
*/
protected $tablePrefix = '';
/**
* The database connection configuration options.
*
* @var array
*/
protected $config = array();
/**
* Create a new database connection instance.
*
* @param PDO $pdo
* @param string $database
* @param string $tablePrefix
* @param array $config
* @return void
*/
public function __construct(PDO $pdo, $database = '', $tablePrefix = '', array $config = array())
{
$this->pdo = $pdo;
// First we will setup the default properties. We keep track of the DB
// name we are connected to since it is needed when some reflective
// type commands are run such as checking whether a table exists.
$this->database = $database;
$this->tablePrefix = $tablePrefix;
$this->config = $config;
// We need to initialize a query grammar and the query post processors
// which are both very important parts of the database abstractions
// so we initialize these to their default values while starting.
$this->useDefaultQueryGrammar();
$this->useDefaultPostProcessor();
}
/**
* Set the query grammar to the default implementation.
*
* @return void
*/
public function useDefaultQueryGrammar()
{
$this->queryGrammar = $this->getDefaultQueryGrammar();
}
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\Grammar
*/
protected function getDefaultQueryGrammar()
{
return new Query\Grammars\Grammar;
}
/**
* Set the schema grammar to the default implementation.
*
* @return void
*/
public function useDefaultSchemaGrammar()
{
$this->schemaGrammar = $this->getDefaultSchemaGrammar();
}
/**
* Get the default schema grammar instance.
*
* @return \Illuminate\Database\Schema\Grammars\Grammar
*/
protected function getDefaultSchemaGrammar() {}
/**
* Set the query post processor to the default implementation.
*
* @return void
*/
public function useDefaultPostProcessor()
{
$this->postProcessor = $this->getDefaultPostProcessor();
}
/**
* Get the default post processor instance.
*
* @return \Illuminate\Database\Query\Processors\Processor
*/
protected function getDefaultPostProcessor()
{
return new Query\Processors\Processor;
}
/**
* Get a schema builder instance for the connection.
*
* @return \Illuminate\Database\Schema\Builder
*/
public function getSchemaBuilder()
{
if (is_null($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); }
return new Schema\Builder($this);
}
/**
* Begin a fluent query against a database table.
*
* @param string $table
* @return \Illuminate\Database\Query\Builder
*/
public function table($table)
{
$processor = $this->getPostProcessor();
$query = new Query\Builder($this, $this->getQueryGrammar(), $processor);
return $query->from($table);
}
/**
* Get a new raw query expression.
*
* @param mixed $value
* @return \Illuminate\Database\Query\Expression
*/
public function raw($value)
{
return new Query\Expression($value);
}
/**
* Run a select statement and return a single result.
*
* @param string $query
* @param array $bindings
* @return mixed
*/
public function selectOne($query, $bindings = array())
{
$records = $this->select($query, $bindings);
return count($records) > 0 ? reset($records) : null;
}
/**
* Run a select statement against the database.
*
* @param string $query
* @param array $bindings
* @return array
*/
public function select($query, $bindings = array())
{
return $this->run($query, $bindings, function($me, $query, $bindings)
{
if ($me->pretending()) return array();
// For select statements, we'll simply execute the query and return an array
// of the database result set. Each element in the array will be a single
// row from the database table, and will either be an array or objects.
$statement = $me->getPdo()->prepare($query);
$statement->execute($me->prepareBindings($bindings));
return $statement->fetchAll($me->getFetchMode());
});
}
/**
* Run an insert statement against the database.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function insert($query, $bindings = array())
{
return $this->statement($query, $bindings);
}
/**
* Run an update statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function update($query, $bindings = array())
{
return $this->affectingStatement($query, $bindings);
}
/**
* Run a delete statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function delete($query, $bindings = array())
{
return $this->affectingStatement($query, $bindings);
}
/**
* Execute an SQL statement and return the boolean result.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function statement($query, $bindings = array())
{
return $this->run($query, $bindings, function($me, $query, $bindings)
{
if ($me->pretending()) return true;
$bindings = $me->prepareBindings($bindings);
return $me->getPdo()->prepare($query)->execute($bindings);
});
}
/**
* Run an SQL statement and get the number of rows affected.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function affectingStatement($query, $bindings = array())
{
return $this->run($query, $bindings, function($me, $query, $bindings)
{
if ($me->pretending()) return 0;
// For update or delete statements, we want to get the number of rows affected
// by the statement and return that back to the developer. We'll first need
// to execute the statement and then we'll use PDO to fetch the affected.
$statement = $me->getPdo()->prepare($query);
$statement->execute($me->prepareBindings($bindings));
return $statement->rowCount();
});
}
/**
* Run a raw, unprepared query against the PDO connection.
*
* @param string $query
* @return bool
*/
public function unprepared($query)
{
return $this->run($query, array(), function($me, $query, $bindings)
{
if ($me->pretending()) return true;
return (bool) $me->getPdo()->exec($query);
});
}
/**
* Prepare the query bindings for execution.
*
* @param array $bindings
* @return array
*/
public function prepareBindings(array $bindings)
{
$grammar = $this->getQueryGrammar();
foreach ($bindings as $key => $value)
{
// We need to transform all instances of the DateTime class into an actual
// date string. Each query grammar maintains its own date string format
// so we'll just ask the grammar for the format to get from the date.
if ($value instanceof DateTime)
{
$bindings[$key] = $value->format($grammar->getDateFormat());
}
elseif ($value === false)
{
$bindings[$key] = 0;
}
}
return $bindings;
}
/**
* Execute a Closure within a transaction.
*
* @param Closure $callback
* @return mixed
*/
public function transaction(Closure $callback)
{
$this->beginTransaction();
// We'll simply execute the given callback within a try / catch block
// and if we catch any exception we can rollback the transaction
// so that none of the changes are persisted to the database.
try
{
$result = $callback($this);
$this->commit();
}
// If we catch an exception, we will roll back so nothing gets messed
// up in the database. Then we'll re-throw the exception so it can
// be handled how the developer sees fit for their applications.
catch (\Exception $e)
{
$this->rollBack();
throw $e;
}
return $result;
}
/**
* Start a new database transaction.
*
* @return void
*/
public function beginTransaction()
{
++$this->transactions;
if ($this->transactions == 1)
{
$this->pdo->beginTransaction();
}
}
/**
* Commit the active database transaction.
*
* @return void
*/
public function commit()
{
if ($this->transactions == 1) $this->pdo->commit();
--$this->transactions;
}
/**
* Rollback the active database transaction.
*
* @return void
*/
public function rollBack()
{
if ($this->transactions == 1)
{
$this->transactions = 0;
$this->pdo->rollBack();
}
else
{
--$this->transactions;
}
}
/**
* Execute the given callback in "dry run" mode.
*
* @param Closure $callback
* @return array
*/
public function pretend(Closure $callback)
{
$this->pretending = true;
$this->queryLog = array();
// Basically to make the database connection "pretend", we will just return
// the default values for all the query methods, then we will return an
// array of queries that were "executed" within the Closure callback.
$callback($this);
$this->pretending = false;
return $this->queryLog;
}
/**
* Run a SQL statement and log its execution context.
*
* @param string $query
* @param array $bindings
* @param Closure $callback
* @return mixed
*/
protected function run($query, $bindings, Closure $callback)
{
$start = microtime(true);
// To execute the statement, we'll simply call the callback, which will actually
// run the SQL against the PDO connection. Then we can calculate the time it
// took to execute and log the query SQL, bindings and time in our memory.
try
{
$result = $callback($this, $query, $bindings);
}
// If an exception occurs when attempting to run a query, we'll format the error
// message to include the bindings with SQL, which will make this exception a
// lot more helpful to the developer instead of just the database's errors.
catch (\Exception $e)
{
$this->handleQueryException($e, $query, $bindings);
}
// Once we have run the query we will calculate the time that it took to run and
// then log the query, bindings, and execution time so we will report them on
// the event that the developer needs them. We'll log time in milliseconds.
$time = $this->getElapsedTime($start);
$this->logQuery($query, $bindings, $time);
return $result;
}
/**
* Handle an exception that occurred during a query.
*
* @param Exception $e
* @param string $query
* @param array $bindings
* @return void
*/
protected function handleQueryException(\Exception $e, $query, $bindings)
{
$bindings = var_export($bindings, true);
$message = $e->getMessage()." (SQL: {$query}) (Bindings: {$bindings})";
throw new \Exception($message, 0, $e);
}
/**
* Log a query in the connection's query log.
*
* @param string $query
* @param array $bindings
* @param $time
* @return void
*/
public function logQuery($query, $bindings, $time = null)
{
if (isset($this->events))
{
$this->events->fire('illuminate.query', array($query, $bindings, $time, $this->getName()));
}
if ( ! $this->loggingQueries) return;
$this->queryLog[] = compact('query', 'bindings', 'time');
}
/**
* Register a database query listener with the connection.
*
* @param Closure $callback
* @return void
*/
public function listen(Closure $callback)
{
if (isset($this->events))
{
$this->events->listen('illuminate.query', $callback);
}
}
/**
* Get the elapsed time since a given starting point.
*
* @param int $start
* @return float
*/
protected function getElapsedTime($start)
{
return round((microtime(true) - $start) * 1000, 2);
}
/**
* Get a Doctrine Schema Column instance.
*
* @param string $table
* @param string $column
* @return \Doctrine\DBAL\Schema\Column
*/
public function getDoctrineColumn($table, $column)
{
$schema = $this->getDoctrineSchemaManager();
return $schema->listTableDetails($table)->getColumn($column);
}
/**
* Get the Doctrine DBAL schema manager for the connection.
*
* @return \Doctrine\DBAL\Schema\AbstractSchemaManager
*/
public function getDoctrineSchemaManager()
{
return $this->getDoctrineDriver()->getSchemaManager($this->getDoctrineConnection());
}
/**
* Get the Doctrine DBAL database connection instance.
*
* @return \Doctrine\DBAL\Connection
*/
public function getDoctrineConnection()
{
$driver = $this->getDoctrineDriver();
$data = array('pdo' => $this->pdo, 'dbname' => $this->getConfig('database'));
return new \Doctrine\DBAL\Connection($data, $driver);
}
/**
* Get the currently used PDO connection.
*
* @return PDO
*/
public function getPdo()
{
return $this->pdo;
}
/**
* Get the database connection name.
*
* @return string|null
*/
public function getName()
{
return $this->getConfig('name');
}
/**
* Get an option from the configuration options.
*
* @param string $option
* @return mixed
*/
public function getConfig($option)
{
return array_get($this->config, $option);
}
/**
* Get the PDO driver name.
*
* @return string
*/
public function getDriverName()
{
return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
}
/**
* Get the query grammar used by the connection.
*
* @return \Illuminate\Database\Query\Grammars\Grammar
*/
public function getQueryGrammar()
{
return $this->queryGrammar;
}
/**
* Set the query grammar used by the connection.
*
* @param \Illuminate\Database\Query\Grammars\Grammar
* @return void
*/
public function setQueryGrammar(Query\Grammars\Grammar $grammar)
{
$this->queryGrammar = $grammar;
}
/**
* Get the schema grammar used by the connection.
*
* @return \Illuminate\Database\Query\Grammars\Grammar
*/
public function getSchemaGrammar()
{
return $this->schemaGrammar;
}
/**
* Set the schema grammar used by the connection.
*
* @param \Illuminate\Database\Schema\Grammars\Grammar
* @return void
*/
public function setSchemaGrammar(Schema\Grammars\Grammar $grammar)
{
$this->schemaGrammar = $grammar;
}
/**
* Get the query post processor used by the connection.
*
* @return \Illuminate\Database\Query\Processors\Processor
*/
public function getPostProcessor()
{
return $this->postProcessor;
}
/**
* Set the query post processor used by the connection.
*
* @param \Illuminate\Database\Query\Processors\Processor
* @return void
*/
public function setPostProcessor(Processor $processor)
{
$this->postProcessor = $processor;
}
/**
* Get the event dispatcher used by the connection.
*
* @return \Illuminate\Events\Dispatcher
*/
public function getEventDispatcher()
{
return $this->events;
}
/**
* Set the event dispatcher instance on the connection.
*
* @param \Illuminate\Events\Dispatcher
* @return void
*/
public function setEventDispatcher(\Illuminate\Events\Dispatcher $events)
{
$this->events = $events;
}
/**
* Get the paginator environment instance.
*
* @return \Illuminate\Pagination\Environment
*/
public function getPaginator()
{
if ($this->paginator instanceof Closure)
{
$this->paginator = call_user_func($this->paginator);
}
return $this->paginator;
}
/**
* Set the pagination environment instance.
*
* @param \Illuminate\Pagination\Environment|\Closure $paginator
* @return void
*/
public function setPaginator($paginator)
{
$this->paginator = $paginator;
}
/**
* Get the cache manager instance.
*
* @return \Illuminate\Cache\CacheManager
*/
public function getCacheManager()
{
if ($this->cache instanceof Closure)
{
$this->cache = call_user_func($this->cache);
}
return $this->cache;
}
/**
* Set the cache manager instance on the connection.
*
* @param \Illuminate\Cache\CacheManager|\Closure $cache
* @return void
*/
public function setCacheManager($cache)
{
$this->cache = $cache;
}
/**
* Determine if the connection in a "dry run".
*
* @return bool
*/
public function pretending()
{
return $this->pretending === true;
}
/**
* Get the default fetch mode for the connection.
*
* @return int
*/
public function getFetchMode()
{
return $this->fetchMode;
}
/**
* Set the default fetch mode for the connection.
*
* @param int $fetchMode
* @return int
*/
public function setFetchMode($fetchMode)
{
$this->fetchMode = $fetchMode;
}
/**
* Get the connection query log.
*
* @return array
*/
public function getQueryLog()
{
return $this->queryLog;
}
/**
* Clear the query log.
*
* @return void
*/
public function flushQueryLog()
{
$this->queryLog = array();
}
/**
* Enable the query log on the connection.
*
* @return void
*/
public function enableQueryLog()
{
$this->loggingQueries = true;
}
/**
* Disable the query log on the connection.
*
* @return void
*/
public function disableQueryLog()
{
$this->loggingQueries = false;
}
/**
* Get the name of the connected database.
*
* @return string
*/
public function getDatabaseName()
{
return $this->database;
}
/**
* Set the name of the connected database.
*
* @param string $database
* @return string
*/
public function setDatabaseName($database)
{
$this->database = $database;
}
/**
* Get the table prefix for the connection.
*
* @return string
*/
public function getTablePrefix()
{
return $this->tablePrefix;
}
/**
* Set the table prefix in use by the connection.
*
* @param string $prefix
* @return void
*/
public function setTablePrefix($prefix)
{
$this->tablePrefix = $prefix;
$this->getQueryGrammar()->setTablePrefix($prefix);
}
/**
* Set the table prefix and return the grammar.
*
* @param \Illuminate\Database\Grammar $grammar
* @return \Illuminate\Database\Grammar
*/
public function withTablePrefix(Grammar $grammar)
{
$grammar->setTablePrefix($this->tablePrefix);
return $grammar;
}
}

View File

@@ -0,0 +1,67 @@
<?php namespace Illuminate\Database; use Closure;
interface ConnectionInterface {
/**
* Run a select statement and return a single result.
*
* @param string $query
* @param array $bindings
* @return mixed
*/
public function selectOne($query, $bindings = array());
/**
* Run a select statement against the database.
*
* @param string $query
* @param array $bindings
* @return array
*/
public function select($query, $bindings = array());
/**
* Run an insert statement against the database.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function insert($query, $bindings = array());
/**
* Run an update statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function update($query, $bindings = array());
/**
* Run a delete statement against the database.
*
* @param string $query
* @param array $bindings
* @return int
*/
public function delete($query, $bindings = array());
/**
* Execute an SQL statement and return the boolean result.
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function statement($query, $bindings = array());
/**
* Execute a Closure within a transaction.
*
* @param Closure $callback
* @return mixed
*/
public function transaction(Closure $callback);
}

View File

@@ -0,0 +1,90 @@
<?php namespace Illuminate\Database;
class ConnectionResolver implements ConnectionResolverInterface {
/**
* All of the registered connections.
*
* @var array
*/
protected $connections = array();
/**
* The default connection name.
*
* @var string
*/
protected $default;
/**
* Create a new connection resolver instance.
*
* @param array $connections
* @return void
*/
public function __construct(array $connections = array())
{
foreach ($connections as $name => $connection)
{
$this->addConnection($name, $connection);
}
}
/**
* Get a database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null)
{
if (is_null($name)) $name = $this->getDefaultConnection();
return $this->connections[$name];
}
/**
* Add a connection to the resolver.
*
* @param string $name
* @param \Illuminate\Database\Connection $connection
* @return void
*/
public function addConnection($name, Connection $connection)
{
$this->connections[$name] = $connection;
}
/**
* Check if a connection has been registered.
*
* @param string $name
* @return bool
*/
public function hasConnection($name)
{
return isset($this->connections[$name]);
}
/**
* Get the default connection name.
*
* @return string
*/
public function getDefaultConnection()
{
return $this->default;
}
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setDefaultConnection($name)
{
$this->default = $name;
}
}

View File

@@ -0,0 +1,28 @@
<?php namespace Illuminate\Database;
interface ConnectionResolverInterface {
/**
* Get a database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null);
/**
* Get the default connection name.
*
* @return string
*/
public function getDefaultConnection();
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setDefaultConnection($name);
}

View File

@@ -0,0 +1,124 @@
<?php namespace Illuminate\Database\Connectors;
use PDO;
use Illuminate\Container\Container;
use Illuminate\Database\MySqlConnection;
use Illuminate\Database\SQLiteConnection;
use Illuminate\Database\PostgresConnection;
use Illuminate\Database\SqlServerConnection;
class ConnectionFactory {
/**
* The IoC container instance.
*
* @var \Illuminate\Container\Container
*/
protected $container;
/**
* Create a new connection factory instance.
*
* @param \Illuminate\Container\Container $container
* @return void
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Establish a PDO connection based on the configuration.
*
* @param array $config
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function make(array $config, $name = null)
{
$config = $this->parseConfig($config, $name);
$pdo = $this->createConnector($config)->connect($config);
return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config);
}
/**
* Parse and prepare the database configuration.
*
* @param array $config
* @param string $name
* @return array
*/
protected function parseConfig(array $config, $name)
{
return array_add(array_add($config, 'prefix', ''), 'name', $name);
}
/**
* Create a connector instance based on the configuration.
*
* @param array $config
* @return \Illuminate\Database\Connectors\ConnectorInterface
*/
public function createConnector(array $config)
{
if ( ! isset($config['driver']))
{
throw new \InvalidArgumentException("A driver must be specified.");
}
switch ($config['driver'])
{
case 'mysql':
return new MySqlConnector;
case 'pgsql':
return new PostgresConnector;
case 'sqlite':
return new SQLiteConnector;
case 'sqlsrv':
return new SqlServerConnector;
}
throw new \InvalidArgumentException("Unsupported driver [{$config['driver']}]");
}
/**
* Create a new connection instance.
*
* @param string $driver
* @param PDO $connection
* @param string $database
* @param string $prefix
* @param array $config
* @return \Illuminate\Database\Connection
*/
protected function createConnection($driver, PDO $connection, $database, $prefix = '', $config = null)
{
if ($this->container->bound($key = "db.connection.{$driver}"))
{
return $this->container->make($key, array($connection, $database, $prefix, $config));
}
switch ($driver)
{
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
case 'sqlite':
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
}
throw new \InvalidArgumentException("Unsupported driver [$driver]");
}
}

View File

@@ -0,0 +1,71 @@
<?php namespace Illuminate\Database\Connectors;
use PDO;
class Connector {
/**
* The default PDO connection options.
*
* @var array
*/
protected $options = array(
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false,
);
/**
* Get the PDO options based on the configuration.
*
* @param array $config
* @return array
*/
public function getOptions(array $config)
{
$options = array_get($config, 'options', array());
return array_diff_key($this->options, $options) + $options;
}
/**
* Create a new PDO connection.
*
* @param string $dsn
* @param array $config
* @param array $options
* @return PDO
*/
public function createConnection($dsn, array $config, array $options)
{
$username = array_get($config, 'username');
$password = array_get($config, 'password');
return new PDO($dsn, $username, $password, $options);
}
/**
* Get the default PDO connection options.
*
* @return array
*/
public function getDefaultOptions()
{
return $this->options;
}
/**
* Set the default PDO connection options.
*
* @param array $options
* @return void
*/
public function setDefaultOptions(array $options)
{
$this->options = $options;
}
}

View File

@@ -0,0 +1,13 @@
<?php namespace Illuminate\Database\Connectors;
interface ConnectorInterface {
/**
* Establish a database connection.
*
* @param array $config
* @return PDO
*/
public function connect(array $config);
}

View File

@@ -0,0 +1,67 @@
<?php namespace Illuminate\Database\Connectors;
class MySqlConnector extends Connector implements ConnectorInterface {
/**
* Establish a database connection.
*
* @param array $options
* @return PDO
*/
public function connect(array $config)
{
$dsn = $this->getDsn($config);
// We need to grab the PDO options that should be used while making the brand
// new connection instance. The PDO options control various aspects of the
// connection's behavior, and some might be specified by the developers.
$options = $this->getOptions($config);
$connection = $this->createConnection($dsn, $config, $options);
$collation = $config['collation'];
$charset = $config['charset'];
// Next we will set the "names" and "collation" on the clients connections so
// a correct character set will be used by this client. The collation also
// is set on the server but needs to be set here on this client objects.
$names = "set names '$charset' collate '$collation'";
$connection->prepare($names)->execute();
return $connection;
}
/**
* Create a DSN string from a configuration.
*
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
// First we will create the basic DSN setup as well as the port if it is in
// in the configuration options. This will give us the basic DSN we will
// need to establish the PDO connections and return them back for use.
extract($config);
$dsn = "mysql:host={$host};dbname={$database}";
if (isset($config['port']))
{
$dsn .= ";port={$port}";
}
// Sometimes the developer may specify the specific UNIX socket that should
// be used. If that is the case we will add that option to the string we
// have created so that it gets utilized while the connection is made.
if (isset($config['unix_socket']))
{
$dsn .= ";unix_socket={$config['unix_socket']}";
}
return $dsn;
}
}

View File

@@ -0,0 +1,82 @@
<?php namespace Illuminate\Database\Connectors;
use PDO;
class PostgresConnector extends Connector implements ConnectorInterface {
/**
* The default PDO connection options.
*
* @var array
*/
protected $options = array(
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
);
/**
* Establish a database connection.
*
* @param array $options
* @return PDO
*/
public function connect(array $config)
{
// First we'll create the basic DSN and connection instance connecting to the
// using the configuration option specified by the developer. We will also
// set the default character set on the connections to UTF-8 by default.
$dsn = $this->getDsn($config);
$options = $this->getOptions($config);
$connection = $this->createConnection($dsn, $config, $options);
$charset = $config['charset'];
$connection->prepare("set names '$charset'")->execute();
// Unlike MySQL, Postgres allows the concept of "schema" and a default schema
// may have been specified on the connections. If that is the case we will
// set the default schema search paths to the specified database schema.
if (isset($config['schema']))
{
$schema = $config['schema'];
$connection->prepare("set search_path to {$schema}")->execute();
}
return $connection;
}
/**
* Create a DSN string from a configuration.
*
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
// First we will create the basic DSN setup as well as the port if it is in
// in the configuration options. This will give us the basic DSN we will
// need to establish the PDO connections and return them back for use.
extract($config);
$host = isset($host) ? "host={$host};" : '';
$dsn = "pgsql:{$host}dbname={$database}";
// If a port was specified, we will add it to this Postgres DSN connections
// format. Once we have done that we are ready to return this connection
// string back out for usage, as this has been fully constructed here.
if (isset($config['port']))
{
$dsn .= ";port={$port}";
}
return $dsn;
}
}

View File

@@ -0,0 +1,36 @@
<?php namespace Illuminate\Database\Connectors;
class SQLiteConnector extends Connector implements ConnectorInterface {
/**
* Establish a database connection.
*
* @param array $options
* @return PDO
*/
public function connect(array $config)
{
$options = $this->getOptions($config);
// SQLite supports "in-memory" databases that only last as long as the owning
// connection does. These are useful for tests or for short lifetime store
// querying. In-memory databases may only have a single open connection.
if ($config['database'] == ':memory:')
{
return $this->createConnection('sqlite::memory:', $config, $options);
}
$path = realpath($config['database']);
// Here we'll verify that the SQLite database exists before we gooing further
// as the developer probably wants to know if the database exists and this
// SQLite driver will not throw any exception if it does not by default.
if ($path === false)
{
throw new \InvalidArgumentException("Database does not exist.");
}
return $this->createConnection("sqlite:{$path}", $config, $options);
}
}

View File

@@ -0,0 +1,67 @@
<?php namespace Illuminate\Database\Connectors;
use PDO;
class SqlServerConnector extends Connector implements ConnectorInterface {
/**
* The PDO connection options.
*
* @var array
*/
protected $options = array(
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
);
/**
* Establish a database connection.
*
* @param array $options
* @return PDO
*/
public function connect(array $config)
{
$options = $this->getOptions($config);
return $this->createConnection($this->getDsn($config), $config, $options);
}
/**
* Create a DSN string from a configuration.
*
* @param array $config
* @return string
*/
protected function getDsn(array $config)
{
extract($config);
// First we will create the basic DSN setup as well as the port if it is in
// in the configuration options. This will give us the basic DSN we will
// need to establish the PDO connections and return them back for use.
$port = isset($config['port']) ? ','.$port : '';
if (in_array('dblib', $this->getAvailableDrivers()))
{
return "dblib:host={$host}{$port};dbname={$database}";
}
else
{
return "sqlsrv:Server={$host}{$port};Database={$database}";
}
}
/**
* Get the available PDO drivers.
*
* @return array
*/
protected function getAvailableDrivers()
{
return PDO::getAvailableDrivers();
}
}

View File

@@ -0,0 +1,49 @@
<?php namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
class BaseCommand extends Command {
/**
* Get the path to the migration directory.
*
* @return string
*/
protected function getMigrationPath()
{
$path = $this->input->getOption('path');
// First, we will check to see if a path option has been defined. If it has
// we will use the path relative to the root of this installation folder
// so that migrations may be run for any path within the applications.
if ( ! is_null($path))
{
return $this->laravel['path.base'].'/'.$path;
}
$package = $this->input->getOption('package');
// If the package is in the list of migration paths we received we will put
// the migrations in that path. Otherwise, we will assume the package is
// is in the package directories and will place them in that location.
if ( ! is_null($package))
{
return $this->packagePath.'/'.$package.'/src/migrations';
}
$bench = $this->input->getOption('bench');
// Finally we will check for the workbench option, which is a shortcut into
// specifying the full path for a "workbench" project. Workbenches allow
// developers to develop packages along side a "standard" app install.
if ( ! is_null($bench))
{
$path = "/workbench/{$bench}/src/migrations";
return $this->laravel['path.base'].$path;
}
return $this->laravel['path'].'/database/migrations';
}
}

View File

@@ -0,0 +1,69 @@
<?php namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Database\Migrations\MigrationRepositoryInterface;
class InstallCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create the migration repository';
/**
* The repository instance.
*
* @var \Illuminate\Database\Console\Migrations\MigrationRepositoryInterface
*/
protected $repository;
/**
* Create a new migration install command instance.
*
* @param \Illuminate\Database\Console\Migrations\MigrationRepositoryInterface $repository
* @return void
*/
public function __construct(MigrationRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$this->repository->setSource($this->input->getOption('database'));
$this->repository->createRepository();
$this->info("Migration table created successfully.");
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
);
}
}

View File

@@ -0,0 +1,126 @@
<?php namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Illuminate\Database\Migrations\MigrationCreator;
class MakeCommand extends BaseCommand {
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:make';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new migration file';
/**
* The migration creator instance.
*
* @var \Illuminate\Database\Migrations\MigrationCreator
*/
protected $creator;
/**
* The path to the packages directory (vendor).
*
* @var string
*/
protected $packagePath;
/**
* Create a new migration install command instance.
*
* @param \Illuminate\Database\Migrations\MigrationCreator $creator
* @param string $packagePath
* @return void
*/
public function __construct(MigrationCreator $creator, $packagePath)
{
parent::__construct();
$this->creator = $creator;
$this->packagePath = $packagePath;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
// It's possible for the developer to specify the tables to modify in this
// schema operation. The developer may also specify if this table needs
// to be freshly created so we can create the appropriate migrations.
$name = $this->input->getArgument('name');
$table = $this->input->getOption('table');
$create = $this->input->getOption('create');
// Now we are ready to write the migration out to disk. Once we've written
// the migration out, we will dump-autoload for the entire framework to
// make sure that the migrations are registered by the class loaders.
$this->writeMigration($name, $table, $create);
$this->call('dump-autoload');
}
/**
* Write the migration file to disk.
*
* @param string $name
* @param string $table
* @param bool $create
* @return string
*/
protected function writeMigration($name, $table, $create)
{
$path = $this->getMigrationPath();
$file = pathinfo($this->creator->create($name, $path, $table, $create), PATHINFO_FILENAME);
$this->line("<info>Created Migration:</info> $file");
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return array(
array('name', InputArgument::REQUIRED, 'The name of the migration'),
);
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('bench', null, InputOption::VALUE_OPTIONAL, 'The workbench the migration belongs to.', null),
array('create', null, InputOption::VALUE_NONE, 'The table needs to be created.'),
array('package', null, InputOption::VALUE_OPTIONAL, 'The package the migration belongs to.', null),
array('path', null, InputOption::VALUE_OPTIONAL, 'Where to store the migration.', null),
array('table', null, InputOption::VALUE_OPTIONAL, 'The table to migrate.'),
);
}
}

View File

@@ -0,0 +1,125 @@
<?php namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Database\Migrations\Migrator;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class MigrateCommand extends BaseCommand {
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the database migrations';
/**
* The migrator instance.
*
* @var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* The path to the packages directory (vendor).
*/
protected $packagePath;
/**
* Create a new migration command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @param string $packagePath
* @return void
*/
public function __construct(Migrator $migrator, $packagePath)
{
parent::__construct();
$this->migrator = $migrator;
$this->packagePath = $packagePath;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$this->prepareDatabase();
// The pretend option can be used for "simulating" the migration and grabbing
// the SQL queries that would fire if the migration were to be run against
// a database for real, which is helpful for double checking migrations.
$pretend = $this->input->getOption('pretend');
$path = $this->getMigrationPath();
$this->migrator->run($path, $pretend);
// Once the migrator has run we will grab the note output and send it out to
// the console screen, since the migrator itself functions without having
// any instances of the OutputInterface contract passed into the class.
foreach ($this->migrator->getNotes() as $note)
{
$this->output->writeln($note);
}
// Finally, if the "seed" option has been given, we will re-run the database
// seed task to re-populate the database, which is convenient when adding
// a migration and a seed at the same time, as it is only this command.
if ($this->input->getOption('seed'))
{
$this->call('db:seed');
}
}
/**
* Prepare the migration database for running.
*
* @return void
*/
protected function prepareDatabase()
{
$this->migrator->setConnection($this->input->getOption('database'));
if ( ! $this->migrator->repositoryExists())
{
$options = array('--database' => $this->input->getOption('database'));
$this->call('migrate:install', $options);
}
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('bench', null, InputOption::VALUE_OPTIONAL, 'The name of the workbench to migrate.', null),
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
array('path', null, InputOption::VALUE_OPTIONAL, 'The path to migration files.', null),
array('package', null, InputOption::VALUE_OPTIONAL, 'The package to migrate.', null),
array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'),
array('seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'),
);
}
}

View File

@@ -0,0 +1,58 @@
<?php namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
class RefreshCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:refresh';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Reset and re-run all migrations';
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$database = $this->input->getOption('database');
$this->call('migrate:reset', array('--database' => $database));
// The refresh command is essentially just a brief aggregate of a few other of
// the migration commands and just provides a convenient wrapper to execute
// them in succession. We'll also see if we need to res-eed the database.
$this->call('migrate', array('--database' => $database));
if ($this->input->getOption('seed'))
{
$this->call('db:seed', array('--database' => $database));
}
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
array('seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'),
);
}
}

View File

@@ -0,0 +1,84 @@
<?php namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Database\Migrations\Migrator;
use Symfony\Component\Console\Input\InputOption;
class ResetCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:reset';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Rollback all database migrations';
/**
* The migrator instance.
*
* @var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* Create a new migration rollback command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @return void
*/
public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$this->migrator->setConnection($this->input->getOption('database'));
$pretend = $this->input->getOption('pretend');
while (true)
{
$count = $this->migrator->rollback($pretend);
// Once the migrator has run we will grab the note output and send it out to
// the console screen, since the migrator itself functions without having
// any instances of the OutputInterface contract passed into the class.
foreach ($this->migrator->getNotes() as $note)
{
$this->output->writeln($note);
}
if ($count == 0) break;
}
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'),
);
}
}

View File

@@ -0,0 +1,79 @@
<?php namespace Illuminate\Database\Console\Migrations;
use Illuminate\Console\Command;
use Illuminate\Database\Migrations\Migrator;
use Symfony\Component\Console\Input\InputOption;
class RollbackCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'migrate:rollback';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Rollback the last database migration';
/**
* The migrator instance.
*
* @var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* Create a new migration rollback command instance.
*
* @param \Illuminate\Database\Migrations\Migrator $migrator
* @return void
*/
public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$this->migrator->setConnection($this->input->getOption('database'));
$pretend = $this->input->getOption('pretend');
$this->migrator->rollback($pretend);
// Once the migrator has run we will grab the note output and send it out to
// the console screen, since the migrator itself functions without having
// any instances of the OutputInterface contract passed into the class.
foreach ($this->migrator->getNotes() as $note)
{
$this->output->writeln($note);
}
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'),
);
}
}

View File

@@ -0,0 +1,96 @@
<?php namespace Illuminate\Database\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
class SeedCommand extends Command {
/**
* The console command name.
*
* @var string
*/
protected $name = 'db:seed';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Seed the database with records';
/**
* The connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* Create a new database seed command instance.
*
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @return void
*/
public function __construct(Resolver $resolver)
{
parent::__construct();
$this->resolver = $resolver;
}
/**
* Execute the console command.
*
* @return void
*/
public function fire()
{
$this->resolver->setDefaultConnection($this->getDatabase());
$this->getSeeder()->run();
$this->info('Database seeded!');
}
/**
* Get a seeder instance from the container.
*
* @return \Illuminate\Database\Seeder
*/
protected function getSeeder()
{
$class = $this->laravel->make($this->input->getOption('class'));
return $class->setContainer($this->laravel)->setCommand($this);
}
/**
* Get the name of the database connection to use.
*
* @return string
*/
protected function getDatabase()
{
$database = $this->input->getOption('database');
return $database ?: $this->laravel['config']['database.default'];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return array(
array('class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'DatabaseSeeder'),
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'),
);
}
}

View File

@@ -0,0 +1,222 @@
<?php namespace Illuminate\Database;
use Illuminate\Support\Manager;
use Illuminate\Database\Connectors\ConnectionFactory;
class DatabaseManager implements ConnectionResolverInterface {
/**
* The application instance.
*
* @var \Illuminate\Foundation\Application
*/
protected $app;
/**
* The database connection factory instance.
*
* @var \Illuminate\Database\Connectors\ConnectionFactory
*/
protected $factory;
/**
* The active connection instances.
*
* @var array
*/
protected $connections = array();
/**
* The custom connection resolvers.
*
* @var array
*/
protected $extensions = array();
/**
* Create a new database manager instance.
*
* @param \Illuminate\Foundation\Application $app
* @param \Illuminate\Database\Connectors\ConnectionFactory $factory
* @return void
*/
public function __construct($app, ConnectionFactory $factory)
{
$this->app = $app;
$this->factory = $factory;
}
/**
* Get a database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function connection($name = null)
{
$name = $name ?: $this->getDefaultConnection();
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if ( ! isset($this->connections[$name]))
{
$connection = $this->makeConnection($name);
$this->connections[$name] = $this->prepare($connection);
}
return $this->connections[$name];
}
/**
* Reconnect to the given database.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function reconnect($name = null)
{
$name = $name ?: $this->getDefaultConnection();
unset($this->connections[$name]);
return $this->connection($name);
}
/**
* Make the database connection instance.
*
* @param string $name
* @return \Illuminate\Database\Connection
*/
protected function makeConnection($name)
{
$config = $this->getConfig($name);
// First we will check by the connection name to see if an extension has been
// registered specifically for that connection. If it has we will call the
// Closure and pass it the config allowing it to resolve the connection.
if (isset($this->extensions[$name]))
{
return call_user_func($this->extensions[$name], $config);
}
$driver = $config['driver'];
// Next we will check to see if an extension has been registered for a driver
// and will call the Closure if so, which allows us to have a more generic
// resolver for the drivers themselves which applies to all connections.
if (isset($this->extensions[$driver]))
{
return call_user_func($this->extensions[$driver], $config);
}
return $this->factory->make($config, $name);
}
/**
* Prepare the database connection instance.
*
* @param \Illuminate\Database\Connection $connection
* @return \Illuminate\Database\Connection
*/
protected function prepare(Connection $connection)
{
$connection->setFetchMode($this->app['config']['database.fetch']);
if ($this->app->bound('events'))
{
$connection->setEventDispatcher($this->app['events']);
}
// The database connection can also utilize a cache manager instance when cache
// functionality is used on queries, which provides an expressive interface
// to caching both fluent queries and Eloquent queries that are executed.
$app = $this->app;
$connection->setCacheManager(function() use ($app)
{
return $app['cache'];
});
// We will setup a Closure to resolve the paginator instance on the connection
// since the Paginator isn't sued on every request and needs quite a few of
// our dependencies. It'll be more efficient to lazily resolve instances.
$connection->setPaginator(function() use ($app)
{
return $app['paginator'];
});
return $connection;
}
/**
* Get the configuration for a connection.
*
* @param string $name
* @return array
*/
protected function getConfig($name)
{
$name = $name ?: $this->getDefaultConnection();
// To get the database connection configuration, we will just pull each of the
// connection configurations and get the configurations for the given name.
// If the configuration doesn't exist, we'll throw an exception and bail.
$connections = $this->app['config']['database.connections'];
if (is_null($config = array_get($connections, $name)))
{
throw new \InvalidArgumentException("Database [$name] not configured.");
}
return $config;
}
/**
* Get the default connection name.
*
* @return string
*/
public function getDefaultConnection()
{
return $this->app['config']['database.default'];
}
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setDefaultConnection($name)
{
$this->app['config']['database.default'] = $name;
}
/**
* Register an extension connection resolver.
*
* @param string $name
* @param callable $resolver
* @return void
*/
public function extend($name, $resolver)
{
$this->extensions[$name] = $resolver;
}
/**
* Dynamically pass methods to the default connection.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return call_user_func_array(array($this->connection(), $method), $parameters);
}
}

View File

@@ -0,0 +1,45 @@
<?php namespace Illuminate\Database;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Connectors\ConnectionFactory;
class DatabaseServiceProvider extends ServiceProvider {
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
// The connection factory is used to create the actual connection instances on
// the database. We will inject the factory into the manager so that it may
// make the connections while they are actually needed and not of before.
$this->app['db.factory'] = $this->app->share(function($app)
{
return new ConnectionFactory($app);
});
// The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections.
$this->app['db'] = $this->app->share(function($app)
{
return new DatabaseManager($app, $app['db.factory']);
});
}
}

View File

@@ -0,0 +1,737 @@
<?php namespace Illuminate\Database\Eloquent;
use Closure;
use DateTime;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
class Builder {
/**
* The base query builder instance.
*
* @var \Illuminate\Database\Query\Builder
*/
protected $query;
/**
* The model being queried.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $model;
/**
* The relationships that should be eager loaded.
*
* @var array
*/
protected $eagerLoad = array();
/**
* The methods that should be returned from query builder.
*
* @var array
*/
protected $passthru = array(
'toSql', 'lists', 'insert', 'insertGetId', 'pluck',
'count', 'min', 'max', 'avg', 'sum', 'exists',
);
/**
* Create a new Eloquent query builder instance.
*
* @param \Illuminate\Database\Query\Builder $query
* @return void
*/
public function __construct(QueryBuilder $query)
{
$this->query = $query;
}
/**
* Find a model by its primary key.
*
* @param mixed $id
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|static|null
*/
public function find($id, $columns = array('*'))
{
$this->query->where($this->model->getKeyName(), '=', $id);
return $this->first($columns);
}
/**
* Find a model by its primary key or throw an exception.
*
* @param mixed $id
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|static
*/
public function findOrFail($id, $columns = array('*'))
{
if ( ! is_null($model = $this->find($id, $columns))) return $model;
throw new ModelNotFoundException;
}
/**
* Execute the query and get the first result.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|static|null
*/
public function first($columns = array('*'))
{
return $this->take(1)->get($columns)->first();
}
/**
* Execute the query and get the first result or throw an exception.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|static
*/
public function firstOrFail($columns = array('*'))
{
if ( ! is_null($model = $this->first($columns))) return $model;
throw new ModelNotFoundException;
}
/**
* Execute the query as a "select" statement.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function get($columns = array('*'))
{
$models = $this->getModels($columns);
// If we actually found models we will also eager load any relationships that
// have been specified as needing to be eager loaded, which will solve the
// n+1 query issue for the developers to avoid running a lot of queries.
if (count($models) > 0)
{
$models = $this->eagerLoadRelations($models);
}
return $this->model->newCollection($models);
}
/**
* Pluck a single column from the database.
*
* @param string $column
* @return mixed
*/
public function pluck($column)
{
$result = $this->first(array($column));
if ($result) return $result->{$column};
}
/**
* Get an array with the values of a given column.
*
* @param string $column
* @param string $key
* @return array
*/
public function lists($column, $key = null)
{
$results = $this->query->lists($column, $key);
// If the model has a mutator for the requested column, we will spin through
// the results and mutate the values so that the mutated version of these
// columns are returned as you would expect from these Eloquent models.
if ($this->model->hasGetMutator($column))
{
foreach ($results as $key => &$value)
{
$fill = array($column => $value);
$value = $this->model->newFromBuilder($fill)->$column;
}
}
return $results;
}
/**
* Get a paginator for the "select" statement.
*
* @param int $perPage
* @param array $columns
* @return \Illuminate\Pagination\Paginator
*/
public function paginate($perPage = null, $columns = array('*'))
{
$perPage = $perPage ?: $this->model->getPerPage();
$paginator = $this->query->getConnection()->getPaginator();
if (isset($this->query->groups))
{
return $this->groupedPaginate($paginator, $perPage, $columns);
}
else
{
return $this->ungroupedPaginate($paginator, $perPage, $columns);
}
}
/**
* Get a paginator for a grouped statement.
*
* @param \Illuminate\Pagination\Environment $paginator
* @param int $perPage
* @param array $columns
* @return \Illuminate\Pagination\Paginator
*/
protected function groupedPaginate($paginator, $perPage, $columns)
{
$results = $this->get($columns)->all();
return $this->query->buildRawPaginator($paginator, $results, $perPage);
}
/**
* Get a paginator for an ungrouped statement.
*
* @param \Illuminate\Pagination\Environment $paginator
* @param int $perPage
* @param array $columns
* @return \Illuminate\Pagination\Paginator
*/
protected function ungroupedPaginate($paginator, $perPage, $columns)
{
$total = $this->query->getPaginationCount();
// Once we have the paginator we need to set the limit and offset values for
// the query so we can get the properly paginated items. Once we have an
// array of items we can create the paginator instances for the items.
$page = $paginator->getCurrentPage();
$this->query->forPage($page, $perPage);
return $paginator->make($this->get($columns)->all(), $total, $perPage);
}
/**
* Update a record in the database.
*
* @param array $values
* @return int
*/
public function update(array $values)
{
return $this->query->update($this->addUpdatedAtColumn($values));
}
/**
* Increment a column's value by a given amount.
*
* @param string $column
* @param int $amount
* @param array $extra
* @return int
*/
public function increment($column, $amount = 1, array $extra = array())
{
$extra = $this->addUpdatedAtColumn($extra);
return $this->query->increment($column, $amount, $extra);
}
/**
* Decrement a column's value by a given amount.
*
* @param string $column
* @param int $amount
* @param array $extra
* @return int
*/
public function decrement($column, $amount = 1, array $extra = array())
{
$extra = $this->addUpdatedAtColumn($extra);
return $this->query->decrement($column, $amount, $extra);
}
/**
* Add the "updated at" column to an array of values.
*
* @param array $values
* @return array
*/
protected function addUpdatedAtColumn(array $values)
{
if ( ! $this->model->usesTimestamps()) return $values;
$column = $this->model->getUpdatedAtColumn();
return array_add($values, $column, $this->model->freshTimestampString());
}
/**
* Delete a record from the database.
*
* @return int
*/
public function delete()
{
if ($this->model->isSoftDeleting())
{
return $this->softDelete();
}
else
{
return $this->query->delete();
}
}
/**
* Soft delete the record in the database.
*
* @return int
*/
protected function softDelete()
{
$column = $this->model->getDeletedAtColumn();
return $this->update(array($column => $this->model->freshTimestampString()));
}
/**
* Force a delete on a set of soft deleted models.
*
* @return int
*/
public function forceDelete()
{
return $this->query->delete();
}
/**
* Restore the soft-deleted model instances.
*
* @return int
*/
public function restore()
{
if ($this->model->isSoftDeleting())
{
$column = $this->model->getDeletedAtColumn();
return $this->update(array($column => null));
}
}
/**
* Include the soft deleted models in the results.
*
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function withTrashed()
{
$column = $this->model->getQualifiedDeletedAtColumn();
foreach ($this->query->wheres as $key => $where)
{
// If the where clause is a soft delete date constraint, we will remove it from
// the query and reset the keys on the wheres. This allows this developer to
// include deleted model in a relationship result set that is lazy loaded.
if ($this->isSoftDeleteConstraint($where, $column))
{
unset($this->query->wheres[$key]);
$this->query->wheres = array_values($this->query->wheres);
}
}
return $this;
}
/**
* Force the result set to only included soft deletes.
*
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function onlyTrashed()
{
$this->withTrashed();
$this->query->whereNotNull($this->model->getQualifiedDeletedAtColumn());
return $this;
}
/**
* Determine if the given where clause is a soft delete constraint.
*
* @param array $where
* @param string $column
* @return bool
*/
protected function isSoftDeleteConstraint(array $where, $column)
{
return $where['column'] == $column and $where['type'] == 'Null';
}
/**
* Get the hydrated models without eager loading.
*
* @param array $columns
* @return array|static[]
*/
public function getModels($columns = array('*'))
{
// First, we will simply get the raw results from the query builders which we
// can use to populate an array with Eloquent models. We will pass columns
// that should be selected as well, which are typically just everything.
$results = $this->query->get($columns);
$connection = $this->model->getConnectionName();
$models = array();
// Once we have the results, we can spin through them and instantiate a fresh
// model instance for each records we retrieved from the database. We will
// also set the proper connection name for the model after we create it.
foreach ($results as $result)
{
$models[] = $model = $this->model->newFromBuilder($result);
$model->setConnection($connection);
}
return $models;
}
/**
* Eager load the relationships for the models.
*
* @param array $models
* @return array
*/
public function eagerLoadRelations(array $models)
{
foreach ($this->eagerLoad as $name => $constraints)
{
// For nested eager loads we'll skip loading them here and they will be set as an
// eager load on the query to retrieve the relation so that they will be eager
// loaded on that query, because that is where they get hydrated as models.
if (strpos($name, '.') === false)
{
$models = $this->loadRelation($models, $name, $constraints);
}
}
return $models;
}
/**
* Eagerly load the relationship on a set of models.
*
* @param array $models
* @param string $name
* @param \Closure $constraints
* @return array
*/
protected function loadRelation(array $models, $name, Closure $constraints)
{
// First we will "back up" the existing where conditions on the query so we can
// add our eager constraints. Then we will merge the wheres that were on the
// query back to it in order that any where conditions might be specified.
$relation = $this->getRelation($name);
$relation->addEagerConstraints($models);
call_user_func($constraints, $relation);
$models = $relation->initRelation($models, $name);
// Once we have the results, we just match those back up to their parent models
// using the relationship instance. Then we just return the finished arrays
// of models which have been eagerly hydrated and are readied for return.
$results = $relation->get();
return $relation->match($models, $results, $name);
}
/**
* Get the relation instance for the given relation name.
*
* @param string $relation
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function getRelation($relation)
{
$me = $this;
// We want to run a relationship query without any constrains so that we will
// not have to remove these where clauses manually which gets really hacky
// and is error prone while we remove the developer's own where clauses.
$query = Relation::noConstraints(function() use ($me, $relation)
{
return $me->getModel()->$relation();
});
$nested = $this->nestedRelations($relation);
// If there are nested relationships set on the query, we will put those onto
// the query instances so that they can be handled after this relationship
// is loaded. In this way they will all trickle down as they are loaded.
if (count($nested) > 0)
{
$query->getQuery()->with($nested);
}
return $query;
}
/**
* Get the deeply nested relations for a given top-level relation.
*
* @param string $relation
* @return array
*/
protected function nestedRelations($relation)
{
$nested = array();
// We are basically looking for any relationships that are nested deeper than
// the given top-level relationship. We will just check for any relations
// that start with the given top relations and adds them to our arrays.
foreach ($this->eagerLoad as $name => $constraints)
{
if ($this->isNested($name, $relation))
{
$nested[substr($name, strlen($relation.'.'))] = $constraints;
}
}
return $nested;
}
/**
* Determine if the relationship is nested.
*
* @param string $name
* @param string $relation
* @return bool
*/
protected function isNested($name, $relation)
{
$dots = str_contains($name, '.');
return $dots and starts_with($name, $relation) and $name != $relation;
}
/**
* Add a relationship count condition to the query.
*
* @param string $relation
* @param string $operator
* @param int $count
* @param string $boolean
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function has($relation, $operator = '>=', $count = 1, $boolean = 'and')
{
$instance = $this->model->$relation();
$query = $instance->getRelationCountQuery($instance->getRelated()->newQuery());
$this->query->mergeBindings($query->getQuery());
return $this->where(new Expression('('.$query->toSql().')'), $operator, $count, $boolean);
}
/**
* Add a relationship count condition to the query with an "or".
*
* @param string $relation
* @param string $operator
* @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orHas($relation, $operator = '>=', $count = 1)
{
return $this->has($relation, $operator, $count, 'or');
}
/**
* Set the relationships that should be eager loaded.
*
* @param dynamic $relations
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function with($relations)
{
if (is_string($relations)) $relations = func_get_args();
$eagers = $this->parseRelations($relations);
$this->eagerLoad = array_merge($this->eagerLoad, $eagers);
return $this;
}
/**
* Parse a list of relations into individuals.
*
* @param array $relations
* @return array
*/
protected function parseRelations(array $relations)
{
$results = array();
foreach ($relations as $name => $constraints)
{
// If the "relation" value is actually a numeric key, we can assume that no
// constraints have been specified for the eager load and we'll just put
// an empty Closure with the loader so that we can treat all the same.
if (is_numeric($name))
{
$f = function() {};
list($name, $constraints) = array($constraints, $f);
}
// We need to separate out any nested includes. Which allows the developers
// to load deep relationships using "dots" without stating each level of
// the relationship with its own key in the array of eager load names.
$results = $this->parseNested($name, $results);
$results[$name] = $constraints;
}
return $results;
}
/**
* Parse the nested relationships in a relation.
*
* @param string $name
* @param array $results
* @return array
*/
protected function parseNested($name, $results)
{
$progress = array();
// If the relation has already been set on the result array, we will not set it
// again, since that would override any constraints that were already placed
// on the relationships. We will only set the ones that are not specified.
foreach (explode('.', $name) as $segment)
{
$progress[] = $segment;
if ( ! isset($results[$last = implode('.', $progress)]))
{
$results[$last] = function() {};
}
}
return $results;
}
/**
* Get the underlying query builder instance.
*
* @return \Illuminate\Database\Query\Builder|static
*/
public function getQuery()
{
return $this->query;
}
/**
* Set the underlying query builder instance.
*
* @param \Illuminate\Database\Query\Builder $query
* @return void
*/
public function setQuery($query)
{
$this->query = $query;
}
/**
* Get the relationships being eagerly loaded.
*
* @return array
*/
public function getEagerLoads()
{
return $this->eagerLoad;
}
/**
* Set the relationships being eagerly loaded.
*
* @param array $eagerLoad
* @return void
*/
public function setEagerLoads(array $eagerLoad)
{
$this->eagerLoad = $eagerLoad;
}
/**
* Get the model instance being queried.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function getModel()
{
return $this->model;
}
/**
* Set a model instance for the model being queried.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Builder
*/
public function setModel(Model $model)
{
$this->model = $model;
$this->query->from($model->getTable());
return $this;
}
/**
* Dynamically handle calls into the query instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (method_exists($this->model, $scope = 'scope'.ucfirst($method)))
{
array_unshift($parameters, $this);
call_user_func_array(array($this->model, $scope), $parameters);
}
else
{
$result = call_user_func_array(array($this->query, $method), $parameters);
}
return in_array($method, $this->passthru) ? $result : $this;
}
}

View File

@@ -0,0 +1,84 @@
<?php namespace Illuminate\Database\Eloquent;
use Illuminate\Support\Collection as BaseCollection;
class Collection extends BaseCollection {
/**
* Find a model in the collection by key.
*
* @param mixed $key
* @param mixed $default
* @return \Illuminate\Database\Eloquent\Model
*/
public function find($key, $default = null)
{
return array_first($this->items, function($key, $model) use ($key)
{
return $model->getKey() == $key;
}, $default);
}
/**
* Load a set of relationships onto the collection.
*
* @param dynamic string
* @return void
*/
public function load()
{
if (count($this->items) > 0)
{
$query = $this->first()->newQuery()->with(func_get_args());
$this->items = $query->eagerLoadRelations($this->items);
}
}
/**
* Add an item to the collection.
*
* @param mixed $item
* @return \Illuminate\Database\Eloquent\Collection
*/
public function add($item)
{
$this->items[] = $item;
return $this;
}
/**
* Determine if a key exists in the collection.
*
* @param mixed $key
* @return bool
*/
public function contains($key)
{
return ! is_null($this->find($key));
}
/**
* Fetch a nested element of the collection.
*
* @param string $key
* @return \Illuminate\Support\Collection
*/
public function fetch($key)
{
return new static(array_fetch($this->toArray(), $key));
}
/**
* Get the array of primary keys
*
* @return array
*/
public function modelKeys()
{
return array_map(function($m) { return $m->getKey(); }, $this->items);
}
}

View File

@@ -0,0 +1,3 @@
<?php namespace Illuminate\Database\Eloquent;
class MassAssignmentException extends \RuntimeException {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
<?php namespace Illuminate\Database\Eloquent;
class ModelNotFoundException extends \RuntimeException {}

View File

@@ -0,0 +1,221 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use LogicException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
class BelongsTo extends Relation {
/**
* The foreign key of the parent model.
*
* @var string
*/
protected $foreignKey;
/**
* The name of the relationship.
*
* @var string
*/
protected $relation;
/**
* Create a new has many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $relation
* @return void
*/
public function __construct(Builder $query, Model $parent, $foreignKey, $relation)
{
$this->relation = $relation;
$this->foreignKey = $foreignKey;
parent::__construct($query, $parent);
}
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return $this->query->first();
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
if (static::$constraints)
{
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$key = $this->related->getKeyName();
$table = $this->related->getTable();
$this->query->where($table.'.'.$key, '=', $this->parent->{$this->foreignKey});
}
}
/**
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationCountQuery(Builder $query)
{
throw new LogicException('Has method invalid on "belongsTo" relations.');
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
// We'll grab the primary key name of the related models since it could be set to
// a non-standard name and not "id". We will then construct the constraint for
// our eagerly loading query so it returns the proper models from execution.
$key = $this->related->getKeyName();
$key = $this->related->getTable().'.'.$key;
$this->query->whereIn($key, $this->getEagerModelKeys($models));
}
/**
* Gather the keys from an array of related models.
*
* @param array $models
* @return array
*/
protected function getEagerModelKeys(array $models)
{
$keys = array();
// First we need to gather all of the keys from the parent models so we know what
// to query for via the eager loading query. We will add them to an array then
// execute a "where in" statement to gather up all of those related records.
foreach ($models as $model)
{
if ( ! is_null($value = $model->{$this->foreignKey}))
{
$keys[] = $value;
}
}
// If there are no keys that were not null we will just return an array with 0 in
// it so the query doesn't fail, but will not return any results, which should
// be what this developer is expecting in a case where this happens to them.
if (count($keys) == 0)
{
return array(0);
}
return array_values(array_unique($keys));
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return void
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model)
{
$model->setRelation($relation, null);
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
$foreign = $this->foreignKey;
// First we will get to build a dictionary of the child models by their primary
// key of the relationship, then we can easily match the children back onto
// the parents using that dictionary and the primary key of the children.
$dictionary = array();
foreach ($results as $result)
{
$dictionary[$result->getKey()] = $result;
}
// Once we have the dictionary constructed, we can loop through all the parents
// and match back onto their children using these keys of the dictionary and
// the primary key of the children to map them onto the correct instances.
foreach ($models as $model)
{
if (isset($dictionary[$model->$foreign]))
{
$model->setRelation($relation, $dictionary[$model->$foreign]);
}
}
return $models;
}
/**
* Associate the model instance to the given parent.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function associate(Model $model)
{
$this->parent->setAttribute($this->foreignKey, $model->getKey());
return $this->parent->setRelation($this->relation, $model);
}
/**
* Update the parent model on the relationship.
*
* @param array $attributes
* @return mixed
*/
public function update(array $attributes)
{
$instance = $this->getResults();
return $instance->fill($attributes)->save();
}
/**
* Get the foreign key of the relationship.
*
* @return string
*/
public function getForeignKey()
{
return $this->foreignKey;
}
}

View File

@@ -0,0 +1,900 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use DateTime;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Collection;
class BelongsToMany extends Relation {
/**
* The intermediate table for the relation.
*
* @var string
*/
protected $table;
/**
* The foreign key of the parent model.
*
* @var string
*/
protected $foreignKey;
/**
* The associated key of the relation.
*
* @var string
*/
protected $otherKey;
/**
* The "name" of the relationship.
*
* @var string
*/
protected $relationName;
/**
* The pivot table columns to retrieve.
*
* @var array
*/
protected $pivotColumns = array();
/**
* Create a new has many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $table
* @param string $foreignKey
* @param string $otherKey
* @param string $relationName
* @return void
*/
public function __construct(Builder $query, Model $parent, $table, $foreignKey, $otherKey, $relationName = null)
{
$this->table = $table;
$this->otherKey = $otherKey;
$this->foreignKey = $foreignKey;
$this->relationName = $relationName;
parent::__construct($query, $parent);
}
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return $this->get();
}
/**
* Execute the query and get the first result.
*
* @param array $columns
* @return mixed
*/
public function first($columns = array('*'))
{
$results = $this->take(1)->get($columns);
return count($results) > 0 ? $results->first() : null;
}
/**
* Execute the query as a "select" statement.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Collection
*/
public function get($columns = array('*'))
{
// First we'll add the proper select columns onto the query so it is run with
// the proper columns. Then, we will get the results and hydrate out pivot
// models with the result of those columns as a separate model relation.
$select = $this->getSelectColumns($columns);
$models = $this->query->addSelect($select)->getModels();
$this->hydratePivotRelation($models);
// If we actually found models we will also eager load any relationships that
// have been specified as needing to be eager loaded. This will solve the
// n + 1 query problem for the developer and also increase performance.
if (count($models) > 0)
{
$models = $this->query->eagerLoadRelations($models);
}
return $this->related->newCollection($models);
}
/**
* Get a paginator for the "select" statement.
*
* @param int $perPage
* @param array $columns
* @return \Illuminate\Pagination\Paginator
*/
public function paginate($perPage = null, $columns = array('*'))
{
$this->query->addSelect($this->getSelectColumns($columns));
// When paginating results, we need to add the pivot columns to the query and
// then hydrate into the pivot objects once the results have been gathered
// from the database since this isn't performed by the Eloquent builder.
$pager = $this->query->paginate($perPage, $columns);
$this->hydratePivotRelation($pager->getItems());
return $pager;
}
/**
* Hydrate the pivot table relationship on the models.
*
* @param array $models
* @return void
*/
protected function hydratePivotRelation(array $models)
{
// To hydrate the pivot relationship, we will just gather the pivot attributes
// and create a new Pivot model, which is basically a dynamic model that we
// will set the attributes, table, and connections on so it they be used.
foreach ($models as $model)
{
$pivot = $this->newExistingPivot($this->cleanPivotAttributes($model));
$model->setRelation('pivot', $pivot);
}
}
/**
* Get the pivot attributes from a model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return array
*/
protected function cleanPivotAttributes(Model $model)
{
$values = array();
foreach ($model->getAttributes() as $key => $value)
{
// To get the pivots attributes we will just take any of the attributes which
// begin with "pivot_" and add those to this arrays, as well as unsetting
// them from the parent's models since they exist in a different table.
if (strpos($key, 'pivot_') === 0)
{
$values[substr($key, 6)] = $value;
unset($model->$key);
}
}
return $values;
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
$this->setJoin();
if (static::$constraints) $this->setWhere();
}
/**
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationCountQuery(Builder $query)
{
$this->setJoin($query);
return parent::getRelationCountQuery($query);
}
/**
* Set the select clause for the relation query.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function getSelectColumns(array $columns = array('*'))
{
if ($columns == array('*'))
{
$columns = array($this->related->getTable().'.*');
}
return array_merge($columns, $this->getAliasedPivotColumns());
}
/**
* Get the pivot columns for the relation.
*
* @return array
*/
protected function getAliasedPivotColumns()
{
$defaults = array($this->foreignKey, $this->otherKey);
// We need to alias all of the pivot columns with the "pivot_" prefix so we
// can easily extract them out of the models and put them into the pivot
// relationships when they are retrieved and hydrated into the models.
$columns = array();
foreach (array_merge($defaults, $this->pivotColumns) as $column)
{
$columns[] = $this->table.'.'.$column.' as pivot_'.$column;
}
return array_unique($columns);
}
/**
* Set the join clause for the relation query.
*
* @param \Illuminate\Database\Eloquent\Builder|null
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function setJoin($query = null)
{
$query = $query ?: $this->query;
// We need to join to the intermediate table on the related model's primary
// key column with the intermediate table's foreign key for the related
// model instance. Then we can set the "where" for the parent models.
$baseTable = $this->related->getTable();
$key = $baseTable.'.'.$this->related->getKeyName();
$query->join($this->table, $key, '=', $this->getOtherKey());
return $this;
}
/**
* Set the where clause for the relation query.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function setWhere()
{
$foreign = $this->getForeignKey();
$this->query->where($foreign, '=', $this->parent->getKey());
return $this;
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
$this->query->whereIn($this->getForeignKey(), $this->getKeys($models));
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return void
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model)
{
$model->setRelation($relation, $this->related->newCollection());
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
$dictionary = $this->buildDictionary($results);
// Once we have an array dictionary of child objects we can easily match the
// children back to their parent using the dictionary and the keys on the
// the parent models. Then we will return the hydrated models back out.
foreach ($models as $model)
{
if (isset($dictionary[$key = $model->getKey()]))
{
$collection = $this->related->newCollection($dictionary[$key]);
$model->setRelation($relation, $collection);
}
}
return $models;
}
/**
* Build model dictionary keyed by the relation's foreign key.
*
* @param \Illuminate\Database\Eloquent\Collection $results
* @return array
*/
protected function buildDictionary(Collection $results)
{
$foreign = $this->foreignKey;
// First we will build a dictionary of child models keyed by the foreign key
// of the relation so that we will easily and quickly match them to their
// parents without having a possibly slow inner loops for every models.
$dictionary = array();
foreach ($results as $result)
{
$dictionary[$result->pivot->$foreign][] = $result;
}
return $dictionary;
}
/**
* Touch all of the related models for the relationship.
*
* E.g.: Touch all roles associated with this user.
*
* @return void
*/
public function touch()
{
$key = $this->getRelated()->getKeyName();
$columns = $this->getRelatedFreshUpdate();
// If we actually have IDs for the relation, we will run the query to update all
// the related model's timestamps, to make sure these all reflect the changes
// to the parent models. This will help us keep any caching synced up here.
$ids = $this->getRelatedIds();
if (count($ids) > 0)
{
$this->getRelated()->newQuery()->whereIn($key, $ids)->update($columns);
}
}
/**
* Get all of the IDs for the related models.
*
* @return array
*/
public function getRelatedIds()
{
$related = $this->getRelated();
$fullKey = $related->getQualifiedKeyName();
return $this->getQuery()->select($fullKey)->lists($related->getKeyName());
}
/**
* Save a new model and attach it to the parent model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param array $joining
* @param bool $touch
* @return \Illuminate\Database\Eloquent\Model
*/
public function save(Model $model, array $joining = array(), $touch = true)
{
$model->save(array('touch' => false));
$this->attach($model->getKey(), $joining, $touch);
return $model;
}
/**
* Save an array of new models and attach them to the parent model.
*
* @param array $models
* @param array $joinings
* @return array
*/
public function saveMany(array $models, array $joinings = array())
{
foreach ($models as $key => $model)
{
$this->save($model, (array) array_get($joinings, $key), false);
}
$this->touchIfTouching();
return $models;
}
/**
* Create a new instance of the related model.
*
* @param array $attributes
* @param array $joining
* @param bool $touch
* @return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes, array $joining = array(), $touch = true)
{
$instance = $this->related->newInstance($attributes);
// Once we save the related model, we need to attach it to the base model via
// through intermediate table so we'll use the existing "attach" method to
// accomplish this which will insert the record and any more attributes.
$instance->save(array('touch' => false));
$this->attach($instance->getKey(), $joining, $touch);
return $instance;
}
/**
* Create an array of new instances of the related models.
*
* @param array $records
* @param array $joinings
* @return \Illuminate\Database\Eloquent\Model
*/
public function createMany(array $records, array $joinings = array())
{
$instances = array();
foreach ($records as $key => $record)
{
$instances[] = $this->create($record, (array) array_get($joinings, $key), false);
}
$this->touchIfTouching();
return $instances;
}
/**
* Sync the intermediate tables with a list of IDs.
*
* @param array $ids
* @param bool $detaching
* @return void
*/
public function sync(array $ids, $detaching = true)
{
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->newPivotQuery()->lists($this->otherKey);
$records = $this->formatSyncList($ids);
$detach = array_diff($current, array_keys($records));
// Next, we will take the differences of the currents and given IDs and detach
// all of the entities that exist in the "current" array but are not in the
// the array of the IDs given to the method which will complete the sync.
if ($detaching and count($detach) > 0)
{
$this->detach($detach);
}
// Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records.
$this->attachNew($records, $current, false);
$this->touchIfTouching();
}
/**
* Format the sync list so that it is keyed by ID.
*
* @param array $records
* @return array
*/
protected function formatSyncList(array $records)
{
$results = array();
foreach ($records as $id => $attributes)
{
if (is_numeric($attributes))
{
list($id, $attributes) = array((int) $attributes, array());
}
$results[$id] = $attributes;
}
return $results;
}
/**
* Attach all of the IDs that aren't in the current array.
*
* @param array $records
* @param array $current
* @param bool $touch
* @return void
*/
protected function attachNew(array $records, array $current, $touch = true)
{
foreach ($records as $id => $attributes)
{
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
// record, otherwise, we will just update this existing record on this joining
// table, so that the developers will easily update these records pain free.
if ( ! in_array($id, $current))
{
$this->attach($id, $attributes, $touch);
}
elseif (count($attributes) > 0)
{
$this->updateExistingPivot($id, $attributes, $touch);
}
}
}
/**
* Update an existing pivot record on the table.
*
* @param mixed $id
* @param array $attributes
* @param bool $touch
* @return void
*/
protected function updateExistingPivot($id, array $attributes, $touch)
{
if (in_array($this->updatedAt(), $this->pivotColumns))
{
$attributes = $this->setTimestampsOnAttach($attributes, true);
}
$this->newPivotStatementForId($id)->update($attributes);
if ($touch) $this->touchIfTouching();
}
/**
* Attach a model to the parent.
*
* @param mixed $id
* @param array $attributes
* @param bool $touch
* @return void
*/
public function attach($id, array $attributes = array(), $touch = true)
{
if ($id instanceof Model) $id = $id->getKey();
$query = $this->newPivotStatement();
$query->insert($this->createAttachRecords((array) $id, $attributes));
if ($touch) $this->touchIfTouching();
}
/**
* Create an array of records to insert into the pivot table.
*
* @param array $ids
* @return void
*/
protected function createAttachRecords($ids, array $attributes)
{
$records = array();
$timed = in_array($this->createdAt(), $this->pivotColumns);
// To create the attachment records, we will simply spin through the IDs given
// and create a new record to insert for each ID. Each ID may actually be a
// key in the array, with extra attributes to be placed in other columns.
foreach ($ids as $key => $value)
{
$records[] = $this->attacher($key, $value, $attributes, $timed);
}
return $records;
}
/**
* Create a full attachment record payload.
*
* @param int $key
* @param mixed $value
* @param array $attributes
* @param bool $timed
* @return array
*/
protected function attacher($key, $value, $attributes, $timed)
{
list($id, $extra) = $this->getAttachId($key, $value, $attributes);
// To create the attachment records, we will simply spin through the IDs given
// and create a new record to insert for each ID. Each ID may actually be a
// key in the array, with extra attributes to be placed in other columns.
$record = $this->createAttachRecord($id, $timed);
return array_merge($record, $extra);
}
/**
* Get the attach record ID and extra attributes.
*
* @param mixed $key
* @param mixed $value
* @param array $attributes
* @return array
*/
protected function getAttachId($key, $value, array $attributes)
{
if (is_array($value))
{
return array($key, array_merge($value, $attributes));
}
else
{
return array($value, $attributes);
}
}
/**
* Create a new pivot attachment record.
*
* @param int $id
* @param bool $timed
* @return array
*/
protected function createAttachRecord($id, $timed)
{
$record[$this->foreignKey] = $this->parent->getKey();
$record[$this->otherKey] = $id;
// If the record needs to have creation and update timestamps, we will make
// them by calling the parent model's "freshTimestamp" method which will
// provide us with a fresh timestamp in this model's preferred format.
if ($timed)
{
$record = $this->setTimestampsOnAttach($record);
}
return $record;
}
/**
* Set the creation and update timstamps on an attach record.
*
* @param array $record
* @param bool $exists
* @return array
*/
protected function setTimestampsOnAttach(array $record, $exists = false)
{
$fresh = $this->parent->freshTimestamp();
if ( ! $exists) $record[$this->createdAt()] = $fresh;
$record[$this->updatedAt()] = $fresh;
return $record;
}
/**
* Detach models from the relationship.
*
* @param int|array $ids
* @param bool $touch
* @return int
*/
public function detach($ids = array(), $touch = true)
{
if ($ids instanceof Model) $ids = (array) $ids->getKey();
$query = $this->newPivotQuery();
// If associated IDs were passed to the method we will only delete those
// associations, otherwise all of the association ties will be broken.
// We'll return the numbers of affected rows when we do the deletes.
$ids = (array) $ids;
if (count($ids) > 0)
{
$query->whereIn($this->otherKey, $ids);
}
if ($touch) $this->touchIfTouching();
// Once we have all of the conditions set on the statement, we are ready
// to run the delete on the pivot table. Then, if the touch parameter
// is true, we will go ahead and touch all related models to sync.
$results = $query->delete();
return $results;
}
/**
* If we're touching the parent model, touch.
*
* @return void
*/
public function touchIfTouching()
{
if ($this->touchingParent()) $this->getParent()->touch();
if ($this->getParent()->touches($this->relationName)) $this->touch();
}
/**
* Determine if we should touch the parent on sync.
*
* @return bool
*/
protected function touchingParent()
{
return $this->getRelated()->touches($this->guessInverseRelation());
}
/**
* Attempt to guess the name of the inverse of the relation.
*
* @return string
*/
protected function guessInverseRelation()
{
return strtolower(str_plural(class_basename($this->getParent())));
}
/**
* Create a new query builder for the pivot table.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function newPivotQuery()
{
$query = $this->newPivotStatement();
return $query->where($this->foreignKey, $this->parent->getKey());
}
/**
* Get a new plain query builder for the pivot table.
*
* @return \Illuminate\Database\Query\Builder
*/
public function newPivotStatement()
{
return $this->query->getQuery()->newQuery()->from($this->table);
}
/**
* Get a new pivot statement for a given "other" ID.
*
* @param mixed $id
* @return \Illuminate\Database\Query\Builder
*/
protected function newPivotStatementForId($id)
{
$pivot = $this->newPivotStatement();
$key = $this->parent->getKey();
return $pivot->where($this->foreignKey, $key)->where($this->otherKey, $id);
}
/**
* Create a new pivot model instance.
*
* @param array $attributes
* @param bool $exists
* @return \Illuminate\Database\Eloquent\Relation\Pivot
*/
public function newPivot(array $attributes = array(), $exists = false)
{
$pivot = new Pivot($this->parent, $attributes, $this->table, $exists);
$pivot->setPivotKeys($this->foreignKey, $this->otherKey);
return $pivot;
}
/**
* Create a new existing pivot model instance.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
public function newExistingPivot(array $attributes = array())
{
return $this->newPivot($attributes, true);
}
/**
* Set the columns on the pivot table to retrieve.
*
* @param array $columns
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function withPivot($columns)
{
$columns = is_array($columns) ? $columns : func_get_args();
$this->pivotColumns = array_merge($this->pivotColumns, $columns);
return $this;
}
/**
* Specify that the pivot table has creation and update timestamps.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function withTimestamps()
{
return $this->withPivot($this->createdAt(), $this->updatedAt());
}
/**
* Get the related model's updated at column name.
*
* @return string
*/
public function getRelatedFreshUpdate()
{
return array($this->related->getUpdatedAtColumn() => $this->related->freshTimestamp());
}
/**
* Get the fully qualified foreign key for the relation.
*
* @return string
*/
public function getForeignKey()
{
return $this->table.'.'.$this->foreignKey;
}
/**
* Get the fully qualified "other key" for the relation.
*
* @return string
*/
public function getOtherKey()
{
return $this->table.'.'.$this->otherKey;
}
/**
* Get the intermediate table for the relationship.
*
* @return string
*/
public function getTable()
{
return $this->table;
}
}

View File

@@ -0,0 +1,47 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
class HasMany extends HasOneOrMany {
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return $this->query->get();
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return void
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model)
{
$model->setRelation($relation, $this->related->newCollection());
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchMany($models, $results, $relation);
}
}

View File

@@ -0,0 +1,47 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
class HasOne extends HasOneOrMany {
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return $this->query->first();
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return void
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model)
{
$model->setRelation($relation, null);
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchOne($models, $results, $relation);
}
}

View File

@@ -0,0 +1,259 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Collection;
abstract class HasOneOrMany extends Relation {
/**
* The foreign key of the parent model.
*
* @var string
*/
protected $foreignKey;
/**
* Create a new has many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $foreignKey)
{
$this->foreignKey = $foreignKey;
parent::__construct($query, $parent);
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
if (static::$constraints)
{
$key = $this->parent->getKey();
$this->query->where($this->foreignKey, '=', $key);
}
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
$this->query->whereIn($this->foreignKey, $this->getKeys($models));
}
/**
* Match the eagerly loaded results to their single parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function matchOne(array $models, Collection $results, $relation)
{
return $this->matchOneOrMany($models, $results, $relation, 'one');
}
/**
* Match the eagerly loaded results to their many parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function matchMany(array $models, Collection $results, $relation)
{
return $this->matchOneOrMany($models, $results, $relation, 'many');
}
/**
* Match the eagerly loaded results to their many parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @param string $type
* @return array
*/
protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
{
$dictionary = $this->buildDictionary($results);
// Once we have the dictionary we can simply spin through the parent models to
// link them up with their children using the keyed dictionary to make the
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model)
{
$key = $model->getKey();
if (isset($dictionary[$key]))
{
$value = $this->getRelationValue($dictionary, $key, $type);
$model->setRelation($relation, $value);
}
}
return $models;
}
/**
* Get the value of a relationship by one or many type.
*
* @param array $dictionary
* @param string $key
* @param string $type
* @return mixed
*/
protected function getRelationValue(array $dictionary, $key, $type)
{
$value = $dictionary[$key];
return $type == 'one' ? reset($value) : $this->related->newCollection($value);
}
/**
* Build model dictionary keyed by the relation's foreign key.
*
* @param \Illuminate\Database\Eloquent\Collection $results
* @return array
*/
protected function buildDictionary(Collection $results)
{
$dictionary = array();
$foreign = $this->getPlainForeignKey();
// First we will create a dictionary of models keyed by the foreign key of the
// relationship as this will allow us to quickly access all of the related
// models without having to do nested looping which will be quite slow.
foreach ($results as $result)
{
$dictionary[$result->{$foreign}][] = $result;
}
return $dictionary;
}
/**
* Attach a model instance to the parent model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function save(Model $model)
{
$model->setAttribute($this->getPlainForeignKey(), $this->parent->getKey());
return $model->save() ? $model : false;
}
/**
* Attach an array of models to the parent instance.
*
* @param array $models
* @return array
*/
public function saveMany(array $models)
{
array_walk($models, array($this, 'save'));
return $models;
}
/**
* Create a new instance of the related model.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes)
{
$foreign = array(
$this->getPlainForeignKey() => $this->parent->getKey()
);
// Here we will set the raw attributes to avoid hitting the "fill" method so
// that we do not have to worry about a mass accessor rules blocking sets
// on the models. Otherwise, some of these attributes will not get set.
$instance = $this->related->newInstance();
$instance->setRawAttributes(array_merge($attributes, $foreign));
$instance->save();
return $instance;
}
/**
* Create an array of new instances of the related model.
*
* @param array $records
* @return array
*/
public function createMany(array $records)
{
$instances = array();
foreach ($records as $record)
{
$instances[] = $this->create($record);
}
return $instances;
}
/**
* Perform an update on all the related models.
*
* @param array $attributes
* @return int
*/
public function update(array $attributes)
{
if ($this->related->usesTimestamps())
{
$attributes[$this->relatedUpdatedAt()] = $this->related->freshTimestamp();
}
return $this->query->update($attributes);
}
/**
* Get the foreign key for the relationship.
*
* @return string
*/
public function getForeignKey()
{
return $this->foreignKey;
}
/**
* Get the plain foreign key.
*
* @return string
*/
public function getPlainForeignKey()
{
$segments = explode('.', $this->getForeignKey());
return $segments[count($segments) - 1];
}
}

View File

@@ -0,0 +1,47 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
class MorphMany extends MorphOneOrMany {
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return $this->query->get();
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return void
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model)
{
$model->setRelation($relation, $this->related->newCollection());
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchMany($models, $results, $relation);
}
}

View File

@@ -0,0 +1,47 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Collection;
class MorphOne extends MorphOneOrMany {
/**
* Get the results of the relationship.
*
* @return mixed
*/
public function getResults()
{
return $this->query->first();
}
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return void
*/
public function initRelation(array $models, $relation)
{
foreach ($models as $model)
{
$model->setRelation($relation, null);
}
return $models;
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
public function match(array $models, Collection $results, $relation)
{
return $this->matchOne($models, $results, $relation);
}
}

View File

@@ -0,0 +1,160 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
abstract class MorphOneOrMany extends HasOneOrMany {
/**
* The foreign key type for the relationship.
*
* @var string
*/
protected $morphType;
/**
* The class name of the parent model.
*
* @var string
*/
protected $morphClass;
/**
* Create a new has many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @return void
*/
public function __construct(Builder $query, Model $parent, $type, $id)
{
$this->morphType = $type;
$this->morphClass = get_class($parent);
parent::__construct($query, $parent, $id);
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
if (static::$constraints)
{
parent::addConstraints();
$this->query->where($this->morphType, $this->morphClass);
}
}
/**
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationCountQuery(Builder $query)
{
$query = parent::getRelationCountQuery($query);
return $query->where($this->morphType, $this->morphClass);
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
parent::addEagerConstraints($models);
$this->query->where($this->morphType, $this->morphClass);
}
/**
* Attach a model instance to the parent model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function save(Model $model)
{
$model->setAttribute($this->getPlainMorphType(), $this->morphClass);
return parent::save($model);
}
/**
* Create a new instance of the related model.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes)
{
$foreign = $this->getForeignAttributesForCreate();
// When saving a polymorphic relationship, we need to set not only the foreign
// key, but also the foreign key type, which is typically the class name of
// the parent model. This makes the polymorphic item unique in the table.
$attributes = array_merge($attributes, $foreign);
$instance = $this->related->newInstance($attributes);
$instance->save();
return $instance;
}
/**
* Get the foreign ID and type for creating a related model.
*
* @return array
*/
protected function getForeignAttributesForCreate()
{
$foreign = array($this->getPlainForeignKey() => $this->parent->getKey());
$foreign[last(explode('.', $this->morphType))] = $this->morphClass;
return $foreign;
}
/**
* Get the foreign key "type" name.
*
* @return string
*/
public function getMorphType()
{
return $this->morphType;
}
/**
* Get the plain morph type name without the table.
*
* @return string
*/
public function getPlainMorphType()
{
return last(explode('.', $this->morphType));
}
/**
* Get the class name of the parent model.
*
* @return string
*/
public function getMorphClass()
{
return $this->morphClass;
}
}

View File

@@ -0,0 +1,158 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Model;
class Pivot extends Model {
/**
* The parent model of the relationship.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $parent;
/**
* The name of the foreign key column.
*
* @var string
*/
protected $foreignKey;
/**
* The name of the "other key" column.
*
* @var string
*/
protected $otherKey;
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = array();
/**
* Create a new pivot model instance.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @param array $attributes
* @param string $table
* @param bool $exists
* @return void
*/
public function __construct(Model $parent, $attributes, $table, $exists = false)
{
parent::__construct();
// The pivot model is a "dynamic" model since we will set the tables dynamically
// for the instance. This allows it work for any intermediate tables for the
// many to many relationship that are defined by this developer's classes.
$this->setRawAttributes($attributes);
$this->setTable($table);
$this->setConnection($parent->getConnectionName());
// We store off the parent instance so we will access the timestamp column names
// for the model, since the pivot model timestamps aren't easily configurable
// from the developer's point of view. We can use the parents to get these.
$this->parent = $parent;
$this->exists = $exists;
$this->timestamps = $this->hasTimestampAttributes();
}
/**
* Set the keys for a save update query.
*
* @param \Illuminate\Database\Eloquent\Builder
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function setKeysForSaveQuery($query)
{
$query->where($this->foreignKey, $this->getAttribute($this->foreignKey));
return $query->where($this->otherKey, $this->getAttribute($this->otherKey));
}
/**
* Delete the pivot model record from the database.
*
* @return int
*/
public function delete()
{
$foreign = $this->getAttribute($this->foreignKey);
$query = $this->newQuery()->where($this->foreignKey, $foreign);
return $query->where($this->otherKey, $this->getAttribute($this->otherKey))->delete();
}
/**
* Get the foreign key column name.
*
* @return string
*/
public function getForeignKey()
{
return $this->foreignKey;
}
/**
* Get the "other key" column name.
*
* @return string
*/
public function getOtherKey()
{
return $this->otherKey;
}
/**
* Set the key names for the pivot model instance.
*
* @param string $foreignKey
* @param string $otherKey
* @return void
*/
public function setPivotKeys($foreignKey, $otherKey)
{
$this->foreignKey = $foreignKey;
$this->otherKey = $otherKey;
}
/**
* Determine if the pivot model has timestamp attributes.
*
* @return bool
*/
public function hasTimestampAttributes()
{
return array_key_exists($this->getCreatedAtColumn(), $this->attributes);
}
/**
* Get the name of the "created at" column.
*
* @return string
*/
public function getCreatedAtColumn()
{
return $this->parent->getCreatedAtColumn();
}
/**
* Get the name of the "updated at" column.
*
* @return string
*/
public function getUpdatedAtColumn()
{
return $this->parent->getUpdatedAtColumn();
}
}

View File

@@ -0,0 +1,278 @@
<?php namespace Illuminate\Database\Eloquent\Relations;
use Closure;
use DateTime;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Collection;
abstract class Relation {
/**
* The Eloquent query builder instance.
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $query;
/**
* The parent model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $parent;
/**
* The related model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $related;
/**
* Indicates if the relation is adding constraints.
*
* @var bool
*/
protected static $constraints = true;
/**
* Create a new relation instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @return void
*/
public function __construct(Builder $query, Model $parent)
{
$this->query = $query;
$this->parent = $parent;
$this->related = $query->getModel();
$this->addConstraints();
}
/**
* Set the base constraints on the relation query.
*
* @return void
*/
abstract public function addConstraints();
/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
abstract public function addEagerConstraints(array $models);
/**
* Initialize the relation on a set of models.
*
* @param array $models
* @param string $relation
* @return void
*/
abstract public function initRelation(array $models, $relation);
/**
* Match the eagerly loaded results to their parents.
*
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @return array
*/
abstract public function match(array $models, Collection $results, $relation);
/**
* Get the results of the relationship.
*
* @return mixed
*/
abstract public function getResults();
/**
* Touch all of the related models for the relationship.
*
* @return void
*/
public function touch()
{
$column = $this->getRelated()->getUpdatedAtColumn();
$this->rawUpdate(array($column => $this->getRelated()->freshTimestamp()));
}
/**
* Restore all of the soft deleted related models.
*
* @return int
*/
public function restore()
{
return $this->query->withTrashed()->restore();
}
/**
* Run a raw update against the base query.
*
* @param array $attributes
* @return int
*/
public function rawUpdate(array $attributes = array())
{
return $this->query->update($attributes);
}
/**
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getRelationCountQuery(Builder $query)
{
$query->select(new Expression('count(*)'));
$key = $this->wrap($this->parent->getQualifiedKeyName());
return $query->where($this->getForeignKey(), '=', new Expression($key));
}
/**
* Run a callback with constrains disabled on the relation.
*
* @param \Closure $callback
* @return mixed
*/
public static function noConstraints(Closure $callback)
{
static::$constraints = false;
// When resetting the relation where clause, we want to shift the first element
// off of the bindings, leaving only the constraints that the developers put
// as "extra" on the relationships, and not original relation constraints.
$results = call_user_func($callback);
static::$constraints = true;
return $results;
}
/**
* Get all of the primary keys for an array of models.
*
* @param array $models
* @return array
*/
protected function getKeys(array $models)
{
return array_values(array_map(function($value)
{
return $value->getKey();
}, $models));
}
/**
* Get the underlying query for the relation.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getQuery()
{
return $this->query;
}
/**
* Get the base query builder driving the Eloquent builder.
*
* @return \Illuminate\Database\Query\Builder
*/
public function getBaseQuery()
{
return $this->query->getQuery();
}
/**
* Get the parent model of the relation.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function getParent()
{
return $this->parent;
}
/**
* Get the related model of the relation.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function getRelated()
{
return $this->related;
}
/**
* Get the name of the "created at" column.
*
* @return string
*/
public function createdAt()
{
return $this->parent->getCreatedAtColumn();
}
/**
* Get the name of the "updated at" column.
*
* @return string
*/
public function updatedAt()
{
return $this->parent->getUpdatedAtColumn();
}
/**
* Get the name of the related model's "updated at" column.
*
* @return string
*/
public function relatedUpdatedAt()
{
return $this->related->getUpdatedAtColumn();
}
/**
* Wrap the given value with the parent query's grammar.
*
* @param string $value
* @return string
*/
public function wrap($value)
{
return $this->parent->getQuery()->getGrammar()->wrap($value);
}
/**
* Handle dynamic method calls to the relationship.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
$result = call_user_func_array(array($this->query, $method), $parameters);
if ($result === $this->query) return $this;
return $result;
}
}

View File

@@ -0,0 +1,177 @@
<?php namespace Illuminate\Database;
abstract class Grammar {
/**
* The grammar table prefix.
*
* @var string
*/
protected $tablePrefix = '';
/**
* Wrap an array of values.
*
* @param array $values
* @return array
*/
public function wrapArray(array $values)
{
return array_map(array($this, 'wrap'), $values);
}
/**
* Wrap a table in keyword identifiers.
*
* @param string $table
* @return string
*/
public function wrapTable($table)
{
if ($this->isExpression($table)) return $this->getValue($table);
return $this->wrap($this->tablePrefix.$table);
}
/**
* Wrap a value in keyword identifiers.
*
* @param string $value
* @return string
*/
public function wrap($value)
{
if ($this->isExpression($value)) return $this->getValue($value);
// If the value being wrapped has a column alias we will need to separate out
// the pieces so we can wrap each of the segments of the expression on it
// own, and then joins them both back together with the "as" connector.
if (strpos(strtolower($value), ' as ') !== false)
{
$segments = explode(' ', $value);
return $this->wrap($segments[0]).' as '.$this->wrap($segments[2]);
}
$wrapped = array();
$segments = explode('.', $value);
// If the value is not an aliased table expression, we'll just wrap it like
// normal, so if there is more than one segment, we will wrap the first
// segments as if it was a table and the rest as just regular values.
foreach ($segments as $key => $segment)
{
if ($key == 0 and count($segments) > 1)
{
$wrapped[] = $this->wrapTable($segment);
}
else
{
$wrapped[] = $this->wrapValue($segment);
}
}
return implode('.', $wrapped);
}
/**
* Wrap a single string in keyword identifiers.
*
* @param string $value
* @return string
*/
protected function wrapValue($value)
{
return $value !== '*' ? sprintf($this->wrapper, $value) : $value;
}
/**
* Convert an array of column names into a delimited string.
*
* @param array $columns
* @return string
*/
public function columnize(array $columns)
{
return implode(', ', array_map(array($this, 'wrap'), $columns));
}
/**
* Create query parameter place-holders for an array.
*
* @param array $values
* @return string
*/
public function parameterize(array $values)
{
return implode(', ', array_map(array($this, 'parameter'), $values));
}
/**
* Get the appropriate query parameter place-holder for a value.
*
* @param mixed $value
* @return string
*/
public function parameter($value)
{
return $this->isExpression($value) ? $this->getValue($value) : '?';
}
/**
* Get the value of a raw expression.
*
* @param \Illuminate\Database\Query\Expression $expression
* @return string
*/
public function getValue($expression)
{
return $expression->getValue();
}
/**
* Determine if the given value is a raw expression.
*
* @param mixed $value
* @return bool
*/
public function isExpression($value)
{
return $value instanceof Query\Expression;
}
/**
* Get the format for database stored dates.
*
* @return string
*/
public function getDateFormat()
{
return 'Y-m-d H:i:s';
}
/**
* Get the grammar's table prefix.
*
* @return string
*/
public function getTablePrefix()
{
return $this->tablePrefix;
}
/**
* Set the grammar's table prefix.
*
* @param string $prefix
* @return \Illuminate\Database\Grammar
*/
public function setTablePrefix($prefix)
{
$this->tablePrefix = $prefix;
return $this;
}
}

View File

@@ -0,0 +1,207 @@
<?php namespace Illuminate\Database;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Migrations\Migrator;
use Illuminate\Database\Migrations\MigrationCreator;
use Illuminate\Database\Console\Migrations\MakeCommand;
use Illuminate\Database\Console\Migrations\ResetCommand;
use Illuminate\Database\Console\Migrations\RefreshCommand;
use Illuminate\Database\Console\Migrations\InstallCommand;
use Illuminate\Database\Console\Migrations\MigrateCommand;
use Illuminate\Database\Console\Migrations\RollbackCommand;
use Illuminate\Database\Migrations\DatabaseMigrationRepository;
class MigrationServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerRepository();
// Once we have registered the migrator instance we will go ahead and register
// all of the migration related commands that are used by the "Artisan" CLI
// so that they may be easily accessed for registering with the consoles.
$this->registerMigrator();
$this->registerCommands();
}
/**
* Register the migration repository service.
*
* @return void
*/
protected function registerRepository()
{
$this->app['migration.repository'] = $this->app->share(function($app)
{
$table = $app['config']['database.migrations'];
return new DatabaseMigrationRepository($app['db'], $table);
});
}
/**
* Register the migrator service.
*
* @return void
*/
protected function registerMigrator()
{
// The migrator is responsible for actually running and rollback the migration
// files in the application. We'll pass in our database connection resolver
// so the migrator can resolve any of these connections when it needs to.
$this->app['migrator'] = $this->app->share(function($app)
{
$repository = $app['migration.repository'];
return new Migrator($repository, $app['db'], $app['files']);
});
}
/**
* Register all of the migration commands.
*
* @return void
*/
protected function registerCommands()
{
$commands = array('Migrate', 'Rollback', 'Reset', 'Refresh', 'Install', 'Make');
// We'll simply spin through the list of commands that are migration related
// and register each one of them with an application container. They will
// be resolved in the Artisan start file and registered on the console.
foreach ($commands as $command)
{
$this->{'register'.$command.'Command'}();
}
// Once the commands are registered in the application IoC container we will
// register them with the Artisan start event so that these are available
// when the Artisan application actually starts up and is getting used.
$this->commands(
'command.migrate', 'command.migrate.make',
'command.migrate.install', 'command.migrate.rollback',
'command.migrate.reset', 'command.migrate.refresh'
);
}
/**
* Register the "migrate" migration command.
*
* @return void
*/
protected function registerMigrateCommand()
{
$this->app['command.migrate'] = $this->app->share(function($app)
{
$packagePath = $app['path.base'].'/vendor';
return new MigrateCommand($app['migrator'], $packagePath);
});
}
/**
* Register the "rollback" migration command.
*
* @return void
*/
protected function registerRollbackCommand()
{
$this->app['command.migrate.rollback'] = $this->app->share(function($app)
{
return new RollbackCommand($app['migrator']);
});
}
/**
* Register the "reset" migration command.
*
* @return void
*/
protected function registerResetCommand()
{
$this->app['command.migrate.reset'] = $this->app->share(function($app)
{
return new ResetCommand($app['migrator']);
});
}
/**
* Register the "refresh" migration command.
*
* @return void
*/
protected function registerRefreshCommand()
{
$this->app['command.migrate.refresh'] = $this->app->share(function($app)
{
return new RefreshCommand;
});
}
/**
* Register the "install" migration command.
*
* @return void
*/
protected function registerInstallCommand()
{
$this->app['command.migrate.install'] = $this->app->share(function($app)
{
return new InstallCommand($app['migration.repository']);
});
}
/**
* Register the "install" migration command.
*
* @return void
*/
protected function registerMakeCommand()
{
$this->app['migration.creator'] = $this->app->share(function($app)
{
return new MigrationCreator($app['files']);
});
$this->app['command.migrate.make'] = $this->app->share(function($app)
{
// Once we have the migration creator registered, we will create the command
// and inject the creator. The creator is responsible for the actual file
// creation of the migrations, and may be extended by these developers.
$creator = $app['migration.creator'];
$packagePath = $app['path.base'].'/vendor';
return new MakeCommand($creator, $packagePath);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array(
'migrator', 'migration.repository', 'command.migrate',
'command.migrate.rollback', 'command.migrate.reset',
'command.migrate.refresh', 'command.migrate.install',
'migration.creator', 'command.migrate.make',
);
}
}

View File

@@ -0,0 +1,182 @@
<?php namespace Illuminate\Database\Migrations;
use Closure;
use Illuminate\Database\Connection;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
class DatabaseMigrationRepository implements MigrationRepositoryInterface {
/**
* The database connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* The name of the migration table.
*
* @var string
*/
protected $table;
/**
* The name of the database connection to use.
*
* @var string
*/
protected $connection;
/**
* Create a new database migration repository instance.
*
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @return void
*/
public function __construct(Resolver $resolver, $table)
{
$this->table = $table;
$this->resolver = $resolver;
}
/**
* Get the ran migrations.
*
* @return array
*/
public function getRan()
{
return $this->table()->lists('migration');
}
/**
* Get the last migration batch.
*
* @return array
*/
public function getLast()
{
$query = $this->table()->where('batch', $this->getLastBatchNumber());
return $query->orderBy('migration', 'desc')->get();
}
/**
* Log that a migration was run.
*
* @param string $file
* @param int $batch
* @return void
*/
public function log($file, $batch)
{
$record = array('migration' => $file, 'batch' => $batch);
$this->table()->insert($record);
}
/**
* Remove a migration from the log.
*
* @param \StdClass $migration
* @return void
*/
public function delete($migration)
{
$query = $this->table()->where('migration', $migration->migration)->delete();
}
/**
* Get the next migration batch number.
*
* @return int
*/
public function getNextBatchNumber()
{
return $this->getLastBatchNumber() + 1;
}
/**
* Get the last migration batch number.
*
* @return int
*/
public function getLastBatchNumber()
{
return $this->table()->max('batch');
}
/**
* Create the migration repository data store.
*
* @return void
*/
public function createRepository()
{
$schema = $this->getConnection()->getSchemaBuilder();
$schema->create($this->table, function($table)
{
// The migrations table is responsible for keeping track of which of the
// migrations have actually run for the application. We'll create the
// table to hold the migration file's path as well as the batch ID.
$table->string('migration');
$table->integer('batch');
});
}
/**
* Determine if the migration repository exists.
*
* @return bool
*/
public function repositoryExists()
{
$schema = $this->getConnection()->getSchemaBuilder();
return $schema->hasTable($this->table);
}
/**
* Get a query builder for the migration table.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function table()
{
return $this->getConnection()->table($this->table);
}
/**
* Get the connection resolver instance.
*
* @return \Illuminate\Database\ConnectionResolverInterface
*/
public function getConnectionResolver()
{
return $this->resolver;
}
/**
* Resolve the database connection instance.
*
* @return \Illuminate\Database\Connection
*/
public function getConnection()
{
return $this->resolver->connection($this->connection);
}
/**
* Set the information source to gather data.
*
* @param string $name
* @return void
*/
public function setSource($name)
{
$this->connection = $name;
}
}

View File

@@ -0,0 +1,22 @@
<?php namespace Illuminate\Database\Migrations;
abstract class Migration {
/**
* The name of the database connection to use.
*
* @var string
*/
protected $connection;
/**
* Get the migration connection name.
*
* @return string
*/
public function getConnection()
{
return $this->connection;
}
}

View File

@@ -0,0 +1,171 @@
<?php namespace Illuminate\Database\Migrations;
use Closure;
use Illuminate\Filesystem\Filesystem;
class MigrationCreator {
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The registered post create hooks.
*
* @var array
*/
protected $postCreate = array();
/**
* Create a new migration creator instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @return void
*/
public function __construct(Filesystem $files)
{
$this->files = $files;
}
/**
* Create a new migration at the given path.
*
* @param string $name
* @param string $path
* @param string $table
* @param bool $create
* @return string
*/
public function create($name, $path, $table = null, $create = false)
{
$path = $this->getPath($name, $path);
// First we will get the stub file for the migration, which serves as a type
// of template for the migration. Once we have those we will populate the
// various place-holders, save the file, and run the post create event.
$stub = $this->getStub($table, $create);
$this->files->put($path, $this->populateStub($name, $stub, $table));
$this->firePostCreateHooks();
return $path;
}
/**
* Get the migration stub file.
*
* @param string $table
* @return void
*/
protected function getStub($table, $create)
{
if (is_null($table))
{
return $this->files->get($this->getStubPath().'/blank.stub');
}
// We also have stubs for creating new tables and modifying existing tables
// to save the developer some typing when they are creating a new tables
// or modifying existing tables. We'll grab the appropriate stub here.
else
{
$stub = $create ? 'create.stub' : 'update.stub';
return $this->files->get($this->getStubPath()."/{$stub}");
}
}
/**
* Populate the place-holders in the migration stub.
*
* @param string $name
* @param string $stub
* @param string $table
* @return string
*/
protected function populateStub($name, $stub, $table)
{
$stub = str_replace('{{class}}', studly_case($name), $stub);
// Here we will replace the table place-holders with the table specified by
// the developer, which is useful for quickly creating a tables creation
// or update migration from the console instead of typing it manually.
if ( ! is_null($table))
{
$stub = str_replace('{{table}}', $table, $stub);
}
return $stub;
}
/**
* Fire the registered post create hooks.
*
* @return void
*/
protected function firePostCreateHooks()
{
foreach ($this->postCreate as $callback)
{
call_user_func($callback);
}
}
/**
* Register a post migration create hook.
*
* @param Closure $callback
* @return void
*/
public function afterCreate(Closure $callback)
{
$this->postCreate[] = $callback;
}
/**
* Get the full path name to the migration.
*
* @param string $name
* @param string $path
* @return string
*/
protected function getPath($name, $path)
{
return $path.'/'.$this->getDatePrefix().'_'.$name.'.php';
}
/**
* Get the date prefix for the migration.
*
* @return int
*/
protected function getDatePrefix()
{
return date('Y_m_d_His');
}
/**
* Get the path to the stubs.
*
* @return string
*/
public function getStubPath()
{
return __DIR__.'/stubs';
}
/**
* Get the filesystem instance.
*
* @return \Illuminate\Filesystem\Filesystem
*/
public function getFilesystem()
{
return $this->files;
}
}

View File

@@ -0,0 +1,65 @@
<?php namespace Illuminate\Database\Migrations;
interface MigrationRepositoryInterface {
/**
* Get the ran migrations for a given package.
*
* @return array
*/
public function getRan();
/**
* Get the last migration batch.
*
* @return array
*/
public function getLast();
/**
* Log that a migration was run.
*
* @param string $file
* @param int $batch
* @return void
*/
public function log($file, $batch);
/**
* Remove a migration from the log.
*
* @param \StdClass $migration
* @return void
*/
public function delete($migration);
/**
* Get the next migration batch number.
*
* @return int
*/
public function getNextBatchNumber();
/**
* Create the migration repository data store.
*
* @return void
*/
public function createRepository();
/**
* Determine if the migration repository exists.
*
* @return bool
*/
public function repositoryExists();
/**
* Set the information source to gather data.
*
* @param string $name
* @return void
*/
public function setSource($name);
}

View File

@@ -0,0 +1,383 @@
<?php namespace Illuminate\Database\Migrations;
use Closure;
use Illuminate\Events\Dispatcher;
use Illuminate\Database\Connection;
use Illuminate\Filesystem\Filesystem;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
class Migrator {
/**
* The migration repository implementation.
*
* @var \Illuminate\Database\Migrations\MigrationRepositoryInterface
*/
protected $repository;
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* The name of the default connection.
*
* @var string
*/
protected $connection;
/**
* The notes for the current operation.
*
* @var array
*/
protected $notes = array();
/**
* Create a new migrator instance.
*
* @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @param \Illuminate\Filesystem\Filesystem $files
* @return void
*/
public function __construct(MigrationRepositoryInterface $repository,
Resolver $resolver,
Filesystem $files)
{
$this->files = $files;
$this->resolver = $resolver;
$this->repository = $repository;
}
/**
* Run the outstanding migrations at a given path.
*
* @param string $path
* @param bool $pretend
* @return void
*/
public function run($path, $pretend = false)
{
$this->notes = array();
$this->requireFiles($path, $files = $this->getMigrationFiles($path));
// Once we grab all of the migration files for the path, we will compare them
// against the migrations that have already been run for this package then
// run all of the oustanding migrations against the database connection.
$ran = $this->repository->getRan();
$migrations = array_diff($files, $ran);
$this->runMigrationList($migrations, $pretend);
}
/**
* Run an array of migrations.
*
* @param array $migrations
* @param bool $pretend
* @return void
*/
public function runMigrationList($migrations, $pretend = false)
{
// First we will just make sure that there are any migrations to run. If there
// aren't, we will just make a note of it to the developer so they're aware
// that all of the migrations have been run against this database system.
if (count($migrations) == 0)
{
$this->note('<info>Nothing to migrate.</info>');
return;
}
$batch = $this->repository->getNextBatchNumber();
// Once we have the array of migrations, we will spin through them and run the
// migrations "up" so the changes are made to the databases. We'll then log
// that the migration was run so we don't repeat it next time we execute.
foreach ($migrations as $file)
{
$this->runUp($file, $batch, $pretend);
}
}
/**
* Run "up" a migration instance.
*
* @param string $file
* @param int $batch
* @param bool $pretend
* @return void
*/
protected function runUp($file, $batch, $pretend)
{
// First we will resolve a "real" instance of the migration class from this
// migration file name. Once we have the instances we can run the actual
// command such as "up" or "down", or we can just simulate the action.
$migration = $this->resolve($file);
if ($pretend)
{
return $this->pretendToRun($migration, 'up');
}
$migration->up();
// Once we have run a migrations class, we will log that it was run in this
// repository so that we don't try to run it next time we do a migration
// in the application. A migration repository keeps the migrate order.
$this->repository->log($file, $batch);
$this->note("<info>Migrated:</info> $file");
}
/**
* Rollback the last migration operation.
*
* @param bool $pretend
* @return int
*/
public function rollback($pretend = false)
{
$this->notes = array();
// We want to pull in the last batch of migrations that ran on the previous
// migration operation. We'll then reverse those migrations and run each
// of them "down" to reverse the last migration "operation" which ran.
$migrations = $this->repository->getLast();
if (count($migrations) == 0)
{
$this->note('<info>Nothing to rollback.</info>');
return count($migrations);
}
// We need to reverse these migrations so that they are "downed" in reverse
// to what they run on "up". It lets us backtrack through the migrations
// and properly reverse the entire database schema operation that ran.
foreach ($migrations as $migration)
{
$this->runDown((object) $migration, $pretend);
}
return count($migrations);
}
/**
* Run "down" a migration instance.
*
* @param \StdClass $migration
* @param bool $pretend
* @return void
*/
protected function runDown($migration, $pretend)
{
$file = $migration->migration;
// First we will get the file name of the migration so we can resolve out an
// instance of the migration. Once we get an instance we can either run a
// pretend execution of the migration or we can run the real migration.
$instance = $this->resolve($file);
if ($pretend)
{
return $this->pretendToRun($instance, 'down');
}
$instance->down();
// Once we have successfully run the migration "down" we will remove it from
// the migration repository so it will be considered to have not been run
// by the application then will be able to fire by any later operation.
$this->repository->delete($migration);
$this->note("<info>Rolled back:</info> $file");
}
/**
* Get all of the migration files in a given path.
*
* @param string $path
* @return array
*/
public function getMigrationFiles($path)
{
$files = $this->files->glob($path.'/*_*.php');
// Once we have the array of files in the directory we will just remove the
// extension and take the basename of the file which is all we need when
// finding the migrations that haven't been run against the databases.
if ($files === false) return array();
$files = array_map(function($file)
{
return str_replace('.php', '', basename($file));
}, $files);
// Once we have all of the formatted file names we will sort them and since
// they all start with a timestamp this should give us the migrations in
// the order they were actually created by the application developers.
sort($files);
return $files;
}
/**
* Require in all the migration files in a given path.
*
* @param array $files
* @return void
*/
public function requireFiles($path, array $files)
{
foreach ($files as $file) $this->files->requireOnce($path.'/'.$file.'.php');
}
/**
* Pretend to run the migrations.
*
* @param object $migration
* @return void
*/
protected function pretendToRun($migration, $method)
{
foreach ($this->getQueries($migration, $method) as $query)
{
$name = get_class($migration);
$this->note("<info>{$name}:</info> {$query['query']}");
}
}
/**
* Get all of the queries that would be run for a migration.
*
* @param object $migration
* @param string $method
* @return array
*/
protected function getQueries($migration, $method)
{
$connection = $migration->getConnection();
// Now that we have the connections we can resolve it and pretend to run the
// queries against the database returning the array of raw SQL statements
// that would get fired against the database system for this migration.
$db = $this->resolveConnection($connection);
return $db->pretend(function() use ($migration, $method)
{
$migration->$method();
});
}
/**
* Resolve a migration instance from a file.
*
* @param string $file
* @return object
*/
public function resolve($file)
{
$file = implode('_', array_slice(explode('_', $file), 4));
$class = studly_case($file);
return new $class;
}
/**
* Raise a note event for the migrator.
*
* @param string $message
* @return void
*/
protected function note($message)
{
$this->notes[] = $message;
}
/**
* Get the notes for the last operation.
*
* @return array
*/
public function getNotes()
{
return $this->notes;
}
/**
* Resolve the database connection instance.
*
* @return \Illuminate\Database\Connection
*/
public function resolveConnection()
{
return $this->resolver->connection($this->connection);
}
/**
* Set the default connection name.
*
* @param string $name
* @return void
*/
public function setConnection($name)
{
if ( ! is_null($name))
{
$this->resolver->setDefaultConnection($name);
}
$this->repository->setSource($name);
$this->connection = $name;
}
/**
* Get the migration repository instance.
*
* @return \Illuminate\Database\Migrations\MigrationRepositoryInterface
*/
public function getRepository()
{
return $this->repository;
}
/**
* Determine if the migration repository exists.
*
* @return bool
*/
public function repositoryExists()
{
return $this->repository->repositoryExists();
}
/**
* Get the file system instance.
*
* @return \Illuminate\Filesystem\Filesystem
*/
public function getFilesystem()
{
return $this->files;
}
}

View File

@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
class {{class}} extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class {{class}} extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('{{table}}', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('{{table}}');
}
}

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