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

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}}');
}
}

View File

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

View File

@@ -0,0 +1,47 @@
<?php namespace Illuminate\Database;
class MySqlConnection extends Connection {
/**
* 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\MySqlBuilder($this);
}
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\Grammars\Grammar
*/
protected function getDefaultQueryGrammar()
{
return $this->withTablePrefix(new Query\Grammars\MySqlGrammar);
}
/**
* Get the default schema grammar instance.
*
* @return \Illuminate\Database\Schema\Grammars\Grammar
*/
protected function getDefaultSchemaGrammar()
{
return $this->withTablePrefix(new Schema\Grammars\MySqlGrammar);
}
/**
* Get the Doctrine DBAL Driver.
*
* @return \Doctrine\DBAL\Driver
*/
protected function getDoctrineDriver()
{
return new \Doctrine\DBAL\Driver\PDOMySql\Driver;
}
}

View File

@@ -0,0 +1,45 @@
<?php namespace Illuminate\Database;
class PostgresConnection extends Connection {
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\Grammars\Grammar
*/
protected function getDefaultQueryGrammar()
{
return $this->withTablePrefix(new Query\Grammars\PostgresGrammar);
}
/**
* Get the default schema grammar instance.
*
* @return \Illuminate\Database\Schema\Grammars\Grammar
*/
protected function getDefaultSchemaGrammar()
{
return $this->withTablePrefix(new Schema\Grammars\PostgresGrammar);
}
/**
* Get the default post processor instance.
*
* @return \Illuminate\Database\Query\Processors\Processor
*/
protected function getDefaultPostProcessor()
{
return new Query\Processors\PostgresProcessor;
}
/**
* Get the Doctrine DBAL Driver.
*
* @return \Doctrine\DBAL\Driver
*/
protected function getDoctrineDriver()
{
return new \Doctrine\DBAL\Driver\PDOPgSql\Driver;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
<?php namespace Illuminate\Database\Query;
class Expression {
/**
* The value of the expression.
*
* @var mixed
*/
protected $value;
/**
* Create a new raw query expression.
*
* @param mixed $value
* @return void
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Get the value of the expression.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Get the value of the expression.
*
* @return string
*/
public function __toString()
{
return (string) $this->getValue();
}
}

View File

@@ -0,0 +1,654 @@
<?php namespace Illuminate\Database\Query\Grammars;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Grammar as BaseGrammar;
class Grammar extends BaseGrammar {
/**
* The keyword identifier wrapper format.
*
* @var string
*/
protected $wrapper = '"%s"';
/**
* The components that make up a select clause.
*
* @var array
*/
protected $selectComponents = array(
'aggregate',
'columns',
'from',
'joins',
'wheres',
'groups',
'havings',
'orders',
'limit',
'offset',
'unions',
);
/**
* Compile a select query into SQL.
*
* @param \Illuminate\Database\Query\Builder
* @return string
*/
public function compileSelect(Builder $query)
{
if (is_null($query->columns)) $query->columns = array('*');
return trim($this->concatenate($this->compileComponents($query)));
}
/**
* Compile the components necessary for a select clause.
*
* @param \Illuminate\Database\Query\Builder
* @return array
*/
protected function compileComponents(Builder $query)
{
$sql = array();
foreach ($this->selectComponents as $component)
{
// To compile the query, we'll spin through each component of the query and
// see if that component exists. If it does we'll just call the compiler
// function for the component which is responsible for making the SQL.
if ( ! is_null($query->$component))
{
$method = 'compile'.ucfirst($component);
$sql[$component] = $this->$method($query, $query->$component);
}
}
return $sql;
}
/**
* Compile an aggregated select clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $aggregate
* @return string
*/
protected function compileAggregate(Builder $query, $aggregate)
{
$column = $this->columnize($aggregate['columns']);
// If the query has a "distinct" constraint and we're not asking for all columns
// we need to prepend "distinct" onto the column name so that the query takes
// it into account when it performs the aggregating operations on the data.
if ($query->distinct and $column !== '*')
{
$column = 'distinct '.$column;
}
return 'select '.$aggregate['function'].'('.$column.') as aggregate';
}
/**
* Compile the "select *" portion of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $columns
* @return string
*/
protected function compileColumns(Builder $query, $columns)
{
// If the query is actually performing an aggregating select, we will let that
// compiler handle the building of the select clauses, as it will need some
// more syntax that is best handled by that function to keep things neat.
if ( ! is_null($query->aggregate)) return;
$select = $query->distinct ? 'select distinct ' : 'select ';
return $select.$this->columnize($columns);
}
/**
* Compile the "from" portion of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $table
* @return string
*/
protected function compileFrom(Builder $query, $table)
{
return 'from '.$this->wrapTable($table);
}
/**
* Compile the "join" portions of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $joins
* @return string
*/
protected function compileJoins(Builder $query, $joins)
{
$sql = array();
foreach ($joins as $join)
{
$table = $this->wrapTable($join->table);
// First we need to build all of the "on" clauses for the join. There may be many
// of these clauses so we will need to iterate through each one and build them
// separately, then we'll join them up into a single string when we're done.
$clauses = array();
foreach ($join->clauses as $clause)
{
$clauses[] = $this->compileJoinConstraint($clause);
}
// Once we have constructed the clauses, we'll need to take the boolean connector
// off of the first clause as it obviously will not be required on that clause
// because it leads the rest of the clauses, thus not requiring any boolean.
$clauses[0] = $this->removeLeadingBoolean($clauses[0]);
$clauses = implode(' ', $clauses);
$type = $join->type;
// Once we have everything ready to go, we will just concatenate all the parts to
// build the final join statement SQL for the query and we can then return the
// final clause back to the callers as a single, stringified join statement.
$sql[] = "$type join $table on $clauses";
}
return implode(' ', $sql);
}
/**
* Create a join clause constraint segment.
*
* @param array $clause
* @return string
*/
protected function compileJoinConstraint(array $clause)
{
$first = $this->wrap($clause['first']);
$second = $this->wrap($clause['second']);
return "{$clause['boolean']} $first {$clause['operator']} $second";
}
/**
* Compile the "where" portions of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileWheres(Builder $query)
{
$sql = array();
if (is_null($query->wheres)) return '';
// Each type of where clauses has its own compiler function which is responsible
// for actually creating the where clauses SQL. This helps keep the code nice
// and maintainable since each clause has a very small method that it uses.
foreach ($query->wheres as $where)
{
$method = "where{$where['type']}";
$sql[] = $where['boolean'].' '.$this->$method($query, $where);
}
// If we actually have some where clauses, we will strip off the first boolean
// operator, which is added by the query builders for convenience so we can
// avoid checking for the first clauses in each of the compilers methods.
if (count($sql) > 0)
{
$sql = implode(' ', $sql);
return 'where '.preg_replace('/and |or /', '', $sql, 1);
}
return '';
}
/**
* Compile a nested where clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNested(Builder $query, $where)
{
$nested = $where['query'];
return '('.substr($this->compileWheres($nested), 6).')';
}
/**
* Compile a where condition with a sub-select.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereSub(Builder $query, $where)
{
$select = $this->compileSelect($where['query']);
return $this->wrap($where['column']).' '.$where['operator']." ($select)";
}
/**
* Compile a basic where clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBasic(Builder $query, $where)
{
$value = $this->parameter($where['value']);
return $this->wrap($where['column']).' '.$where['operator'].' '.$value;
}
/**
* Compile a "between" where clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBetween(Builder $query, $where)
{
return $this->wrap($where['column']).' between ? and ?';
}
/**
* Compile a where exists clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereExists(Builder $query, $where)
{
return 'exists ('.$this->compileSelect($where['query']).')';
}
/**
* Compile a where exists clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNotExists(Builder $query, $where)
{
return 'not exists ('.$this->compileSelect($where['query']).')';
}
/**
* Compile a "where in" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereIn(Builder $query, $where)
{
$values = $this->parameterize($where['values']);
return $this->wrap($where['column']).' in ('.$values.')';
}
/**
* Compile a "where not in" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNotIn(Builder $query, $where)
{
$values = $this->parameterize($where['values']);
return $this->wrap($where['column']).' not in ('.$values.')';
}
/**
* Compile a where in sub-select clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereInSub(Builder $query, $where)
{
$select = $this->compileSelect($where['query']);
return $this->wrap($where['column']).' in ('.$select.')';
}
/**
* Compile a where not in sub-select clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNotInSub(Builder $query, $where)
{
$select = $this->compileSelect($where['query']);
return $this->wrap($where['column']).' not in ('.$select.')';
}
/**
* Compile a "where null" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNull(Builder $query, $where)
{
return $this->wrap($where['column']).' is null';
}
/**
* Compile a "where not null" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereNotNull(Builder $query, $where)
{
return $this->wrap($where['column']).' is not null';
}
/**
* Compile a raw where clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereRaw(Builder $query, $where)
{
return $where['sql'];
}
/**
* Compile the "group by" portions of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $groups
* @return string
*/
protected function compileGroups(Builder $query, $groups)
{
return 'group by '.$this->columnize($groups);
}
/**
* Compile the "having" portions of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $havings
* @return string
*/
protected function compileHavings(Builder $query, $havings)
{
$me = $this;
$sql = implode(' ', array_map(array($this, 'compileHaving'), $havings));
return 'having '.preg_replace('/and /', '', $sql, 1);
}
/**
* Compile a single having clause.
*
* @param array $having
* @return string
*/
protected function compileHaving(array $having)
{
// If the having clause is "raw", we can just return the clause straight away
// without doing any more processing on it. Otherwise, we will compile the
// clause into SQL based on the components that make it up from builder.
if ($having['type'] === 'raw')
{
return $having['boolean'].' '.$having['sql'];
}
return $this->compileBasicHaving($having);
}
/**
* Compile a basic having clause.
*
* @param array $having
* @return string
*/
protected function compileBasicHaving($having)
{
$column = $this->wrap($having['column']);
$parameter = $this->parameter($having['value']);
return 'and '.$column.' '.$having['operator'].' '.$parameter;
}
/**
* Compile the "order by" portions of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $orders
* @return string
*/
protected function compileOrders(Builder $query, $orders)
{
$me = $this;
return 'order by '.implode(', ', array_map(function($order) use ($me)
{
return $me->wrap($order['column']).' '.$order['direction'];
}
, $orders));
}
/**
* Compile the "limit" portions of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param int $limit
* @return string
*/
protected function compileLimit(Builder $query, $limit)
{
return "limit $limit";
}
/**
* Compile the "offset" portions of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param int $offset
* @return string
*/
protected function compileOffset(Builder $query, $offset)
{
return "offset $offset";
}
/**
* Compile the "union" queries attached to the main query.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileUnions(Builder $query)
{
$sql = '';
foreach ($query->unions as $union)
{
$joiner = $union['all'] ? 'union all ' : 'union ';
$sql = $joiner.$union['query']->toSql();
}
return $sql;
}
/**
* Compile an insert statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileInsert(Builder $query, array $values)
{
// Essentially we will force every insert to be treated as a batch insert which
// simply makes creating the SQL easier for us since we can utilize the same
// basic routine regardless of an amount of records given to us to insert.
$table = $this->wrapTable($query->from);
if ( ! is_array(reset($values)))
{
$values = array($values);
}
$columns = $this->columnize(array_keys(reset($values)));
// We need to build a list of parameter place-holders of values that are bound
// to the query. Each insert should have the exact same amount of parameter
// bindings so we can just go off the first list of values in this array.
$parameters = $this->parameterize(reset($values));
$value = array_fill(0, count($values), "($parameters)");
$parameters = implode(', ', $value);
return "insert into $table ($columns) values $parameters";
}
/**
* Compile an insert and get ID statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param string $sequence
* @return string
*/
public function compileInsertGetId(Builder $query, $values, $sequence)
{
return $this->compileInsert($query, $values);
}
/**
* Compile an update statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileUpdate(Builder $query, $values)
{
$table = $this->wrapTable($query->from);
// Each one of the columns in the update statements needs to be wrapped in the
// keyword identifiers, also a place-holder needs to be created for each of
// the values in the list of bindings so we can make the sets statements.
$columns = array();
foreach ($values as $key => $value)
{
$columns[] = $this->wrap($key).' = '.$this->parameter($value);
}
$columns = implode(', ', $columns);
// If the query has any "join" clauses, we will setup the joins on the builder
// and compile them so we can attach them to this update, as update queries
// can get join statements to attach to other tables when they're needed.
if (isset($query->joins))
{
$joins = ' '.$this->compileJoins($query, $query->joins);
}
else
{
$joins = '';
}
// Of course, update queries may also be constrained by where clauses so we'll
// need to compile the where clauses and attach it to the query so only the
// intended records are updated by the SQL statements we generate to run.
$where = $this->compileWheres($query);
return trim("update {$table}{$joins} set $columns $where");
}
/**
* Compile a delete statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileDelete(Builder $query)
{
$table = $this->wrapTable($query->from);
$where = is_array($query->wheres) ? $this->compileWheres($query) : '';
return trim("delete from $table ".$where);
}
/**
* Compile a truncate table statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @return array
*/
public function compileTruncate(Builder $query)
{
return array('truncate '.$this->wrapTable($query->from) => array());
}
/**
* Concatenate an array of segments, removing empties.
*
* @param array $segments
* @return string
*/
protected function concatenate($segments)
{
return implode(' ', array_filter($segments, function($value)
{
return (string) $value !== '';
}));
}
/**
* Remove the leading boolean from a statement.
*
* @param string $value
* @return string
*/
protected function removeLeadingBoolean($value)
{
return preg_replace('/and |or /', '', $value, 1);
}
}

View File

@@ -0,0 +1,12 @@
<?php namespace Illuminate\Database\Query\Grammars;
class MySqlGrammar extends Grammar {
/**
* The keyword identifier wrapper format.
*
* @var string
*/
protected $wrapper = '`%s`';
}

View File

@@ -0,0 +1,151 @@
<?php namespace Illuminate\Database\Query\Grammars;
use Illuminate\Database\Query\Builder;
class PostgresGrammar extends Grammar {
/**
* Compile an update statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @return string
*/
public function compileUpdate(Builder $query, $values)
{
$table = $this->wrapTable($query->from);
// Each one of the columns in the update statements needs to be wrapped in the
// keyword identifiers, also a place-holder needs to be created for each of
// the values in the list of bindings so we can make the sets statements.
$columns = $this->compileUpdateColumns($values);
$from = $this->compileUpdateFrom($query);
$where = $this->compileUpdateWheres($query);
return trim("update {$table} set {$columns}{$from} $where");
}
/**
* Compile the columns for the update statement.
*
* @param array $values
* @return string
*/
protected function compileUpdateColumns($values)
{
$columns = array();
// When gathering the columns for an update statement, we'll wrap each of the
// columns and convert it to a parameter value. Then we will concatenate a
// list of the columns that can be added into this update query clauses.
foreach ($values as $key => $value)
{
$columns[] = $this->wrap($key).' = '.$this->parameter($value);
}
return implode(', ', $columns);
}
/**
* Compile the "from" clause for an update with a join.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileUpdateFrom(Builder $query)
{
if ( ! isset($query->joins)) return '';
$froms = array();
// When using Postgres, updates with joins list the joined tables in the from
// clause, which is different than other systems like MySQL. Here, we will
// compile out the tables that are joined and add them to a from clause.
foreach ($query->joins as $join)
{
$froms[] = $this->wrapTable($join->table);
}
if (count($froms) > 0) return ' from '.implode(', ', $froms);
}
/**
* Compile the additional where clauses for updates with joins.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileUpdateWheres(Builder $query)
{
$baseWhere = $this->compileWheres($query);
if ( ! isset($query->joins)) return $baseWhere;
// Once we compile the join constraints, we will either use them as the where
// clause or append them to the existing base where clauses. If we need to
// strip the leading boolean we will do so when using as the only where.
$joinWhere = $this->compileUpdateJoinWheres($query);
if (trim($baseWhere) == '')
{
return 'where '.$this->removeLeadingBoolean($joinWhere);
}
else
{
return $baseWhere.' '.$joinWhere;
}
}
/**
* Compile the "join" clauses for an update.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileUpdateJoinWheres(Builder $query)
{
$joinWheres = array();
// Here we will just loop through all of the join constraints and compile them
// all out then implode them. This should give us "where" like syntax after
// everything has been built and then we will join it to the real wheres.
foreach ($query->joins as $join)
{
foreach ($join->clauses as $clause)
{
$joinWheres[] = $this->compileJoinConstraint($clause);
}
}
return implode(' ', $joinWheres);
}
/**
* Compile an insert and get ID statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $values
* @param string $sequence
* @return string
*/
public function compileInsertGetId(Builder $query, $values, $sequence)
{
if (is_null($sequence)) $sequence = 'id';
return $this->compileInsert($query, $values).' returning '.$this->wrap($sequence);
}
/**
* Compile a truncate table statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @return array
*/
public function compileTruncate(Builder $query)
{
return array('truncate '.$this->wrapTable($query->from).' restart identity' => array());
}
}

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