diff --git a/Capsule/Manager.php b/Capsule/Manager.php
index 1b01b0177..aa272faec 100755
--- a/Capsule/Manager.php
+++ b/Capsule/Manager.php
@@ -1,32 +1,33 @@
setupContainer($container);
+ $this->setupContainer($container ?: new Container);
// Once we have the container setup, we will setup the default configuration
// options in the container "config" binding. This will make the database
@@ -36,19 +37,6 @@ public function __construct(Container $container = null)
$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.
*
@@ -156,7 +144,7 @@ public function bootEloquent()
* Set the fetch mode for the database connections.
*
* @param int $fetchMode
- * @return \Illuminate\Database\Capsule\Manager
+ * @return $this
*/
public function setFetchMode($fetchMode)
{
@@ -166,19 +154,19 @@ public function setFetchMode($fetchMode)
}
/**
- * Make this capsule instance available globally.
+ * Get the database manager instance.
*
- * @return void
+ * @return \Illuminate\Database\DatabaseManager
*/
- public function setAsGlobal()
+ public function getDatabaseManager()
{
- static::$instance = $this;
+ return $this->manager;
}
/**
* Get the current event dispatcher instance.
*
- * @return \Illuminate\Events\Dispatcher
+ * @return \Illuminate\Contracts\Events\Dispatcher
*/
public function getEventDispatcher()
{
@@ -191,7 +179,7 @@ public function getEventDispatcher()
/**
* Set the event dispatcher instance to be used by connections.
*
- * @param \Illuminate\Events\Dispatcher $dispatcher
+ * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @return void
*/
public function setEventDispatcher(Dispatcher $dispatcher)
@@ -199,51 +187,6 @@ 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 be 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.
*
@@ -256,4 +199,4 @@ public static function __callStatic($method, $parameters)
return call_user_func_array(array(static::connection(), $method), $parameters);
}
-}
\ No newline at end of file
+}
diff --git a/Connection.php b/Connection.php
index b5ff3fadd..f58e5510c 100755
--- a/Connection.php
+++ b/Connection.php
@@ -3,6 +3,10 @@
use PDO;
use Closure;
use DateTime;
+use Exception;
+use LogicException;
+use RuntimeException;
+use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Query\Processors\Processor;
use Doctrine\DBAL\Connection as DoctrineConnection;
@@ -22,6 +26,13 @@ class Connection implements ConnectionInterface {
*/
protected $readPdo;
+ /**
+ * The reconnector instance for the connection.
+ *
+ * @var callable
+ */
+ protected $reconnector;
+
/**
* The query grammar implementation.
*
@@ -46,24 +57,10 @@ class Connection implements ConnectionInterface {
/**
* The event dispatcher instance.
*
- * @var \Illuminate\Events\Dispatcher
+ * @var \Illuminate\Contracts\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.
*
@@ -72,7 +69,7 @@ class Connection implements ConnectionInterface {
protected $fetchMode = PDO::FETCH_ASSOC;
/**
- * The number of active transasctions.
+ * The number of active transactions.
*
* @var int
*/
@@ -90,7 +87,7 @@ class Connection implements ConnectionInterface {
*
* @var bool
*/
- protected $loggingQueries = true;
+ protected $loggingQueries = false;
/**
* Indicates if the connection is in a "dry run".
@@ -123,10 +120,10 @@ class Connection implements ConnectionInterface {
/**
* Create a new database connection instance.
*
- * @param PDO $pdo
- * @param string $database
- * @param string $tablePrefix
- * @param array $config
+ * @param \PDO $pdo
+ * @param string $database
+ * @param string $tablePrefix
+ * @param array $config
* @return void
*/
public function __construct(PDO $pdo, $database = '', $tablePrefix = '', array $config = array())
@@ -266,16 +263,29 @@ public function selectOne($query, $bindings = array())
* @param array $bindings
* @return array
*/
- public function select($query, $bindings = array())
+ public function selectFromWriteConnection($query, $bindings = array())
{
- return $this->run($query, $bindings, function($me, $query, $bindings)
+ return $this->select($query, $bindings, false);
+ }
+
+ /**
+ * Run a select statement against the database.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @param bool $useReadPdo
+ * @return array
+ */
+ public function select($query, $bindings = array(), $useReadPdo = true)
+ {
+ return $this->run($query, $bindings, function($me, $query, $bindings) use ($useReadPdo)
{
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->getReadPdo()->prepare($query);
+ $statement = $this->getPdoForSelect($useReadPdo)->prepare($query);
$statement->execute($me->prepareBindings($bindings));
@@ -283,6 +293,17 @@ public function select($query, $bindings = array())
});
}
+ /**
+ * Get the PDO connection to use for a select query.
+ *
+ * @param bool $useReadPdo
+ * @return \PDO
+ */
+ protected function getPdoForSelect($useReadPdo = true)
+ {
+ return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
+ }
+
/**
* Run an insert statement against the database.
*
@@ -370,7 +391,7 @@ public function affectingStatement($query, $bindings = array())
*/
public function unprepared($query)
{
- return $this->run($query, array(), function($me, $query, $bindings)
+ return $this->run($query, array(), function($me, $query)
{
if ($me->pretending()) return true;
@@ -409,7 +430,7 @@ public function prepareBindings(array $bindings)
/**
* Execute a Closure within a transaction.
*
- * @param Closure $callback
+ * @param \Closure $callback
* @return mixed
*
* @throws \Exception
@@ -431,7 +452,7 @@ public function transaction(Closure $callback)
// 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)
+ catch (Exception $e)
{
$this->rollBack();
@@ -454,6 +475,8 @@ public function beginTransaction()
{
$this->pdo->beginTransaction();
}
+
+ $this->fireConnectionEvent('beganTransaction');
}
/**
@@ -466,6 +489,8 @@ public function commit()
if ($this->transactions == 1) $this->pdo->commit();
--$this->transactions;
+
+ $this->fireConnectionEvent('committed');
}
/**
@@ -485,12 +510,24 @@ public function rollBack()
{
--$this->transactions;
}
+
+ $this->fireConnectionEvent('rollingBack');
+ }
+
+ /**
+ * Get the number of active transactions.
+ *
+ * @return int
+ */
+ public function transactionLevel()
+ {
+ return $this->transactions;
}
/**
* Execute the given callback in "dry run" mode.
*
- * @param Closure $callback
+ * @param \Closure $callback
* @return array
*/
public function pretend(Closure $callback)
@@ -512,17 +549,55 @@ public function pretend(Closure $callback)
/**
* Run a SQL statement and log its execution context.
*
- * @param string $query
- * @param array $bindings
- * @param Closure $callback
+ * @param string $query
+ * @param array $bindings
+ * @param \Closure $callback
* @return mixed
*
- * @throws QueryException
+ * @throws \Illuminate\Database\QueryException
*/
protected function run($query, $bindings, Closure $callback)
{
+ $this->reconnectIfMissingConnection();
+
$start = microtime(true);
+ // Here we will run this query. If an exception occurs we'll determine if it was
+ // caused by a connection that has been lost. If that is the cause, we'll try
+ // to re-establish connection and re-run the query with a fresh connection.
+ try
+ {
+ $result = $this->runQueryCallback($query, $bindings, $callback);
+ }
+ catch (QueryException $e)
+ {
+ $result = $this->tryAgainIfCausedByLostConnection(
+ $e, $query, $bindings, $callback
+ );
+ }
+
+ // 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;
+ }
+
+ /**
+ * Run a SQL statement.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @param \Closure $callback
+ * @return mixed
+ *
+ * @throws \Illuminate\Database\QueryException
+ */
+ protected function runQueryCallback($query, $bindings, Closure $callback)
+ {
// 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.
@@ -534,19 +609,88 @@ protected function run($query, $bindings, Closure $callback)
// 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)
+ catch (Exception $e)
{
- throw new QueryException($query, $bindings, $e);
+ throw new QueryException(
+ $query, $this->prepareBindings($bindings), $e
+ );
}
- // 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);
+ return $result;
+ }
- $this->logQuery($query, $bindings, $time);
+ /**
+ * Handle a query exception that occurred during query execution.
+ *
+ * @param \Illuminate\Database\QueryException $e
+ * @param string $query
+ * @param array $bindings
+ * @param \Closure $callback
+ * @return mixed
+ *
+ * @throws \Illuminate\Database\QueryException
+ */
+ protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback)
+ {
+ if ($this->causedByLostConnection($e))
+ {
+ $this->reconnect();
- return $result;
+ return $this->runQueryCallback($query, $bindings, $callback);
+ }
+
+ throw $e;
+ }
+
+ /**
+ * Determine if the given exception was caused by a lost connection.
+ *
+ * @param \Illuminate\Database\QueryException
+ * @return bool
+ */
+ protected function causedByLostConnection(QueryException $e)
+ {
+ return str_contains($e->getPrevious()->getMessage(), 'server has gone away');
+ }
+
+ /**
+ * Disconnect from the underlying PDO connection.
+ *
+ * @return void
+ */
+ public function disconnect()
+ {
+ $this->setPdo(null)->setReadPdo(null);
+ }
+
+ /**
+ * Reconnect to the database.
+ *
+ * @return void
+ *
+ * @throws \LogicException
+ */
+ public function reconnect()
+ {
+ if (is_callable($this->reconnector))
+ {
+ return call_user_func($this->reconnector, $this);
+ }
+
+ throw new LogicException("Lost connection and no reconnector available.");
+ }
+
+ /**
+ * Reconnect to the database if a PDO connection is missing.
+ *
+ * @return void
+ */
+ protected function reconnectIfMissingConnection()
+ {
+ if (is_null($this->getPdo()) || is_null($this->getReadPdo()))
+ {
+ $this->reconnect();
+ }
}
/**
@@ -554,7 +698,7 @@ protected function run($query, $bindings, Closure $callback)
*
* @param string $query
* @param array $bindings
- * @param $time
+ * @param float|null $time
* @return void
*/
public function logQuery($query, $bindings, $time = null)
@@ -572,7 +716,7 @@ public function logQuery($query, $bindings, $time = null)
/**
* Register a database query listener with the connection.
*
- * @param Closure $callback
+ * @param \Closure $callback
* @return void
*/
public function listen(Closure $callback)
@@ -583,6 +727,20 @@ public function listen(Closure $callback)
}
}
+ /**
+ * Fire an event for this connection.
+ *
+ * @param string $event
+ * @return void
+ */
+ protected function fireConnectionEvent($event)
+ {
+ if (isset($this->events))
+ {
+ $this->events->fire('connection.'.$this->getName().'.'.$event, $this);
+ }
+ }
+
/**
* Get the elapsed time since a given starting point.
*
@@ -635,7 +793,7 @@ public function getDoctrineConnection()
/**
* Get the current PDO connection.
*
- * @return PDO
+ * @return \PDO
*/
public function getPdo()
{
@@ -645,21 +803,26 @@ public function getPdo()
/**
* Get the current PDO connection used for reading.
*
- * @return PDO
+ * @return \PDO
*/
public function getReadPdo()
{
+ if ($this->transactions >= 1) return $this->getPdo();
+
return $this->readPdo ?: $this->pdo;
}
/**
* Set the PDO connection.
*
- * @param PDO $pdo
- * @return \Illuminate\Database\Connection
+ * @param \PDO|null $pdo
+ * @return $this
*/
- public function setPdo(PDO $pdo)
+ public function setPdo($pdo)
{
+ if ($this->transactions >= 1)
+ throw new RuntimeException("Can't swap PDO instance while within transaction.");
+
$this->pdo = $pdo;
return $this;
@@ -668,16 +831,29 @@ public function setPdo(PDO $pdo)
/**
* Set the PDO connection used for reading.
*
- * @param PDO $pdo
- * @return \Illuminate\Database\Connection
+ * @param \PDO|null $pdo
+ * @return $this
*/
- public function setReadPdo(PDO $pdo)
+ public function setReadPdo($pdo)
{
$this->readPdo = $pdo;
return $this;
}
+ /**
+ * Set the reconnect instance on the connection.
+ *
+ * @param callable $reconnector
+ * @return $this
+ */
+ public function setReconnector(callable $reconnector)
+ {
+ $this->reconnector = $reconnector;
+
+ return $this;
+ }
+
/**
* Get the database connection name.
*
@@ -775,7 +951,7 @@ public function setPostProcessor(Processor $processor)
/**
* Get the event dispatcher used by the connection.
*
- * @return \Illuminate\Events\Dispatcher
+ * @return \Illuminate\Contracts\Events\Dispatcher
*/
public function getEventDispatcher()
{
@@ -785,66 +961,14 @@ public function getEventDispatcher()
/**
* Set the event dispatcher instance on the connection.
*
- * @param \Illuminate\Events\Dispatcher
+ * @param \Illuminate\Contracts\Events\Dispatcher
* @return void
*/
- public function setEventDispatcher(\Illuminate\Events\Dispatcher $events)
+ public function setEventDispatcher(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".
*
@@ -916,6 +1040,16 @@ public function disableQueryLog()
$this->loggingQueries = false;
}
+ /**
+ * Determine whether we're logging queries.
+ *
+ * @return bool
+ */
+ public function logging()
+ {
+ return $this->loggingQueries;
+ }
+
/**
* Get the name of the connected database.
*
diff --git a/ConnectionInterface.php b/ConnectionInterface.php
index fb5282a02..48d69b334 100755
--- a/ConnectionInterface.php
+++ b/ConnectionInterface.php
@@ -4,6 +4,22 @@
interface ConnectionInterface {
+ /**
+ * Begin a fluent query against a database table.
+ *
+ * @param string $table
+ * @return \Illuminate\Database\Query\Builder
+ */
+ public function table($table);
+
+ /**
+ * Get a new raw query expression.
+ *
+ * @param mixed $value
+ * @return \Illuminate\Database\Query\Expression
+ */
+ public function raw($value);
+
/**
* Run a select statement and return a single result.
*
@@ -58,12 +74,75 @@ public function delete($query, $bindings = array());
*/
public function statement($query, $bindings = array());
+ /**
+ * 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());
+
+ /**
+ * Run a raw, unprepared query against the PDO connection.
+ *
+ * @param string $query
+ * @return bool
+ */
+ public function unprepared($query);
+
+ /**
+ * Prepare the query bindings for execution.
+ *
+ * @param array $bindings
+ * @return array
+ */
+ public function prepareBindings(array $bindings);
+
/**
* Execute a Closure within a transaction.
*
- * @param Closure $callback
+ * @param \Closure $callback
* @return mixed
+ *
+ * @throws \Exception
*/
public function transaction(Closure $callback);
-}
\ No newline at end of file
+ /**
+ * Start a new database transaction.
+ *
+ * @return void
+ */
+ public function beginTransaction();
+
+ /**
+ * Commit the active database transaction.
+ *
+ * @return void
+ */
+ public function commit();
+
+ /**
+ * Rollback the active database transaction.
+ *
+ * @return void
+ */
+ public function rollBack();
+
+ /**
+ * Get the number of active transactions.
+ *
+ * @return int
+ */
+ public function transactionLevel();
+
+ /**
+ * Execute the given callback in "dry run" mode.
+ *
+ * @param \Closure $callback
+ * @return array
+ */
+ public function pretend(Closure $callback);
+
+}
diff --git a/ConnectionResolver.php b/ConnectionResolver.php
index 4312475ed..79469b20e 100755
--- a/ConnectionResolver.php
+++ b/ConnectionResolver.php
@@ -34,7 +34,7 @@ public function __construct(array $connections = array())
* Get a database connection instance.
*
* @param string $name
- * @return \Illuminate\Database\Connection
+ * @return \Illuminate\Database\ConnectionInterface
*/
public function connection($name = null)
{
@@ -47,10 +47,10 @@ public function connection($name = null)
* Add a connection to the resolver.
*
* @param string $name
- * @param \Illuminate\Database\Connection $connection
+ * @param \Illuminate\Database\ConnectionInterface $connection
* @return void
*/
- public function addConnection($name, Connection $connection)
+ public function addConnection($name, ConnectionInterface $connection)
{
$this->connections[$name] = $connection;
}
@@ -87,4 +87,4 @@ public function setDefaultConnection($name)
$this->default = $name;
}
-}
\ No newline at end of file
+}
diff --git a/ConnectionResolverInterface.php b/ConnectionResolverInterface.php
index 7e9cfd651..46abdc037 100755
--- a/ConnectionResolverInterface.php
+++ b/ConnectionResolverInterface.php
@@ -25,4 +25,4 @@ public function getDefaultConnection();
*/
public function setDefaultConnection($name);
-}
\ No newline at end of file
+}
diff --git a/Connectors/ConnectionFactory.php b/Connectors/ConnectionFactory.php
index 5a27557c4..37dd69fca 100755
--- a/Connectors/ConnectionFactory.php
+++ b/Connectors/ConnectionFactory.php
@@ -1,25 +1,26 @@
createReadWriteConnection($config);
}
- else
- {
- return $this->createSingleConnection($config);
- }
+
+ return $this->createSingleConnection($config);
}
/**
@@ -116,7 +115,7 @@ protected function getWriteConfig(array $config)
/**
* Get a read / write level configuration.
*
- * @param array $config
+ * @param array $config
* @param string $type
* @return array
*/
@@ -126,10 +125,8 @@ protected function getReadWriteConfig(array $config, $type)
{
return $config[$type][array_rand($config[$type])];
}
- else
- {
- return $config[$type];
- }
+
+ return $config[$type];
}
/**
@@ -168,7 +165,12 @@ public function createConnector(array $config)
{
if ( ! isset($config['driver']))
{
- throw new \InvalidArgumentException("A driver must be specified.");
+ throw new InvalidArgumentException("A driver must be specified.");
+ }
+
+ if ($this->container->bound($key = "db.connector.{$config['driver']}"))
+ {
+ return $this->container->make($key);
}
switch ($config['driver'])
@@ -186,22 +188,22 @@ public function createConnector(array $config)
return new SqlServerConnector;
}
- throw new \InvalidArgumentException("Unsupported driver [{$config['driver']}]");
+ 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
+ * @param string $driver
+ * @param \PDO $connection
+ * @param string $database
+ * @param string $prefix
+ * @param array $config
* @return \Illuminate\Database\Connection
*
* @throws \InvalidArgumentException
*/
- protected function createConnection($driver, PDO $connection, $database, $prefix = '', $config = null)
+ protected function createConnection($driver, PDO $connection, $database, $prefix = '', array $config = array())
{
if ($this->container->bound($key = "db.connection.{$driver}"))
{
@@ -223,7 +225,7 @@ protected function createConnection($driver, PDO $connection, $database, $prefix
return new SqlServerConnection($connection, $database, $prefix, $config);
}
- throw new \InvalidArgumentException("Unsupported driver [$driver]");
+ throw new InvalidArgumentException("Unsupported driver [$driver]");
}
}
diff --git a/Connectors/Connector.php b/Connectors/Connector.php
index bb24034f4..6c388e068 100755
--- a/Connectors/Connector.php
+++ b/Connectors/Connector.php
@@ -10,11 +10,11 @@ class Connector {
* @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,
+ 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,
);
/**
@@ -36,7 +36,7 @@ public function getOptions(array $config)
* @param string $dsn
* @param array $config
* @param array $options
- * @return PDO
+ * @return \PDO
*/
public function createConnection($dsn, array $config, array $options)
{
@@ -68,4 +68,4 @@ public function setDefaultOptions(array $options)
$this->options = $options;
}
-}
\ No newline at end of file
+}
diff --git a/Connectors/ConnectorInterface.php b/Connectors/ConnectorInterface.php
index c734f9dbe..c2c76a5fd 100755
--- a/Connectors/ConnectorInterface.php
+++ b/Connectors/ConnectorInterface.php
@@ -6,8 +6,8 @@ interface ConnectorInterface {
* Establish a database connection.
*
* @param array $config
- * @return PDO
+ * @return \PDO
*/
public function connect(array $config);
-}
\ No newline at end of file
+}
diff --git a/Connectors/MySqlConnector.php b/Connectors/MySqlConnector.php
index fbdadfb0d..46ad0a799 100755
--- a/Connectors/MySqlConnector.php
+++ b/Connectors/MySqlConnector.php
@@ -6,19 +6,24 @@ class MySqlConnector extends Connector implements ConnectorInterface {
* Establish a database connection.
*
* @param array $config
- * @return PDO
+ * @return \PDO
*/
public function connect(array $config)
{
$dsn = $this->getDsn($config);
+ $options = $this->getOptions($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);
+ if (isset($config['unix_socket']))
+ {
+ $connection->exec("use {$config['database']};");
+ }
+
$collation = $config['collation'];
// Next we will set the "names" and "collation" on the clients connections so
@@ -26,7 +31,8 @@ public function connect(array $config)
// is set on the server but needs to be set here on this client objects.
$charset = $config['charset'];
- $names = "set names '$charset' collate '$collation'";
+ $names = "set names '$charset'".
+ ( ! is_null($collation) ? " collate '$collation'" : '');
$connection->prepare($names)->execute();
@@ -42,34 +48,54 @@ public function connect(array $config)
}
/**
- * Create a DSN string from a configuration.
+ * Create a DSN string from a configuration. Chooses socket or host/port based on
+ * the 'unix_socket' config value
*
* @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);
+ return $this->configHasSocket($config) ? $this->getSocketDsn($config) : $this->getHostDsn($config);
+ }
- $dsn = "mysql:host={$host};dbname={$database}";
+ /**
+ * Determine if the given configuration array has a UNIX socket value.
+ *
+ * @param array $config
+ * @return bool
+ */
+ protected function configHasSocket(array $config)
+ {
+ return isset($config['unix_socket']) && ! empty($config['unix_socket']);
+ }
- if (isset($config['port']))
- {
- $dsn .= ";port={$port}";
- }
+ /**
+ * Get the DSN string for a socket configuration.
+ *
+ * @param array $config
+ * @return string
+ */
+ protected function getSocketDsn(array $config)
+ {
+ extract($config);
- // 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 "mysql:unix_socket={$config['unix_socket']};dbname={$database}";
+ }
+
+ /**
+ * Get the DSN string for a host / port configuration.
+ *
+ * @param array $config
+ * @return string
+ */
+ protected function getHostDsn(array $config)
+ {
+ extract($config);
- return $dsn;
+ return isset($config['port'])
+ ? "mysql:host={$host};port={$port};dbname={$database}"
+ : "mysql:host={$host};dbname={$database}";
}
-}
\ No newline at end of file
+}
diff --git a/Connectors/PostgresConnector.php b/Connectors/PostgresConnector.php
index dfa7d3388..14b0f441e 100755
--- a/Connectors/PostgresConnector.php
+++ b/Connectors/PostgresConnector.php
@@ -10,18 +10,17 @@ class PostgresConnector extends Connector implements ConnectorInterface {
* @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_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 $config
- * @return PDO
+ * @return \PDO
*/
public function connect(array $config)
{
@@ -38,6 +37,16 @@ public function connect(array $config)
$connection->prepare("set names '$charset'")->execute();
+ // Next, we will check to see if a timezone has been specified in this config
+ // and if it has we will issue a statement to modify the timezone with the
+ // database. Setting this DB timezone is an optional configuration item.
+ if (isset($config['timezone']))
+ {
+ $timezone = $config['timezone'];
+
+ $connection->prepare("set time zone '$timezone'")->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.
@@ -76,7 +85,12 @@ protected function getDsn(array $config)
$dsn .= ";port={$port}";
}
+ if (isset($config['sslmode']))
+ {
+ $dsn .= ";sslmode={$sslmode}";
+ }
+
return $dsn;
}
-}
\ No newline at end of file
+}
diff --git a/Connectors/SQLiteConnector.php b/Connectors/SQLiteConnector.php
index 5d9f75dc0..697a72b80 100755
--- a/Connectors/SQLiteConnector.php
+++ b/Connectors/SQLiteConnector.php
@@ -1,12 +1,14 @@
createConnection("sqlite:{$path}", $config, $options);
}
-}
\ No newline at end of file
+}
diff --git a/Connectors/SqlServerConnector.php b/Connectors/SqlServerConnector.php
index df3294d2a..c4d14c238 100755
--- a/Connectors/SqlServerConnector.php
+++ b/Connectors/SqlServerConnector.php
@@ -10,17 +10,17 @@ class SqlServerConnector extends Connector implements ConnectorInterface {
* @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_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 $config
- * @return PDO
+ * @return \PDO
*/
public function connect(array $config)
{
@@ -37,25 +37,47 @@ public function connect(array $config)
*/
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}";
+ return $this->getDblibDsn($config);
}
else
{
- $dbName = $database != '' ? ";Database={$database}" : '';
-
- return "sqlsrv:Server={$host}{$port}{$dbName}";
+ return $this->getSqlSrvDsn($config);
}
}
+ /**
+ * Get the DSN string for a DbLib connection.
+ *
+ * @param array $config
+ * @return string
+ */
+ protected function getDblibDsn(array $config)
+ {
+ $port = isset($config['port']) ? ':'.$config['port'] : '';
+
+ return "dblib:host={$config['host']}{$port};dbname={$config['database']}";
+ }
+
+ /**
+ * Get the DSN string for a SqlSrv connection.
+ *
+ * @param array $config
+ * @return string
+ */
+ protected function getSqlSrvDsn(array $config)
+ {
+ $port = isset($config['port']) ? ','.$config['port'] : '';
+
+ $dbName = $config['database'] != '' ? ";Database={$config['database']}" : '';
+
+ return "sqlsrv:Server={$config['host']}{$port}{$dbName}";
+ }
+
/**
* Get the available PDO drivers.
*
@@ -66,4 +88,4 @@ protected function getAvailableDrivers()
return PDO::getAvailableDrivers();
}
-}
\ No newline at end of file
+}
diff --git a/Console/Migrations/BaseCommand.php b/Console/Migrations/BaseCommand.php
index d4a3d5de6..f24024bb9 100755
--- a/Console/Migrations/BaseCommand.php
+++ b/Console/Migrations/BaseCommand.php
@@ -11,39 +11,7 @@ class BaseCommand extends Command {
*/
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';
+ return $this->laravel['path.database'].'/migrations';
}
-}
\ No newline at end of file
+}
diff --git a/Console/Migrations/InstallCommand.php b/Console/Migrations/InstallCommand.php
index 9feb2aed1..d89c0c4af 100755
--- a/Console/Migrations/InstallCommand.php
+++ b/Console/Migrations/InstallCommand.php
@@ -23,14 +23,14 @@ class InstallCommand extends Command {
/**
* The repository instance.
*
- * @var \Illuminate\Database\Console\Migrations\MigrationRepositoryInterface
+ * @var \Illuminate\Database\Migrations\MigrationRepositoryInterface
*/
protected $repository;
/**
* Create a new migration install command instance.
*
- * @param \Illuminate\Database\Console\Migrations\MigrationRepositoryInterface $repository
+ * @param \Illuminate\Database\Migrations\MigrationRepositoryInterface $repository
* @return void
*/
public function __construct(MigrationRepositoryInterface $repository)
@@ -66,4 +66,4 @@ protected function getOptions()
);
}
-}
\ No newline at end of file
+}
diff --git a/Console/Migrations/MigrateCommand.php b/Console/Migrations/MigrateCommand.php
index 5a06b8764..24db86d07 100755
--- a/Console/Migrations/MigrateCommand.php
+++ b/Console/Migrations/MigrateCommand.php
@@ -1,10 +1,13 @@
migrator = $migrator;
- $this->packagePath = $packagePath;
}
/**
@@ -53,6 +49,8 @@ public function __construct(Migrator $migrator, $packagePath)
*/
public function fire()
{
+ if ( ! $this->confirmToProceed()) return;
+
$this->prepareDatabase();
// The pretend option can be used for "simulating" the migration and grabbing
@@ -60,7 +58,17 @@ public function fire()
// a database for real, which is helpful for double checking migrations.
$pretend = $this->input->getOption('pretend');
- $path = $this->getMigrationPath();
+ // Next, 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 = $this->input->getOption('path')))
+ {
+ $path = $this->laravel['path.base'].'/'.$path;
+ }
+ else
+ {
+ $path = $this->getMigrationPath();
+ }
$this->migrator->run($path, $pretend);
@@ -77,7 +85,7 @@ public function fire()
// a migration and a seed at the same time, as it is only this command.
if ($this->input->getOption('seed'))
{
- $this->call('db:seed');
+ $this->call('db:seed', ['--force' => true]);
}
}
@@ -106,13 +114,11 @@ protected function prepareDatabase()
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('force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'),
- array('package', null, InputOption::VALUE_OPTIONAL, 'The package to migrate.', null),
+ array('path', null, InputOption::VALUE_OPTIONAL, 'The path of migrations files to be executed.'),
array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'),
@@ -120,4 +126,4 @@ protected function getOptions()
);
}
-}
\ No newline at end of file
+}
diff --git a/Console/Migrations/MakeCommand.php b/Console/Migrations/MigrateMakeCommand.php
old mode 100755
new mode 100644
similarity index 77%
rename from Console/Migrations/MakeCommand.php
rename to Console/Migrations/MigrateMakeCommand.php
index 2ca9e7763..2aafca623
--- a/Console/Migrations/MakeCommand.php
+++ b/Console/Migrations/MigrateMakeCommand.php
@@ -1,17 +1,18 @@
creator = $creator;
- $this->packagePath = $packagePath;
+ $this->composer = $composer;
}
/**
@@ -64,19 +63,15 @@ public function fire()
$table = $this->input->getOption('table');
$create = $this->input->getOption('create');
-
- if ( ! $table && is_string($create))
- {
- $table = $create;
- }
+ if ( ! $table && is_string($create)) $table = $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');
+ $this->composer->dumpAutoloads();
}
/**
@@ -116,14 +111,8 @@ protected function getArguments()
protected function getOptions()
{
return array(
- array('bench', null, InputOption::VALUE_OPTIONAL, 'The workbench the migration belongs to.', null),
-
array('create', null, InputOption::VALUE_OPTIONAL, 'The table 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.'),
);
}
diff --git a/Console/Migrations/RefreshCommand.php b/Console/Migrations/RefreshCommand.php
index 330dc73a4..2adc6e82e 100755
--- a/Console/Migrations/RefreshCommand.php
+++ b/Console/Migrations/RefreshCommand.php
@@ -1,10 +1,13 @@
confirmToProceed()) return;
+
$database = $this->input->getOption('database');
- $this->call('migrate:reset', array('--database' => $database));
+ $force = $this->input->getOption('force');
+
+ $this->call('migrate:reset', array(
+ '--database' => $database, '--force' => $force
+ ));
// 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));
+ // them in succession. We'll also see if we need to re-seed the database.
+ $this->call('migrate', array(
+ '--database' => $database, '--force' => $force
+ ));
- if ($this->input->getOption('seed'))
+ if ($this->needsSeeding())
{
- $this->call('db:seed', array('--database' => $database));
+ $this->runSeeder($database);
}
}
+ /**
+ * Determine if the developer has requested database seeding.
+ *
+ * @return bool
+ */
+ protected function needsSeeding()
+ {
+ return $this->option('seed') || $this->option('seeder');
+ }
+
+ /**
+ * Run the database seeder command.
+ *
+ * @param string $database
+ * @return void
+ */
+ protected function runSeeder($database)
+ {
+ $class = $this->option('seeder') ?: 'DatabaseSeeder';
+
+ $this->call('db:seed', array('--database' => $database, '--class' => $class));
+ }
+
/**
* Get the console command options.
*
@@ -51,8 +85,12 @@ protected function getOptions()
return array(
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
+ array('force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'),
+
array('seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'),
+
+ array('seeder', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder.'),
);
}
-}
\ No newline at end of file
+}
diff --git a/Console/Migrations/ResetCommand.php b/Console/Migrations/ResetCommand.php
index 386858daa..f81fa907d 100755
--- a/Console/Migrations/ResetCommand.php
+++ b/Console/Migrations/ResetCommand.php
@@ -1,11 +1,14 @@
confirmToProceed()) return;
+
$this->migrator->setConnection($this->input->getOption('database'));
$pretend = $this->input->getOption('pretend');
@@ -77,8 +82,10 @@ protected function getOptions()
return array(
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
+ array('force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'),
+
array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'),
);
}
-}
\ No newline at end of file
+}
diff --git a/Console/Migrations/RollbackCommand.php b/Console/Migrations/RollbackCommand.php
index 5d2ab4bec..c11198f6f 100755
--- a/Console/Migrations/RollbackCommand.php
+++ b/Console/Migrations/RollbackCommand.php
@@ -1,11 +1,14 @@
confirmToProceed()) return;
+
$this->migrator->setConnection($this->input->getOption('database'));
$pretend = $this->input->getOption('pretend');
@@ -72,8 +77,10 @@ protected function getOptions()
return array(
array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'),
+ array('force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'),
+
array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'),
);
}
-}
\ No newline at end of file
+}
diff --git a/Console/Migrations/StatusCommand.php b/Console/Migrations/StatusCommand.php
new file mode 100644
index 000000000..c575112f8
--- /dev/null
+++ b/Console/Migrations/StatusCommand.php
@@ -0,0 +1,82 @@
+migrator = $migrator;
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return void
+ */
+ public function fire()
+ {
+ if ( ! $this->migrator->repositoryExists())
+ {
+ return $this->error('No migrations found.');
+ }
+
+ $ran = $this->migrator->getRepository()->getRan();
+
+ $migrations = [];
+
+ foreach ($this->getAllMigrationFiles() as $migration)
+ {
+ $migrations[] = in_array($migration, $ran) ? ['✔', $migration] : ['✗', $migration];
+ }
+
+ if (count($migrations) > 0)
+ {
+ $this->table(['Ran?', 'Migration'], $migrations);
+ }
+ else
+ {
+ $this->error('No migrations found');
+ }
+ }
+
+ /**
+ * Get all of the migration files.
+ *
+ * @return array
+ */
+ protected function getAllMigrationFiles()
+ {
+ return $this->migrator->getMigrationFiles($this->getMigrationPath());
+ }
+
+}
diff --git a/Console/SeedCommand.php b/Console/SeedCommand.php
index 7baf697ca..cba115b47 100755
--- a/Console/SeedCommand.php
+++ b/Console/SeedCommand.php
@@ -1,11 +1,14 @@
confirmToProceed()) return;
+
$this->resolver->setDefaultConnection($this->getDatabase());
$this->getSeeder()->run();
@@ -87,7 +92,9 @@ protected function getOptions()
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'),
+
+ array('force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production.'),
);
}
-}
\ No newline at end of file
+}
diff --git a/DatabaseManager.php b/DatabaseManager.php
index 82e6da7aa..45cde61b5 100755
--- a/DatabaseManager.php
+++ b/DatabaseManager.php
@@ -1,5 +1,7 @@
getDefaultConnection();
+ list($name, $type) = $this->parseConnectionName($name);
// 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
@@ -62,6 +64,8 @@ public function connection($name = null)
{
$connection = $this->makeConnection($name);
+ $this->setPdoForType($connection, $type);
+
$this->connections[$name] = $this->prepare($connection);
}
@@ -69,20 +73,32 @@ public function connection($name = null)
}
/**
- * Reconnect to the given database.
+ * Parse the connection into an array of the name and read / write type.
*
* @param string $name
- * @return \Illuminate\Database\Connection
+ * @return array
*/
- public function reconnect($name = null)
+ protected function parseConnectionName($name)
{
$name = $name ?: $this->getDefaultConnection();
+ return Str::endsWith($name, ['::read', '::write'])
+ ? explode('::', $name, 2) : [$name, null];
+ }
+
+ /**
+ * Disconnect from the given database and remove from local cache.
+ *
+ * @param string $name
+ * @return void
+ */
+ public function purge($name = null)
+ {
$this->disconnect($name);
- return $this->connection($name);
+ unset($this->connections[$name]);
}
-
+
/**
* Disconnect from the given database.
*
@@ -91,9 +107,43 @@ public function reconnect($name = null)
*/
public function disconnect($name = null)
{
- $name = $name ?: $this->getDefaultConnection();
+ if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()]))
+ {
+ $this->connections[$name]->disconnect();
+ }
+ }
- unset($this->connections[$name]);
+ /**
+ * Reconnect to the given database.
+ *
+ * @param string $name
+ * @return \Illuminate\Database\Connection
+ */
+ public function reconnect($name = null)
+ {
+ $this->disconnect($name = $name ?: $this->getDefaultConnection());
+
+ if ( ! isset($this->connections[$name]))
+ {
+ return $this->connection($name);
+ }
+
+ return $this->refreshPdoConnections($name);
+ }
+
+ /**
+ * Refresh the PDO connections on a given connection.
+ *
+ * @param string $name
+ * @return \Illuminate\Database\Connection
+ */
+ protected function refreshPdoConnections($name)
+ {
+ $fresh = $this->makeConnection($name);
+
+ return $this->connections[$name]
+ ->setPdo($fresh->getPdo())
+ ->setReadPdo($fresh->getReadPdo());
}
/**
@@ -111,7 +161,7 @@ protected function makeConnection($name)
// 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);
+ return call_user_func($this->extensions[$name], $config, $name);
}
$driver = $config['driver'];
@@ -121,7 +171,7 @@ protected function makeConnection($name)
// resolver for the drivers themselves which applies to all connections.
if (isset($this->extensions[$driver]))
{
- return call_user_func($this->extensions[$driver], $config);
+ return call_user_func($this->extensions[$driver], $config, $name);
}
return $this->factory->make($config, $name);
@@ -142,23 +192,34 @@ protected function prepare(Connection $connection)
$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)
+ // Here we'll set a reconnector callback. This reconnector can be any callable
+ // so we will set a Closure to reconnect from this manager with the name of
+ // the connection, which will allow us to reconnect from the connections.
+ $connection->setReconnector(function($connection)
{
- return $app['cache'];
+ $this->reconnect($connection->getName());
});
- // 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 $connection;
+ }
+
+ /**
+ * Prepare the read write mode for database connection instance.
+ *
+ * @param \Illuminate\Database\Connection $connection
+ * @param string $type
+ * @return \Illuminate\Database\Connection
+ */
+ protected function setPdoForType(Connection $connection, $type = null)
+ {
+ if ($type == 'read')
{
- return $app['paginator'];
- });
+ $connection->setPdo($connection->getReadPdo());
+ }
+ elseif ($type == 'write')
+ {
+ $connection->setReadPdo($connection->getPdo());
+ }
return $connection;
}
@@ -182,7 +243,7 @@ protected function getConfig($name)
if (is_null($config = array_get($connections, $name)))
{
- throw new \InvalidArgumentException("Database [$name] not configured.");
+ throw new InvalidArgumentException("Database [$name] not configured.");
}
return $config;
@@ -216,7 +277,7 @@ public function setDefaultConnection($name)
* @param callable $resolver
* @return void
*/
- public function extend($name, $resolver)
+ public function extend($name, callable $resolver)
{
$this->extensions[$name] = $resolver;
}
diff --git a/DatabaseServiceProvider.php b/DatabaseServiceProvider.php
index e91f5a5ad..a4b6eee22 100755
--- a/DatabaseServiceProvider.php
+++ b/DatabaseServiceProvider.php
@@ -1,45 +1,60 @@
-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->bindShared('db.factory', 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->bindShared('db', function($app)
- {
- return new DatabaseManager($app, $app['db.factory']);
- });
- }
-
-}
\ No newline at end of file
+app['db']);
+
+ Model::setEventDispatcher($this->app['events']);
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->registerQueueableEntityResolver();
+
+ // 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->singleton('db.factory', 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->singleton('db', function($app)
+ {
+ return new DatabaseManager($app, $app['db.factory']);
+ });
+ }
+
+ /**
+ * Register the queueable entity resolver implementation.
+ *
+ * @return void
+ */
+ protected function registerQueueableEntityResolver()
+ {
+ $this->app->singleton('Illuminate\Contracts\Queue\EntityResolver', function()
+ {
+ return new Eloquent\QueueEntityResolver;
+ });
+ }
+
+}
diff --git a/Eloquent/Builder.php b/Eloquent/Builder.php
index b4ff6415a..2747e2332 100755
--- a/Eloquent/Builder.php
+++ b/Eloquent/Builder.php
@@ -1,7 +1,9 @@
findMany($id, $columns);
+ return $this->findMany($id, $columns);
}
- $this->query->where($this->model->getKeyName(), '=', $id);
+ $this->query->where($this->model->getQualifiedKeyName(), '=', $id);
return $this->first($columns);
}
@@ -77,25 +93,36 @@ public function find($id, $columns = array('*'))
*/
public function findMany($id, $columns = array('*'))
{
- $this->query->whereIn($this->model->getKeyName(), $id);
+ if (empty($id)) return $this->model->newCollection();
+
+ $this->query->whereIn($this->model->getQualifiedKeyName(), $id);
return $this->get($columns);
- }
+ }
/**
* Find a model by its primary key or throw an exception.
*
* @param mixed $id
* @param array $columns
- * @return \Illuminate\Database\Eloquent\Model|static
+ * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
*
- * @throws ModelNotFoundException
+ * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findOrFail($id, $columns = array('*'))
{
- if ( ! is_null($model = $this->find($id, $columns))) return $model;
+ $result = $this->find($id, $columns);
- throw new ModelNotFoundException;
+ if (is_array($id))
+ {
+ if (count($result) == count(array_unique($id))) return $result;
+ }
+ elseif ( ! is_null($result))
+ {
+ return $result;
+ }
+
+ throw (new ModelNotFoundException)->setModel(get_class($this->model));
}
/**
@@ -115,13 +142,13 @@ public function first($columns = array('*'))
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|static
*
- * @throws ModelNotFoundException
+ * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function firstOrFail($columns = array('*'))
{
if ( ! is_null($model = $this->first($columns))) return $model;
- throw new ModelNotFoundException;
+ throw (new ModelNotFoundException)->setModel(get_class($this->model));
}
/**
@@ -165,7 +192,7 @@ public function pluck($column)
* @param callable $callback
* @return void
*/
- public function chunk($count, $callback)
+ public function chunk($count, callable $callback)
{
$results = $this->forPage($page = 1, $count)->get();
@@ -210,63 +237,44 @@ public function lists($column, $key = null)
}
/**
- * Get a paginator for the "select" statement.
+ * Paginate the given query.
*
- * @param int $perPage
+ * @param int $perPage
* @param array $columns
- * @return \Illuminate\Pagination\Paginator
+ * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
- public function paginate($perPage = null, $columns = array('*'))
+ public function paginate($perPage = 15, $columns = ['*'])
{
- $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);
- }
- }
+ $total = $this->query->getCountForPagination();
- /**
- * 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();
+ $this->query->forPage(
+ $page = Paginator::resolveCurrentPage(),
+ $perPage = $perPage ?: $this->model->getPerPage()
+ );
- return $this->query->buildRawPaginator($paginator, $results, $perPage);
+ return new LengthAwarePaginator($this->get($columns)->all(), $total, $perPage, $page, [
+ 'path' => Paginator::resolveCurrentPath()
+ ]);
}
/**
- * Get a paginator for an ungrouped statement.
+ * Paginate the given query into a simple paginator.
*
- * @param \Illuminate\Pagination\Environment $paginator
- * @param int $perPage
+ * @param int $perPage
* @param array $columns
- * @return \Illuminate\Pagination\Paginator
+ * @return \Illuminate\Contracts\Pagination\Paginator
*/
- protected function ungroupedPaginate($paginator, $perPage, $columns)
+ public function simplePaginate($perPage = null, $columns = ['*'])
{
- $total = $this->query->getPaginationCount();
+ $page = Paginator::resolveCurrentPage();
- // 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($total);
+ $perPage = $perPage ?: $this->model->getPerPage();
- $this->query->forPage($page, $perPage);
+ $this->skip(($page - 1) * $perPage)->take($perPage + 1);
- return $paginator->make($this->get($columns)->all(), $total, $perPage);
+ return new Paginator($this->get($columns)->all(), $perPage, $page, [
+ 'path' => Paginator::resolveCurrentPath()
+ ]);
}
/**
@@ -328,36 +336,22 @@ protected function addUpdatedAtColumn(array $values)
/**
* Delete a record from the database.
*
- * @return int
+ * @return mixed
*/
public function delete()
{
- if ($this->model->isSoftDeleting())
+ if (isset($this->onDelete))
{
- return $this->softDelete();
+ return call_user_func($this->onDelete, $this);
}
- 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()));
+ return $this->query->delete();
}
/**
- * Force a delete on a set of soft deleted models.
+ * Run the default delete function on the builder.
*
- * @return int
+ * @return mixed
*/
public function forceDelete()
{
@@ -365,76 +359,21 @@ public function forceDelete()
}
/**
- * 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 ((array) $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.
+ * Register a replacement for the default delete function.
*
- * @param array $where
- * @param string $column
- * @return bool
+ * @param \Closure $callback
+ * @return void
*/
- protected function isSoftDeleteConstraint(array $where, $column)
+ public function onDelete(Closure $callback)
{
- return $where['column'] == $column && $where['type'] == 'Null';
+ $this->onDelete = $callback;
}
/**
* Get the hydrated models without eager loading.
*
* @param array $columns
- * @return array|static[]
+ * @return \Illuminate\Database\Eloquent\Model[]
*/
public function getModels($columns = array('*'))
{
@@ -506,7 +445,7 @@ protected function loadRelation(array $models, $name, Closure $constraints)
// 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();
+ $results = $relation->getEager();
return $relation->match($models, $results, $name);
}
@@ -519,14 +458,12 @@ protected function loadRelation(array $models, $name, Closure $constraints)
*/
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)
+ $query = Relation::noConstraints(function() use ($relation)
{
- return $me->getModel()->$relation();
+ return $this->getModel()->$relation();
});
$nested = $this->nestedRelations($relation);
@@ -577,7 +514,47 @@ protected function isNested($name, $relation)
{
$dots = str_contains($name, '.');
- return $dots && starts_with($name, $relation) && $name != $relation;
+ return $dots && starts_with($name, $relation.'.');
+ }
+
+ /**
+ * Add a basic where clause to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param mixed $value
+ * @param string $boolean
+ * @return $this
+ */
+ public function where($column, $operator = null, $value = null, $boolean = 'and')
+ {
+ if ($column instanceof Closure)
+ {
+ $query = $this->model->newQueryWithoutScopes();
+
+ call_user_func($column, $query);
+
+ $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
+ }
+ else
+ {
+ call_user_func_array(array($this->query, 'where'), func_get_args());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add an "or where" clause to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param mixed $value
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ public function orWhere($column, $operator = null, $value = null)
+ {
+ return $this->where($column, $operator, $value, 'or');
}
/**
@@ -587,14 +564,19 @@ protected function isNested($name, $relation)
* @param string $operator
* @param int $count
* @param string $boolean
- * @param \Closure $callback
+ * @param \Closure|null $callback
* @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
+ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
{
+ if (strpos($relation, '.') !== false)
+ {
+ return $this->hasNested($relation, $operator, $count, $boolean, $callback);
+ }
+
$relation = $this->getHasRelationQuery($relation);
- $query = $relation->getRelationCountQuery($relation->getRelated()->newQuery());
+ $query = $relation->getRelationCountQuery($relation->getRelated()->newQuery(), $this);
if ($callback) call_user_func($callback, $query);
@@ -602,12 +584,57 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', $
}
/**
- * Add a relationship count condition to the query with where clauses.
+ * Add nested relationship count conditions to the query.
*
- * @param string $relation
- * @param \Closure $callback
+ * @param string $relations
* @param string $operator
* @param int $count
+ * @param string $boolean
+ * @param \Closure $callback
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
+ {
+ $relations = explode('.', $relations);
+
+ // In order to nest "has", we need to add count relation constraints on the
+ // callback Closure. We'll do this by simply passing the Closure its own
+ // reference to itself so it calls itself recursively on each segment.
+ $closure = function ($q) use (&$closure, &$relations, $operator, $count, $boolean, $callback)
+ {
+ if (count($relations) > 1)
+ {
+ $q->whereHas(array_shift($relations), $closure);
+ }
+ else
+ {
+ $q->has(array_shift($relations), $operator, $count, $boolean, $callback);
+ }
+ };
+
+ return $this->whereHas(array_shift($relations), $closure);
+ }
+
+ /**
+ * Add a relationship count condition to the query.
+ *
+ * @param string $relation
+ * @param string $boolean
+ * @param \Closure|null $callback
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ public function doesntHave($relation, $boolean = 'and', Closure $callback = null)
+ {
+ return $this->has($relation, '<', 1, $boolean, $callback);
+ }
+
+ /**
+ * Add a relationship count condition to the query with where clauses.
+ *
+ * @param string $relation
+ * @param \Closure $callback
+ * @param string $operator
+ * @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
@@ -615,6 +642,18 @@ public function whereHas($relation, Closure $callback, $operator = '>=', $count
return $this->has($relation, $operator, $count, 'and', $callback);
}
+ /**
+ * Add a relationship count condition to the query with where clauses.
+ *
+ * @param string $relation
+ * @param \Closure|null $callback
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ public function whereDoesntHave($relation, Closure $callback = null)
+ {
+ return $this->doesntHave($relation, 'and', $callback);
+ }
+
/**
* Add a relationship count condition to the query with an "or".
*
@@ -631,10 +670,10 @@ public function orHas($relation, $operator = '>=', $count = 1)
/**
* Add a relationship count condition to the query with where clauses and an "or".
*
- * @param string $relation
+ * @param string $relation
* @param \Closure $callback
- * @param string $operator
- * @param int $count
+ * @param string $operator
+ * @param int $count
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1)
@@ -656,6 +695,11 @@ protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator,
{
$this->mergeWheresToHas($hasQuery, $relation);
+ if (is_numeric($count))
+ {
+ $count = new Expression($count);
+ }
+
return $this->where(new Expression('('.$hasQuery->toSql().')'), $operator, $count, $boolean);
}
@@ -673,6 +717,8 @@ protected function mergeWheresToHas(Builder $hasQuery, Relation $relation)
// the has query, and then copy the bindings from the "has" query to the main.
$relationQuery = $relation->getBaseQuery();
+ $hasQuery = $hasQuery->getModel()->removeGlobalScopes($hasQuery);
+
$hasQuery->mergeWheres(
$relationQuery->wheres, $relationQuery->getBindings()
);
@@ -688,19 +734,17 @@ protected function mergeWheresToHas(Builder $hasQuery, Relation $relation)
*/
protected function getHasRelationQuery($relation)
{
- $me = $this;
-
- return Relation::noConstraints(function() use ($me, $relation)
+ return Relation::noConstraints(function() use ($relation)
{
- return $me->getModel()->$relation();
+ return $this->getModel()->$relation();
});
}
/**
* Set the relationships that should be eager loaded.
*
- * @param dynamic $relations
- * @return \Illuminate\Database\Eloquent\Builder|static
+ * @param mixed $relations
+ * @return $this
*/
public function with($relations)
{
@@ -766,8 +810,8 @@ protected function parseNested($name, $results)
if ( ! isset($results[$last = implode('.', $progress)]))
{
- $results[$last] = function() {};
- }
+ $results[$last] = function() {};
+ }
}
return $results;
@@ -777,7 +821,7 @@ protected function parseNested($name, $results)
* Call the given model scope on the underlying model.
*
* @param string $scope
- * @param array $parameters
+ * @param array $parameters
* @return \Illuminate\Database\Query\Builder
*/
protected function callScope($scope, $parameters)
@@ -801,11 +845,13 @@ public function getQuery()
* Set the underlying query builder instance.
*
* @param \Illuminate\Database\Query\Builder $query
- * @return void
+ * @return $this
*/
public function setQuery($query)
{
$this->query = $query;
+
+ return $this;
}
/**
@@ -822,11 +868,13 @@ public function getEagerLoads()
* Set the relationships being eagerly loaded.
*
* @param array $eagerLoad
- * @return void
+ * @return $this
*/
public function setEagerLoads(array $eagerLoad)
{
$this->eagerLoad = $eagerLoad;
+
+ return $this;
}
/**
@@ -843,7 +891,7 @@ public function getModel()
* Set a model instance for the model being queried.
*
* @param \Illuminate\Database\Eloquent\Model $model
- * @return \Illuminate\Database\Eloquent\Builder
+ * @return $this
*/
public function setModel(Model $model)
{
@@ -854,6 +902,29 @@ public function setModel(Model $model)
return $this;
}
+ /**
+ * Extend the builder with a given callback.
+ *
+ * @param string $name
+ * @param \Closure $callback
+ * @return void
+ */
+ public function macro($name, Closure $callback)
+ {
+ $this->macros[$name] = $callback;
+ }
+
+ /**
+ * Get the given macro by name.
+ *
+ * @param string $name
+ * @return \Closure
+ */
+ public function getMacro($name)
+ {
+ return array_get($this->macros, $name);
+ }
+
/**
* Dynamically handle calls into the query instance.
*
@@ -863,15 +934,19 @@ public function setModel(Model $model)
*/
public function __call($method, $parameters)
{
- if (method_exists($this->model, $scope = 'scope'.ucfirst($method)))
+ if (isset($this->macros[$method]))
{
- return $this->callScope($scope, $parameters);
+ array_unshift($parameters, $this);
+
+ return call_user_func_array($this->macros[$method], $parameters);
}
- else
+ elseif (method_exists($this->model, $scope = 'scope'.ucfirst($method)))
{
- $result = call_user_func_array(array($this->query, $method), $parameters);
+ return $this->callScope($scope, $parameters);
}
+ $result = call_user_func_array(array($this->query, $method), $parameters);
+
return in_array($method, $this->passthru) ? $result : $this;
}
diff --git a/Eloquent/Collection.php b/Eloquent/Collection.php
index 7c83ebb0e..ae661431d 100755
--- a/Eloquent/Collection.php
+++ b/Eloquent/Collection.php
@@ -28,8 +28,8 @@ public function find($key, $default = null)
/**
* Load a set of relationships onto the collection.
*
- * @param dynamic $relations
- * @return \Illuminate\Database\Eloquent\Collection
+ * @param mixed $relations
+ * @return $this
*/
public function load($relations)
{
@@ -49,7 +49,7 @@ public function load($relations)
* Add an item to the collection.
*
* @param mixed $item
- * @return \Illuminate\Database\Eloquent\Collection
+ * @return $this
*/
public function add($item)
{
@@ -62,18 +62,24 @@ public function add($item)
* Determine if a key exists in the collection.
*
* @param mixed $key
+ * @param mixed $value
* @return bool
*/
- public function contains($key)
+ public function contains($key, $value = null)
{
- return ! is_null($this->find($key));
+ if (func_num_args() == 1)
+ {
+ return ! is_null($this->find($key));
+ }
+
+ return parent::contains($key, $value);
}
/**
* Fetch a nested element of the collection.
*
* @param string $key
- * @return \Illuminate\Support\Collection
+ * @return static
*/
public function fetch($key)
{
@@ -121,14 +127,14 @@ public function modelKeys()
/**
* Merge the collection with the given items.
*
- * @param \Illuminate\Support\Collection|\Illuminate\Support\Contracts\ArrayableInterface|array $items
- * @return \Illuminate\Support\Collection
+ * @param \ArrayAccess|array $items
+ * @return static
*/
- public function merge($collection)
+ public function merge($items)
{
- $dictionary = $this->getDictionary($this);
+ $dictionary = $this->getDictionary();
- foreach ($collection as $item)
+ foreach ($items as $item)
{
$dictionary[$item->getKey()] = $item;
}
@@ -139,14 +145,14 @@ public function merge($collection)
/**
* Diff the collection with the given items.
*
- * @param \Illuminate\Support\Collection|\Illuminate\Support\Contracts\ArrayableInterface|array $items
- * @return \Illuminate\Support\Collection
+ * @param \ArrayAccess|array $items
+ * @return static
*/
- public function diff($collection)
+ public function diff($items)
{
$diff = new static;
- $dictionary = $this->getDictionary($collection);
+ $dictionary = $this->getDictionary($items);
foreach ($this->items as $item)
{
@@ -162,14 +168,14 @@ public function diff($collection)
/**
* Intersect the collection with the given items.
*
- * @param \Illuminate\Support\Collection|\Illuminate\Support\Contracts\ArrayableInterface|array $items
- * @return \Illuminate\Support\Collection
+ * @param \ArrayAccess|array $items
+ * @return static
*/
- public function intersect($collection)
+ public function intersect($items)
{
$intersect = new static;
- $dictionary = $this->getDictionary($collection);
+ $dictionary = $this->getDictionary($items);
foreach ($this->items as $item)
{
@@ -185,26 +191,54 @@ public function intersect($collection)
/**
* Return only unique items from the collection.
*
- * @return \Illuminate\Support\Collection
+ * @return static
*/
public function unique()
{
- $dictionary = $this->getDictionary($this);
+ $dictionary = $this->getDictionary();
return new static(array_values($dictionary));
}
- /*
+ /**
+ * Returns only the models from the collection with the specified keys.
+ *
+ * @param mixed $keys
+ * @return static
+ */
+ public function only($keys)
+ {
+ $dictionary = array_only($this->getDictionary(), $keys);
+
+ return new static(array_values($dictionary));
+ }
+
+ /**
+ * Returns all models in the collection except the models with specified keys.
+ *
+ * @param mixed $keys
+ * @return static
+ */
+ public function except($keys)
+ {
+ $dictionary = array_except($this->getDictionary(), $keys);
+
+ return new static(array_values($dictionary));
+ }
+
+ /**
* Get a dictionary keyed by primary keys.
*
- * @param \Illuminate\Support\Collection $collection
+ * @param \ArrayAccess|array $items
* @return array
*/
- protected function getDictionary($collection)
+ public function getDictionary($items = null)
{
+ $items = is_null($items) ? $this->items : $items;
+
$dictionary = array();
- foreach ($collection as $value)
+ foreach ($items as $value)
{
$dictionary[$value->getKey()] = $value;
}
@@ -212,4 +246,14 @@ protected function getDictionary($collection)
return $dictionary;
}
+ /**
+ * Get a base Support collection instance from this collection.
+ *
+ * @return \Illuminate\Support\Collection
+ */
+ public function toBase()
+ {
+ return new BaseCollection($this->items);
+ }
+
}
diff --git a/Eloquent/MassAssignmentException.php b/Eloquent/MassAssignmentException.php
index 9352aed3c..8874c7c5c 100755
--- a/Eloquent/MassAssignmentException.php
+++ b/Eloquent/MassAssignmentException.php
@@ -1,3 +1,5 @@
bootIfNotBooted();
+
+ $this->syncOriginal();
+
+ $this->fill($attributes);
+ }
/**
- * Create a new Eloquent model instance.
+ * Check if the model needs to be booted and if so, do it.
*
- * @param array $attributes
* @return void
*/
- public function __construct(array $attributes = array())
+ protected function bootIfNotBooted()
{
- if ( ! isset(static::$booted[get_class($this)]))
+ $class = get_class($this);
+
+ if ( ! isset(static::$booted[$class]))
{
- static::$booted[get_class($this)] = true;
+ static::$booted[$class] = true;
$this->fireModelEvent('booting', false);
@@ -251,10 +278,6 @@ public function __construct(array $attributes = array())
$this->fireModelEvent('booted', false);
}
-
- $this->syncOriginal();
-
- $this->fill($attributes);
}
/**
@@ -280,6 +303,70 @@ protected static function boot()
static::$mutatorCache[$class][] = lcfirst($matches[1]);
}
}
+
+ static::bootTraits();
+ }
+
+ /**
+ * Boot all of the bootable traits on the model.
+ *
+ * @return void
+ */
+ protected static function bootTraits()
+ {
+ foreach (class_uses_recursive(get_called_class()) as $trait)
+ {
+ if (method_exists(get_called_class(), $method = 'boot'.class_basename($trait)))
+ {
+ forward_static_call([get_called_class(), $method]);
+ }
+ }
+ }
+
+ /**
+ * Register a new global scope on the model.
+ *
+ * @param \Illuminate\Database\Eloquent\ScopeInterface $scope
+ * @return void
+ */
+ public static function addGlobalScope(ScopeInterface $scope)
+ {
+ static::$globalScopes[get_called_class()][get_class($scope)] = $scope;
+ }
+
+ /**
+ * Determine if a model has a global scope.
+ *
+ * @param \Illuminate\Database\Eloquent\ScopeInterface $scope
+ * @return bool
+ */
+ public static function hasGlobalScope($scope)
+ {
+ return ! is_null(static::getGlobalScope($scope));
+ }
+
+ /**
+ * Get a global scope registered with the model.
+ *
+ * @param \Illuminate\Database\Eloquent\ScopeInterface $scope
+ * @return \Illuminate\Database\Eloquent\ScopeInterface|null
+ */
+ public static function getGlobalScope($scope)
+ {
+ return array_first(static::$globalScopes[get_called_class()], function($key, $value) use ($scope)
+ {
+ return $scope instanceof $value;
+ });
+ }
+
+ /**
+ * Get the global scopes for this class instance.
+ *
+ * @return \Illuminate\Database\Eloquent\ScopeInterface[]
+ */
+ public function getGlobalScopes()
+ {
+ return array_get(static::$globalScopes, get_class($this), []);
}
/**
@@ -310,12 +397,14 @@ public static function observe($class)
* Fill the model with an array of attributes.
*
* @param array $attributes
- * @return \Illuminate\Database\Eloquent\Model|static
+ * @return $this
*
- * @throws MassAssignmentException
+ * @throws \Illuminate\Database\Eloquent\MassAssignmentException
*/
public function fill(array $attributes)
{
+ $totallyGuarded = $this->totallyGuarded();
+
foreach ($this->fillableFromArray($attributes) as $key => $value)
{
$key = $this->removeTableFromKey($key);
@@ -327,7 +416,7 @@ public function fill(array $attributes)
{
$this->setAttribute($key, $value);
}
- elseif ($this->totallyGuarded())
+ elseif ($totallyGuarded)
{
throw new MassAssignmentException($key);
}
@@ -336,6 +425,23 @@ public function fill(array $attributes)
return $this;
}
+ /**
+ * Fill the model with an array of attributes. Force mass assignment.
+ *
+ * @param array $attributes
+ * @return $this
+ */
+ public function forceFill(array $attributes)
+ {
+ static::unguard();
+
+ $this->fill($attributes);
+
+ static::reguard();
+
+ return $this;
+ }
+
/**
* Get the fillable attributes of a given array.
*
@@ -357,7 +463,7 @@ protected function fillableFromArray(array $attributes)
*
* @param array $attributes
* @param bool $exists
- * @return \Illuminate\Database\Eloquent\Model|static
+ * @return static
*/
public function newInstance($attributes = array(), $exists = false)
{
@@ -375,7 +481,7 @@ public function newInstance($attributes = array(), $exists = false)
* Create a new model instance that is existing.
*
* @param array $attributes
- * @return \Illuminate\Database\Eloquent\Model|static
+ * @return static
*/
public function newFromBuilder($attributes = array())
{
@@ -386,11 +492,59 @@ public function newFromBuilder($attributes = array())
return $instance;
}
+ /**
+ * Create a collection of models from plain arrays.
+ *
+ * @param array $items
+ * @param string $connection
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ public static function hydrate(array $items, $connection = null)
+ {
+ $collection = with($instance = new static)->newCollection();
+
+ foreach ($items as $item)
+ {
+ $model = $instance->newFromBuilder($item);
+
+ if ( ! is_null($connection))
+ {
+ $model->setConnection($connection);
+ }
+
+ $collection->push($model);
+ }
+
+ return $collection;
+ }
+
+ /**
+ * Create a collection of models from a raw query.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @param string $connection
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ public static function hydrateRaw($query, $bindings = array(), $connection = null)
+ {
+ $instance = new static;
+
+ if ( ! is_null($connection))
+ {
+ $instance->setConnection($connection);
+ }
+
+ $items = $instance->getConnection()->select($query, $bindings);
+
+ return static::hydrate($items, $connection);
+ }
+
/**
* Save a new model and return the instance.
*
* @param array $attributes
- * @return \Illuminate\Database\Eloquent\Model|static
+ * @return static
*/
public static function create(array $attributes)
{
@@ -401,15 +555,32 @@ public static function create(array $attributes)
return $model;
}
+ /**
+ * Save a new model and return the instance. Allow mass-assignment.
+ *
+ * @param array $attributes
+ * @return static
+ */
+ public static function forceCreate(array $attributes)
+ {
+ static::unguard();
+
+ $model = static::create($attributes);
+
+ static::reguard();
+
+ return $model;
+ }
+
/**
* Get the first record matching the attributes or create it.
*
* @param array $attributes
- * @return \Illuminate\Database\Eloquent\Model
+ * @return static
*/
public static function firstOrCreate(array $attributes)
{
- if ( ! is_null($instance = static::firstByAttributes($attributes)))
+ if ( ! is_null($instance = static::where($attributes)->first()))
{
return $instance;
}
@@ -421,11 +592,11 @@ public static function firstOrCreate(array $attributes)
* Get the first record matching the attributes or instantiate it.
*
* @param array $attributes
- * @return \Illuminate\Database\Eloquent\Model
+ * @return static
*/
public static function firstOrNew(array $attributes)
{
- if ( ! is_null($instance = static::firstByAttributes($attributes)))
+ if ( ! is_null($instance = static::where($attributes)->first()))
{
return $instance;
}
@@ -434,38 +605,47 @@ public static function firstOrNew(array $attributes)
}
/**
- * Get the first model for the given attributes.
+ * Create or update a record matching the attributes, and fill it with values.
*
* @param array $attributes
- * @return \Illuminate\Database\Eloquent\Model|null
+ * @param array $values
+ * @return static
*/
- protected static function firstByAttributes($attributes)
+ public static function updateOrCreate(array $attributes, array $values = array())
{
- $query = static::query();
+ $instance = static::firstOrNew($attributes);
- foreach ($attributes as $key => $value)
- {
- $query->where($key, $value);
- }
+ $instance->fill($values)->save();
- return $query->first() ?: null;
+ return $instance;
+ }
+
+ /**
+ * Get the first model for the given attributes.
+ *
+ * @param array $attributes
+ * @return static|null
+ */
+ protected static function firstByAttributes($attributes)
+ {
+ return static::where($attributes)->first();
}
/**
* Begin querying the model.
*
- * @return \Illuminate\Database\Eloquent\Builder|static
+ * @return \Illuminate\Database\Eloquent\Builder
*/
public static function query()
{
- return with(new static)->newQuery();
+ return (new static)->newQuery();
}
/**
* Begin querying the model on a given connection.
*
* @param string $connection
- * @return \Illuminate\Database\Eloquent\Builder|static
+ * @return \Illuminate\Database\Eloquent\Builder
*/
public static function on($connection = null)
{
@@ -479,6 +659,18 @@ public static function on($connection = null)
return $instance->newQuery();
}
+ /**
+ * Begin querying the model on the write connection.
+ *
+ * @return \Illuminate\Database\Query\Builder
+ */
+ public static function onWriteConnection()
+ {
+ $instance = new static;
+
+ return $instance->newQuery()->useWritePdo();
+ }
+
/**
* Get all of the models from the database.
*
@@ -497,36 +689,49 @@ public static function all($columns = array('*'))
*
* @param mixed $id
* @param array $columns
- * @return \Illuminate\Database\Eloquent\Model|Collection|static
+ * @return \Illuminate\Support\Collection|static|null
*/
public static function find($id, $columns = array('*'))
{
$instance = new static;
+ if (is_array($id) && empty($id)) return $instance->newCollection();
+
return $instance->newQuery()->find($id, $columns);
}
/**
- * Find a model by its primary key or throw an exception.
+ * Find a model by its primary key or return new static.
*
* @param mixed $id
* @param array $columns
- * @return \Illuminate\Database\Eloquent\Model|Collection|static
- *
- * @throws ModelNotFoundException
+ * @return \Illuminate\Support\Collection|static
*/
- public static function findOrFail($id, $columns = array('*'))
+ public static function findOrNew($id, $columns = array('*'))
{
if ( ! is_null($model = static::find($id, $columns))) return $model;
- throw new ModelNotFoundException(get_called_class().' model not found');
+ return new static;
+ }
+
+ /**
+ * Reload a fresh model instance from the database.
+ *
+ * @param array $with
+ * @return $this
+ */
+ public function fresh(array $with = array())
+ {
+ $key = $this->getKeyName();
+
+ return $this->exists ? static::with($with)->where($key, $this->getKey())->first() : null;
}
/**
* Eager load relations on the model.
*
* @param array|string $relations
- * @return \Illuminate\Database\Eloquent\Model
+ * @return $this
*/
public function load($relations)
{
@@ -580,6 +785,7 @@ public function hasOne($related, $foreignKey = null, $localKey = null)
* @param string $name
* @param string $type
* @param string $id
+ * @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
@@ -608,10 +814,10 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
- // of the time this will be what we desire to use for the relatinoships.
+ // of the time this will be what we desire to use for the relationships.
if (is_null($relation))
{
- list(, $caller) = debug_backtrace(false);
+ list(, $caller) = debug_backtrace(false, 2);
$relation = $caller['function'];
}
@@ -637,12 +843,12 @@ public function belongsTo($related, $foreignKey = null, $otherKey = null, $relat
}
/**
- * Define an polymorphic, inverse one-to-one or many relationship.
+ * Define a polymorphic, inverse one-to-one or many relationship.
*
* @param string $name
* @param string $type
* @param string $id
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ * @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphTo($name = null, $type = null, $id = null)
{
@@ -651,19 +857,34 @@ public function morphTo($name = null, $type = null, $id = null)
// use that to get both the class and foreign key that will be utilized.
if (is_null($name))
{
- list(, $caller) = debug_backtrace(false);
+ list(, $caller) = debug_backtrace(false, 2);
$name = snake_case($caller['function']);
}
- // Next we will guess the type and ID if necessary. The type and IDs may also
- // be passed into the function so that the developers may manually specify
- // them on the relations. Otherwise, we will just make a great estimate.
list($type, $id) = $this->getMorphs($name, $type, $id);
- $class = $this->$type;
+ // If the type value is null it is probably safe to assume we're eager loading
+ // the relationship. When that is the case we will pass in a dummy query as
+ // there are multiple types in the morph and we can't use single queries.
+ if (is_null($class = $this->$type))
+ {
+ return new MorphTo(
+ $this->newQuery(), $this, $id, null, $type, $name
+ );
+ }
+
+ // If we are not eager loading the relationship we will essentially treat this
+ // as a belongs-to style relationship since morph-to extends that class and
+ // we will pass in the appropriate values so that it behaves as expected.
+ else
+ {
+ $instance = new $class;
- return $this->belongsTo($class, $id);
+ return new MorphTo(
+ $instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
+ );
+ }
}
/**
@@ -702,7 +923,7 @@ public function hasManyThrough($related, $through, $firstKey = null, $secondKey
$secondKey = $secondKey ?: $through->getForeignKey();
- return new HasManyThrough(with(new $related)->newQuery(), $this, $through, $firstKey, $secondKey);
+ return new HasManyThrough((new $related)->newQuery(), $this, $through, $firstKey, $secondKey);
}
/**
@@ -748,9 +969,7 @@ public function belongsToMany($related, $table = null, $foreignKey = null, $othe
// title of this relation since that is a great convention to apply.
if (is_null($relation))
{
- $caller = $this->getBelongsToManyCaller();
-
- $name = $caller['function'];
+ $relation = $this->getBelongsToManyCaller();
}
// First, we'll need to determine the foreign key and "other key" for the
@@ -779,14 +998,14 @@ public function belongsToMany($related, $table = null, $foreignKey = null, $othe
}
/**
- * Define a many-to-many relationship.
+ * Define a polymorphic many-to-many relationship.
*
* @param string $related
* @param string $name
* @param string $table
* @param string $foreignKey
* @param string $otherKey
- * @param bool $inverse
+ * @param bool $inverse
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function morphToMany($related, $name, $table = null, $foreignKey = null, $otherKey = null, $inverse = false)
@@ -811,19 +1030,18 @@ public function morphToMany($related, $name, $table = null, $foreignKey = null,
return new MorphToMany(
$query, $this, $name, $table, $foreignKey,
- $otherKey, $caller['function'], $inverse
+ $otherKey, $caller, $inverse
);
}
/**
- * Define a many-to-many relationship.
+ * Define a polymorphic, inverse many-to-many relationship.
*
* @param string $related
* @param string $name
* @param string $table
* @param string $foreignKey
* @param string $otherKey
- * @param string $morphClass
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function morphedByMany($related, $name, $table = null, $foreignKey = null, $otherKey = null)
@@ -841,18 +1059,20 @@ public function morphedByMany($related, $name, $table = null, $foreignKey = null
/**
* Get the relationship name of the belongs to many.
*
- * @return string
+ * @return string
*/
protected function getBelongsToManyCaller()
{
$self = __FUNCTION__;
- return array_first(debug_backtrace(false), function($trace) use ($self)
+ $caller = array_first(debug_backtrace(false), function($key, $trace) use ($self)
{
$caller = $trace['function'];
return ( ! in_array($caller, Model::$manyMethods) && $caller != $self);
});
+
+ return ! is_null($caller) ? $caller['function'] : null;
}
/**
@@ -884,10 +1104,15 @@ public function joiningTable($related)
* Destroy the models for the given IDs.
*
* @param array|int $ids
- * @return void
+ * @return int
*/
public static function destroy($ids)
{
+ // We'll initialize a count here so we will return the total number of deletes
+ // for the operation. The developers can then check this number as a boolean
+ // type value or get this total count of records deleted for logging, etc.
+ $count = 0;
+
$ids = is_array($ids) ? $ids : func_get_args();
$instance = new static;
@@ -899,17 +1124,25 @@ public static function destroy($ids)
foreach ($instance->whereIn($key, $ids)->get() as $model)
{
- $model->delete();
+ if ($model->delete()) $count++;
}
+
+ return $count;
}
/**
* Delete the model from the database.
*
* @return bool|null
+ * @throws \Exception
*/
public function delete()
{
+ if (is_null($this->primaryKey))
+ {
+ throw new Exception("No primary key defined on model.");
+ }
+
if ($this->exists)
{
if ($this->fireModelEvent('deleting') === false) return false;
@@ -935,20 +1168,13 @@ public function delete()
/**
* Force a hard delete on a soft deleted model.
*
+ * This method protects developers from running forceDelete when trait is missing.
+ *
* @return void
*/
public function forceDelete()
{
- $softDelete = $this->softDelete;
-
- // We will temporarily disable false delete to allow us to perform the real
- // delete operation against the model. We will then restore the deleting
- // state to what this was prior to this given hard deleting operation.
- $this->softDelete = false;
-
- $this->delete();
-
- $this->softDelete = $softDelete;
+ return $this->delete();
}
/**
@@ -958,158 +1184,103 @@ public function forceDelete()
*/
protected function performDeleteOnModel()
{
- $query = $this->newQuery()->where($this->getKeyName(), $this->getKey());
-
- if ($this->softDelete)
- {
- $this->{static::DELETED_AT} = $time = $this->freshTimestamp();
-
- $query->update(array(static::DELETED_AT => $this->fromDateTime($time)));
- }
- else
- {
- $query->delete();
- }
- }
-
- /**
- * Restore a soft-deleted model instance.
- *
- * @return bool|null
- */
- public function restore()
- {
- if ($this->softDelete)
- {
- // If the restoring event does not return false, we will proceed with this
- // restore operation. Otherwise, we bail out so the developer will stop
- // the restore totally. We will clear the deleted timestamp and save.
- if ($this->fireModelEvent('restoring') === false)
- {
- return false;
- }
-
- $this->{static::DELETED_AT} = null;
-
- // Once we have saved the model, we will fire the "restored" event so this
- // developer will do anything they need to after a restore operation is
- // totally finished. Then we will return the result of the save call.
- $result = $this->save();
-
- $this->fireModelEvent('restored', false);
-
- return $result;
- }
+ $this->newQuery()->where($this->getKeyName(), $this->getKey())->delete();
}
/**
* Register a saving model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function saving($callback)
+ public static function saving($callback, $priority = 0)
{
- static::registerModelEvent('saving', $callback);
+ static::registerModelEvent('saving', $callback, $priority);
}
/**
* Register a saved model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function saved($callback)
+ public static function saved($callback, $priority = 0)
{
- static::registerModelEvent('saved', $callback);
+ static::registerModelEvent('saved', $callback, $priority);
}
/**
* Register an updating model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function updating($callback)
+ public static function updating($callback, $priority = 0)
{
- static::registerModelEvent('updating', $callback);
+ static::registerModelEvent('updating', $callback, $priority);
}
/**
* Register an updated model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function updated($callback)
+ public static function updated($callback, $priority = 0)
{
- static::registerModelEvent('updated', $callback);
+ static::registerModelEvent('updated', $callback, $priority);
}
/**
* Register a creating model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function creating($callback)
+ public static function creating($callback, $priority = 0)
{
- static::registerModelEvent('creating', $callback);
+ static::registerModelEvent('creating', $callback, $priority);
}
/**
* Register a created model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function created($callback)
+ public static function created($callback, $priority = 0)
{
- static::registerModelEvent('created', $callback);
+ static::registerModelEvent('created', $callback, $priority);
}
/**
* Register a deleting model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function deleting($callback)
+ public static function deleting($callback, $priority = 0)
{
- static::registerModelEvent('deleting', $callback);
+ static::registerModelEvent('deleting', $callback, $priority);
}
/**
* Register a deleted model event with the dispatcher.
*
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- public static function deleted($callback)
+ public static function deleted($callback, $priority = 0)
{
- static::registerModelEvent('deleted', $callback);
- }
-
- /**
- * Register a restoring model event with the dispatcher.
- *
- * @param \Closure|string $callback
- * @return void
- */
- public static function restoring($callback)
- {
- static::registerModelEvent('restoring', $callback);
- }
-
- /**
- * Register a restored model event with the dispatcher.
- *
- * @param \Closure|string $callback
- * @return void
- */
- public static function restored($callback)
- {
- static::registerModelEvent('restored', $callback);
+ static::registerModelEvent('deleted', $callback, $priority);
}
/**
@@ -1134,15 +1305,16 @@ public static function flushEventListeners()
*
* @param string $event
* @param \Closure|string $callback
+ * @param int $priority
* @return void
*/
- protected static function registerModelEvent($event, $callback)
+ protected static function registerModelEvent($event, $callback, $priority = 0)
{
if (isset(static::$dispatcher))
{
$name = get_called_class();
- static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback);
+ static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback, $priority);
}
}
@@ -1163,6 +1335,43 @@ public function getObservableEvents()
);
}
+ /**
+ * Set the observable event names.
+ *
+ * @param array $observables
+ * @return void
+ */
+ public function setObservableEvents(array $observables)
+ {
+ $this->observables = $observables;
+ }
+
+ /**
+ * Add an observable event name.
+ *
+ * @param mixed $observables
+ * @return void
+ */
+ public function addObservableEvents($observables)
+ {
+ $observables = is_array($observables) ? $observables : func_get_args();
+
+ $this->observables = array_unique(array_merge($this->observables, $observables));
+ }
+
+ /**
+ * Remove an observable event name.
+ *
+ * @param mixed $observables
+ * @return void
+ */
+ public function removeObservableEvents($observables)
+ {
+ $observables = is_array($observables) ? $observables : func_get_args();
+
+ $this->observables = array_diff($this->observables, $observables);
+ }
+
/**
* Increment a column's value by a given amount.
*
@@ -1204,14 +1413,31 @@ protected function incrementOrDecrement($column, $amount, $method)
return $query->{$method}($column, $amount);
}
+ $this->incrementOrDecrementAttributeValue($column, $amount, $method);
+
return $query->where($this->getKeyName(), $this->getKey())->{$method}($column, $amount);
}
+ /**
+ * Increment the underlying attribute value and sync with original.
+ *
+ * @param string $column
+ * @param int $amount
+ * @param string $method
+ * @return void
+ */
+ protected function incrementOrDecrementAttributeValue($column, $amount, $method)
+ {
+ $this->{$column} = $this->{$column} + ($method == 'increment' ? $amount : $amount * -1);
+
+ $this->syncOriginalAttribute($column);
+ }
+
/**
* Update the model in the database.
*
* @param array $attributes
- * @return mixed
+ * @return bool|int
*/
public function update(array $attributes = array())
{
@@ -1237,7 +1463,10 @@ public function push()
// us to recurse into all of these nested relations for the model instance.
foreach ($this->relations as $models)
{
- foreach (Collection::make($models) as $model)
+ $models = $models instanceof Collection
+ ? $models->all() : array($models);
+
+ foreach (array_filter($models) as $model)
{
if ( ! $model->push()) return false;
}
@@ -1254,10 +1483,10 @@ public function push()
*/
public function save(array $options = array())
{
- $query = $this->newQueryWithDeleted();
+ $query = $this->newQueryWithoutScopes();
// If the "saving" event returns false we'll bail out of the save and return
- // false, indicating that the save failed. This gives an opportunities to
+ // false, indicating that the save failed. This provides a chance for any
// listeners to cancel save operations if validations fail or whatever.
if ($this->fireModelEvent('saving') === false)
{
@@ -1269,7 +1498,7 @@ public function save(array $options = array())
// clause to only update this model. Otherwise, we'll just insert them.
if ($this->exists)
{
- $saved = $this->performUpdate($query);
+ $saved = $this->performUpdate($query, $options);
}
// If the model is brand new, we'll insert it into our database and set the
@@ -1277,7 +1506,7 @@ public function save(array $options = array())
// which is typically an auto-increment value managed by the database.
else
{
- $saved = $this->performInsert($query);
+ $saved = $this->performInsert($query, $options);
}
if ($saved) $this->finishSave($options);
@@ -1293,10 +1522,10 @@ public function save(array $options = array())
*/
protected function finishSave(array $options)
{
- $this->syncOriginal();
-
$this->fireModelEvent('saved', false);
+ $this->syncOriginal();
+
if (array_get($options, 'touch', true)) $this->touchOwners();
}
@@ -1304,9 +1533,10 @@ protected function finishSave(array $options)
* Perform a model update operation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
- * @return bool
+ * @param array $options
+ * @return bool|null
*/
- protected function performUpdate(Builder $query)
+ protected function performUpdate(Builder $query, array $options = [])
{
$dirty = $this->getDirty();
@@ -1323,19 +1553,22 @@ protected function performUpdate(Builder $query)
// First we need to create a fresh query instance and touch the creation and
// update timestamp on the model which are maintained by us for developer
// convenience. Then we will just continue saving the model instances.
- if ($this->timestamps)
+ if ($this->timestamps && array_get($options, 'timestamps', true))
{
$this->updateTimestamps();
-
- $dirty = $this->getDirty();
}
// Once we have run the update operation, we will fire the "updated" event for
// this model instance. This will allow developers to hook into these after
// models are updated, giving them a chance to do any special processing.
- $this->setKeysForSaveQuery($query)->update($dirty);
+ $dirty = $this->getDirty();
+
+ if (count($dirty) > 0)
+ {
+ $this->setKeysForSaveQuery($query)->update($dirty);
- $this->fireModelEvent('updated', false);
+ $this->fireModelEvent('updated', false);
+ }
}
return true;
@@ -1345,16 +1578,17 @@ protected function performUpdate(Builder $query)
* Perform a model insert operation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
+ * @param array $options
* @return bool
*/
- protected function performInsert(Builder $query)
+ protected function performInsert(Builder $query, array $options = [])
{
if ($this->fireModelEvent('creating') === false) return false;
// First we'll need to create a fresh query instance and touch the creation and
// update timestamps on this model, which are maintained by us for developer
// convenience. After, we will just continue saving these model instances.
- if ($this->timestamps)
+ if ($this->timestamps && array_get($options, 'timestamps', true))
{
$this->updateTimestamps();
}
@@ -1411,6 +1645,11 @@ public function touchOwners()
foreach ($this->touches as $relation)
{
$this->$relation()->touch();
+
+ if ( ! is_null($this->$relation))
+ {
+ $this->$relation->touchOwners();
+ }
}
}
@@ -1470,10 +1709,8 @@ protected function getKeyForSaveQuery()
{
return $this->original[$this->getKeyName()];
}
- else
- {
- return $this->getAttribute($this->getKeyName());
- }
+
+ return $this->getAttribute($this->getKeyName());
}
/**
@@ -1483,6 +1720,8 @@ protected function getKeyForSaveQuery()
*/
public function touch()
{
+ if ( ! $this->timestamps) return false;
+
$this->updateTimestamps();
return $this->save();
@@ -1550,26 +1789,6 @@ public function getUpdatedAtColumn()
return static::UPDATED_AT;
}
- /**
- * Get the name of the "deleted at" column.
- *
- * @return string
- */
- public function getDeletedAtColumn()
- {
- return static::DELETED_AT;
- }
-
- /**
- * Get the fully qualified "deleted at" column.
- *
- * @return string
- */
- public function getQualifiedDeletedAtColumn()
- {
- return $this->getTable().'.'.$this->getDeletedAtColumn();
- }
-
/**
* Get a fresh timestamp for the model.
*
@@ -1593,68 +1812,86 @@ public function freshTimestampString()
/**
* Get a new query builder for the model's table.
*
- * @param bool $excludeDeleted
- * @return \Illuminate\Database\Eloquent\Builder|static
+ * @return \Illuminate\Database\Eloquent\Builder
*/
- public function newQuery($excludeDeleted = true)
+ public function newQuery()
{
- $builder = new Builder($this->newBaseQueryBuilder());
+ $builder = $this->newQueryWithoutScopes();
- // Once we have the query builders, we will set the model instances so the
- // builder can easily access any information it may need from the model
- // while it is constructing and executing various queries against it.
- $builder->setModel($this)->with($this->with);
+ return $this->applyGlobalScopes($builder);
+ }
- if ($excludeDeleted && $this->softDelete)
- {
- $builder->whereNull($this->getQualifiedDeletedAtColumn());
- }
+ /**
+ * Get a new query instance without a given scope.
+ *
+ * @param \Illuminate\Database\Eloquent\ScopeInterface $scope
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function newQueryWithoutScope($scope)
+ {
+ $this->getGlobalScope($scope)->remove($builder = $this->newQuery(), $this);
return $builder;
}
/**
- * Get a new query builder that includes soft deletes.
+ * Get a new query builder that doesn't have any global scopes.
*
* @return \Illuminate\Database\Eloquent\Builder|static
*/
- public function newQueryWithDeleted()
+ public function newQueryWithoutScopes()
{
- return $this->newQuery(false);
+ $builder = $this->newEloquentBuilder(
+ $this->newBaseQueryBuilder()
+ );
+
+ // Once we have the query builders, we will set the model instances so the
+ // builder can easily access any information it may need from the model
+ // while it is constructing and executing various queries against it.
+ return $builder->setModel($this)->with($this->with);
}
/**
- * Determine if the model instance has been soft-deleted.
+ * Apply all of the global scopes to an Eloquent builder.
*
- * @return bool
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return \Illuminate\Database\Eloquent\Builder
*/
- public function trashed()
+ public function applyGlobalScopes($builder)
{
- return $this->softDelete && ! is_null($this->{static::DELETED_AT});
+ foreach ($this->getGlobalScopes() as $scope)
+ {
+ $scope->apply($builder, $this);
+ }
+
+ return $builder;
}
/**
- * Get a new query builder that includes soft deletes.
+ * Remove all of the global scopes from an Eloquent builder.
*
- * @return \Illuminate\Database\Eloquent\Builder|static
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return \Illuminate\Database\Eloquent\Builder
*/
- public static function withTrashed()
+ public function removeGlobalScopes($builder)
{
- return with(new static)->newQueryWithDeleted();
+ foreach ($this->getGlobalScopes() as $scope)
+ {
+ $scope->remove($builder, $this);
+ }
+
+ return $builder;
}
/**
- * Get a new query builder that only includes soft deletes.
+ * Create a new Eloquent query builder for the model.
*
+ * @param \Illuminate\Database\Query\Builder $query
* @return \Illuminate\Database\Eloquent\Builder|static
*/
- public static function onlyTrashed()
+ public function newEloquentBuilder($query)
{
- $instance = new static;
-
- $column = $instance->getQualifiedDeletedAtColumn();
-
- return $instance->newQueryWithDeleted()->whereNotNull($column);
+ return new Builder($query);
}
/**
@@ -1689,7 +1926,7 @@ public function newCollection(array $models = array())
* @param array $attributes
* @param string $table
* @param bool $exists
- * @return \Illuminate\Database\Eloquent\Relation\Pivot
+ * @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
@@ -1729,6 +1966,16 @@ public function getKey()
return $this->getAttribute($this->getKeyName());
}
+ /**
+ * Get the queueable identity for the entity.
+ *
+ * @return mixed
+ */
+ public function getQueueableId()
+ {
+ return $this->getKey();
+ }
+
/**
* Get the primary key for the model.
*
@@ -1739,6 +1986,17 @@ public function getKeyName()
return $this->primaryKey;
}
+ /**
+ * Set the primary key for the model.
+ *
+ * @param string $key
+ * @return void
+ */
+ public function setKeyName($key)
+ {
+ $this->primaryKey = $key;
+ }
+
/**
* Get the table qualified key name.
*
@@ -1750,34 +2008,33 @@ public function getQualifiedKeyName()
}
/**
- * Determine if the model uses timestamps.
+ * Get the value of the model's route key.
*
- * @return bool
+ * @return mixed
*/
- public function usesTimestamps()
+ public function getRouteKey()
{
- return $this->timestamps;
+ return $this->getAttribute($this->getRouteKeyName());
}
/**
- * Determine if the model instance uses soft deletes.
+ * Get the route key for the model.
*
- * @return bool
+ * @return string
*/
- public function isSoftDeleting()
+ public function getRouteKeyName()
{
- return $this->softDelete;
+ return $this->getKeyName();
}
/**
- * Set the soft deleting property on the model.
+ * Determine if the model uses timestamps.
*
- * @param bool $enabled
- * @return void
+ * @return bool
*/
- public function setSoftDeleting($enabled)
+ public function usesTimestamps()
{
- $this->softDelete = $enabled;
+ return $this->timestamps;
}
/**
@@ -1797,6 +2054,16 @@ protected function getMorphs($name, $type, $id)
return array($type, $id);
}
+ /**
+ * Get the class name for polymorphic relations.
+ *
+ * @return string
+ */
+ public function getMorphClass()
+ {
+ return $this->morphClass ?: get_class($this);
+ }
+
/**
* Get the number of models to return per page.
*
@@ -1808,7 +2075,7 @@ public function getPerPage()
}
/**
- * Set the number of models ot return per page.
+ * Set the number of models to return per page.
*
* @param int $perPage
* @return void
@@ -1849,6 +2116,29 @@ public function setHidden(array $hidden)
$this->hidden = $hidden;
}
+ /**
+ * Add hidden attributes for the model.
+ *
+ * @param array|string|null $attributes
+ * @return void
+ */
+ public function addHidden($attributes = null)
+ {
+ $attributes = is_array($attributes) ? $attributes : func_get_args();
+
+ $this->hidden = array_merge($this->hidden, $attributes);
+ }
+
+ /**
+ * Get the visible attributes for the model.
+ *
+ * @return array
+ */
+ public function getVisible()
+ {
+ return $this->visible;
+ }
+
/**
* Set the visible attributes for the model.
*
@@ -1860,6 +2150,19 @@ public function setVisible(array $visible)
$this->visible = $visible;
}
+ /**
+ * Add visible attributes for the model.
+ *
+ * @param array|string|null $attributes
+ * @return void
+ */
+ public function addVisible($attributes = null)
+ {
+ $attributes = is_array($attributes) ? $attributes : func_get_args();
+
+ $this->visible = array_merge($this->visible, $attributes);
+ }
+
/**
* Set the accessors to append to model arrays.
*
@@ -1885,7 +2188,7 @@ public function getFillable()
* Set the fillable attributes for the model.
*
* @param array $fillable
- * @return \Illuminate\Database\Eloquent\Model
+ * @return $this
*/
public function fillable(array $fillable)
{
@@ -1894,11 +2197,21 @@ public function fillable(array $fillable)
return $this;
}
+ /**
+ * Get the guarded attributes for the model.
+ *
+ * @return array
+ */
+ public function getGuarded()
+ {
+ return $this->guarded;
+ }
+
/**
* Set the guarded attributes for the model.
*
* @param array $guarded
- * @return \Illuminate\Database\Eloquent\Model
+ * @return $this
*/
public function guard(array $guarded)
{
@@ -2045,6 +2358,16 @@ public function toJson($options = 0)
return json_encode($this->toArray(), $options);
}
+ /**
+ * Convert the object into something JSON serializable.
+ *
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
/**
* Convert the model instance to an array.
*
@@ -2066,14 +2389,39 @@ public function attributesToArray()
{
$attributes = $this->getArrayableAttributes();
+ // If an attribute is a date, we will cast it to a string after converting it
+ // to a DateTime / Carbon instance. This is so we will get some consistent
+ // formatting while accessing attributes vs. arraying / JSONing a model.
+ foreach ($this->getDates() as $key)
+ {
+ if ( ! isset($attributes[$key])) continue;
+
+ $attributes[$key] = (string) $this->asDateTime($attributes[$key]);
+ }
+
+ $mutatedAttributes = $this->getMutatedAttributes();
+
// We want to spin through all the mutated attributes for this model and call
// the mutator for the attribute. We cache off every mutated attributes so
// we don't have to constantly check on attributes that actually change.
- foreach ($this->getMutatedAttributes() as $key)
+ foreach ($mutatedAttributes as $key)
{
if ( ! array_key_exists($key, $attributes)) continue;
- $attributes[$key] = $this->mutateAttribute(
+ $attributes[$key] = $this->mutateAttributeForArray(
+ $key, $attributes[$key]
+ );
+ }
+
+ // Next we will handle any casts that have been setup for this model and cast
+ // the values to their appropriate type. If the attribute has a mutator we
+ // will not perform the cast on those attributes to avoid any confusion.
+ foreach ($this->casts as $key => $value)
+ {
+ if ( ! array_key_exists($key, $attributes) ||
+ in_array($key, $mutatedAttributes)) continue;
+
+ $attributes[$key] = $this->castAttribute(
$key, $attributes[$key]
);
}
@@ -2081,9 +2429,9 @@ public function attributesToArray()
// Here we will grab all of the appended, calculated attributes to this model
// as these attributes are not really in the attributes array, but are run
// when we need to array or JSON the model for convenience to the coder.
- foreach ($this->appends as $key)
+ foreach ($this->getArrayableAppends() as $key)
{
- $attributes[$key] = $this->mutateAttribute($key, null);
+ $attributes[$key] = $this->mutateAttributeForArray($key, null);
}
return $attributes;
@@ -2099,6 +2447,20 @@ protected function getArrayableAttributes()
return $this->getArrayableItems($this->attributes);
}
+ /**
+ * Get all of the appendable values that are arrayable.
+ *
+ * @return array
+ */
+ protected function getArrayableAppends()
+ {
+ if ( ! count($this->appends)) return [];
+
+ return $this->getArrayableItems(
+ array_combine($this->appends, $this->appends)
+ );
+ }
+
/**
* Get the model's relationships in array form.
*
@@ -2115,7 +2477,7 @@ public function relationsToArray()
// If the values implements the Arrayable interface we can just call this
// toArray method on the instances which will convert both models and
// collections to their proper array form and we'll set the values.
- if ($value instanceof ArrayableInterface)
+ if ($value instanceof Arrayable)
{
$relation = $value->toArray();
}
@@ -2143,6 +2505,8 @@ public function relationsToArray()
{
$attributes[$key] = $relation;
}
+
+ unset($relation);
}
return $attributes;
@@ -2203,11 +2567,9 @@ public function getAttribute($key)
// If the "attribute" exists as a method on the model, we will just assume
// it is a relationship and will load and return results from the query
// and hydrate the relationship's value on the "relationships" array.
- $camelKey = camel_case($key);
-
- if (method_exists($this, $camelKey))
+ if (method_exists($this, $key))
{
- return $this->getRelationshipFromMethod($key, $camelKey);
+ return $this->getRelationshipFromMethod($key);
}
}
@@ -2229,6 +2591,14 @@ protected function getAttributeValue($key)
return $this->mutateAttribute($key, $value);
}
+ // If the attribute exists within the cast array, we will convert it to
+ // an appropriate native PHP type dependant upon the associated value
+ // given with the key in the pair. Dayle made this comment line up.
+ if ($this->hasCast($key))
+ {
+ $value = $this->castAttribute($key, $value);
+ }
+
// If the attribute is listed as a date, we will convert it to a DateTime
// instance on retrieval, which makes it quite convenient to work with
// date fields without having to create a mutator for each property.
@@ -2257,13 +2627,14 @@ protected function getAttributeFromArray($key)
/**
* Get a relationship value from a method.
*
- * @param string $key
- * @param string $camelKey
+ * @param string $method
* @return mixed
+ *
+ * @throws \LogicException
*/
- protected function getRelationshipFromMethod($key, $camelKey)
+ protected function getRelationshipFromMethod($method)
{
- $relations = $this->$camelKey();
+ $relations = $this->$method();
if ( ! $relations instanceof Relation)
{
@@ -2271,7 +2642,7 @@ protected function getRelationshipFromMethod($key, $camelKey)
. 'Illuminate\Database\Eloquent\Relations\Relation');
}
- return $this->relations[$key] = $relations->getResults();
+ return $this->relations[$method] = $relations->getResults();
}
/**
@@ -2297,6 +2668,93 @@ protected function mutateAttribute($key, $value)
return $this->{'get'.studly_case($key).'Attribute'}($value);
}
+ /**
+ * Get the value of an attribute using its mutator for array conversion.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function mutateAttributeForArray($key, $value)
+ {
+ $value = $this->mutateAttribute($key, $value);
+
+ return $value instanceof Arrayable ? $value->toArray() : $value;
+ }
+
+ /**
+ * Determine whether an attribute should be casted to a native type.
+ *
+ * @param string $key
+ * @return bool
+ */
+ protected function hasCast($key)
+ {
+ return array_key_exists($key, $this->casts);
+ }
+
+ /**
+ * Determine whether a value is JSON castable for inbound manipulation.
+ *
+ * @param string $key
+ * @return bool
+ */
+ protected function isJsonCastable($key)
+ {
+ if ($this->hasCast($key))
+ {
+ $type = $this->getCastType($key);
+
+ return $type === 'array' || $type === 'json' || $type === 'object';
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the type of cast for a model attribute.
+ *
+ * @param string $key
+ * @return string
+ */
+ protected function getCastType($key)
+ {
+ return trim(strtolower($this->casts[$key]));
+ }
+
+ /**
+ * Cast an attribute to a native PHP type.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function castAttribute($key, $value)
+ {
+ switch ($this->getCastType($key))
+ {
+ case 'int':
+ case 'integer':
+ return (int) $value;
+ case 'real':
+ case 'float':
+ case 'double':
+ return (float) $value;
+ case 'string':
+ return (string) $value;
+ case 'bool':
+ case 'boolean':
+ return (bool) $value;
+ case 'object':
+ return json_decode($value);
+ case 'array':
+ case 'json':
+ return json_decode($value, true);
+ default:
+ return $value;
+ }
+ }
+
/**
* Set a given attribute on the model.
*
@@ -2319,12 +2777,14 @@ public function setAttribute($key, $value)
// If an attribute is listed as a "date", we'll convert it from a DateTime
// instance into a form proper for storage on the database tables using
// the connection grammar's date format. We will auto set the values.
- elseif (in_array($key, $this->getDates()))
+ elseif (in_array($key, $this->getDates()) && $value)
{
- if ($value)
- {
- $value = $this->fromDateTime($value);
- }
+ $value = $this->fromDateTime($value);
+ }
+
+ if ($this->isJsonCastable($key))
+ {
+ $value = json_encode($value);
}
$this->attributes[$key] = $value;
@@ -2348,7 +2808,7 @@ public function hasSetMutator($key)
*/
public function getDates()
{
- $defaults = array(static::CREATED_AT, static::UPDATED_AT, static::DELETED_AT);
+ $defaults = array(static::CREATED_AT, static::UPDATED_AT);
return array_merge($this->dates, $defaults);
}
@@ -2390,7 +2850,7 @@ public function fromDateTime($value)
// If this value is some other type of string, we'll create the DateTime with
// the format used by the database connection. Once we get the instance we
// can return back the finally formatted DateTime instances to the devs.
- elseif ( ! $value instanceof DateTime)
+ else
{
$value = Carbon::createFromFormat($format, $value);
}
@@ -2448,11 +2908,18 @@ protected function getDateFormat()
/**
* Clone the model into a new, non-existing instance.
*
+ * @param array $except
* @return \Illuminate\Database\Eloquent\Model
*/
- public function replicate()
+ public function replicate(array $except = null)
{
- $attributes = array_except($this->attributes, array($this->getKeyName()));
+ $except = $except ?: [
+ $this->getKeyName(),
+ $this->getCreatedAtColumn(),
+ $this->getUpdatedAtColumn(),
+ ];
+
+ $attributes = array_except($this->attributes, $except);
with($instance = new static)->setRawAttributes($attributes);
@@ -2498,7 +2965,7 @@ public function getOriginal($key = null, $default = null)
/**
* Sync the original attributes with the current.
*
- * @return \Illuminate\Database\Eloquent\Model
+ * @return $this
*/
public function syncOriginal()
{
@@ -2508,14 +2975,38 @@ public function syncOriginal()
}
/**
- * Determine if a given attribute is dirty.
+ * Sync a single original attribute with its current value.
*
* @param string $attribute
+ * @return $this
+ */
+ public function syncOriginalAttribute($attribute)
+ {
+ $this->original[$attribute] = $this->attributes[$attribute];
+
+ return $this;
+ }
+
+ /**
+ * Determine if the model or given attribute(s) have been modified.
+ *
+ * @param array|string|null $attributes
* @return bool
*/
- public function isDirty($attribute)
+ public function isDirty($attributes = null)
{
- return array_key_exists($attribute, $this->getDirty());
+ $dirty = $this->getDirty();
+
+ if (is_null($attributes)) return count($dirty) > 0;
+
+ if ( ! is_array($attributes)) $attributes = func_get_args();
+
+ foreach ($attributes as $attribute)
+ {
+ if (array_key_exists($attribute, $dirty)) return true;
+ }
+
+ return false;
}
/**
@@ -2529,7 +3020,12 @@ public function getDirty()
foreach ($this->attributes as $key => $value)
{
- if ( ! array_key_exists($key, $this->original) || $value !== $this->original[$key])
+ if ( ! array_key_exists($key, $this->original))
+ {
+ $dirty[$key] = $value;
+ }
+ elseif ($value !== $this->original[$key] &&
+ ! $this->originalIsNumericallyEquivalent($key))
{
$dirty[$key] = $value;
}
@@ -2538,6 +3034,21 @@ public function getDirty()
return $dirty;
}
+ /**
+ * Determine if the new and old values for a given key are numerically equivalent.
+ *
+ * @param string $key
+ * @return bool
+ */
+ protected function originalIsNumericallyEquivalent($key)
+ {
+ $current = $this->attributes[$key];
+
+ $original = $this->original[$key];
+
+ return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0;
+ }
+
/**
* Get all the loaded relations for the instance.
*
@@ -2564,7 +3075,7 @@ public function getRelation($relation)
*
* @param string $relation
* @param mixed $value
- * @return \Illuminate\Database\Eloquent\Model
+ * @return $this
*/
public function setRelation($relation, $value)
{
@@ -2577,7 +3088,7 @@ public function setRelation($relation, $value)
* Set the entire relations array on the model.
*
* @param array $relations
- * @return \Illuminate\Database\Eloquent\Model
+ * @return $this
*/
public function setRelations(array $relations)
{
@@ -2610,7 +3121,7 @@ public function getConnectionName()
* Set the connection associated with the model.
*
* @param string $name
- * @return \Illuminate\Database\Eloquent\Model
+ * @return $this
*/
public function setConnection($name)
{
@@ -2651,10 +3162,20 @@ public static function setConnectionResolver(Resolver $resolver)
static::$resolver = $resolver;
}
+ /**
+ * Unset the connection resolver for models.
+ *
+ * @return void
+ */
+ public static function unsetConnectionResolver()
+ {
+ static::$resolver = null;
+ }
+
/**
* Get the event dispatcher instance.
*
- * @return \Illuminate\Events\Dispatcher
+ * @return \Illuminate\Contracts\Events\Dispatcher
*/
public static function getEventDispatcher()
{
@@ -2664,7 +3185,7 @@ public static function getEventDispatcher()
/**
* Set the event dispatcher instance.
*
- * @param \Illuminate\Events\Dispatcher $dispatcher
+ * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @return void
*/
public static function setEventDispatcher(Dispatcher $dispatcher)
@@ -2693,7 +3214,7 @@ public function getMutatedAttributes()
if (isset(static::$mutatorCache[$class]))
{
- return static::$mutatorCache[get_class($this)];
+ return static::$mutatorCache[$class];
}
return array();
@@ -2771,12 +3292,12 @@ public function offsetUnset($offset)
* Determine if an attribute exists on the model.
*
* @param string $key
- * @return void
+ * @return bool
*/
public function __isset($key)
{
return ((isset($this->attributes[$key]) || isset($this->relations[$key])) ||
- ($this->hasGetMutator($key) && ! is_null($this->getAttributeValue($key))));
+ ($this->hasGetMutator($key) && ! is_null($this->getAttributeValue($key))));
}
/**
@@ -2787,9 +3308,7 @@ public function __isset($key)
*/
public function __unset($key)
{
- unset($this->attributes[$key]);
-
- unset($this->relations[$key]);
+ unset($this->attributes[$key], $this->relations[$key]);
}
/**
@@ -2835,4 +3354,14 @@ public function __toString()
return $this->toJson();
}
+ /**
+ * When a model is being unserialized, check if it needs to be booted.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ $this->bootIfNotBooted();
+ }
+
}
diff --git a/Eloquent/ModelNotFoundException.php b/Eloquent/ModelNotFoundException.php
index 25750dc62..84be36ee0 100755
--- a/Eloquent/ModelNotFoundException.php
+++ b/Eloquent/ModelNotFoundException.php
@@ -1,3 +1,39 @@
model = $model;
+
+ $this->message = "No query results for model [{$model}].";
+
+ return $this;
+ }
+
+ /**
+ * Get the affected Eloquent model.
+ *
+ * @return string
+ */
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+}
diff --git a/Eloquent/QueueEntityResolver.php b/Eloquent/QueueEntityResolver.php
new file mode 100644
index 000000000..2dc2be52c
--- /dev/null
+++ b/Eloquent/QueueEntityResolver.php
@@ -0,0 +1,27 @@
+find($id);
+
+ if ($instance)
+ {
+ return $instance;
+ }
+
+ throw new EntityNotFoundException($type, $id);
+ }
+
+}
diff --git a/Eloquent/Relations/BelongsTo.php b/Eloquent/Relations/BelongsTo.php
index eef442070..8cd133761 100755
--- a/Eloquent/Relations/BelongsTo.php
+++ b/Eloquent/Relations/BelongsTo.php
@@ -1,8 +1,8 @@
select(new Expression('count(*)'));
+
+ $otherKey = $this->wrap($query->getModel()->getTable().'.'.$this->otherKey);
+
+ return $query->where($this->getQualifiedForeignKey(), '=', new Expression($otherKey));
}
/**
@@ -141,7 +144,7 @@ protected function getEagerModelKeys(array $models)
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
public function initRelation(array $models, $relation)
{
@@ -204,6 +207,18 @@ public function associate(Model $model)
return $this->parent->setRelation($this->relation, $model);
}
+ /**
+ * Dissociate previously associated model from the given parent.
+ *
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function dissociate()
+ {
+ $this->parent->setAttribute($this->foreignKey, null);
+
+ return $this->parent->setRelation($this->relation, null);
+ }
+
/**
* Update the parent model on the relationship.
*
@@ -227,4 +242,34 @@ public function getForeignKey()
return $this->foreignKey;
}
-}
\ No newline at end of file
+ /**
+ * Get the fully qualified foreign key of the relationship.
+ *
+ * @return string
+ */
+ public function getQualifiedForeignKey()
+ {
+ return $this->parent->getTable().'.'.$this->foreignKey;
+ }
+
+ /**
+ * Get the associated key of the relationship.
+ *
+ * @return string
+ */
+ public function getOtherKey()
+ {
+ return $this->otherKey;
+ }
+
+ /**
+ * Get the fully qualified associated key of the relationship.
+ *
+ * @return string
+ */
+ public function getQualifiedOtherKeyName()
+ {
+ return $this->related->getTable().'.'.$this->otherKey;
+ }
+
+}
diff --git a/Eloquent/Relations/BelongsToMany.php b/Eloquent/Relations/BelongsToMany.php
index a4534379c..0c147c0bf 100755
--- a/Eloquent/Relations/BelongsToMany.php
+++ b/Eloquent/Relations/BelongsToMany.php
@@ -2,6 +2,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -43,7 +44,14 @@ class BelongsToMany extends Relation {
protected $pivotColumns = array();
/**
- * Create a new has many relationship instance.
+ * Any pivot table restrictions.
+ *
+ * @var array
+ */
+ protected $pivotWheres = [];
+
+ /**
+ * Create a new belongs to many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
@@ -84,6 +92,8 @@ public function getResults()
*/
public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
{
+ $this->pivotWheres[] = func_get_args();
+
return $this->where($this->table.'.'.$column, $operator, $value, $boolean);
}
@@ -93,7 +103,6 @@ public function wherePivot($column, $operator = null, $value = null, $boolean =
* @param string $column
* @param string $operator
* @param mixed $value
- * @param string $boolean
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function orWherePivot($column, $operator = null, $value = null)
@@ -140,6 +149,8 @@ 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.
+ $columns = $this->query->getQuery()->columns ? array() : $columns;
+
$select = $this->getSelectColumns($columns);
$models = $this->query->addSelect($select)->getModels();
@@ -157,27 +168,6 @@ public function get($columns = array('*'))
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.
*
@@ -239,18 +229,55 @@ public function addConstraints()
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Builder $parent
* @return \Illuminate\Database\Eloquent\Builder
*/
- public function getRelationCountQuery(Builder $query)
+ public function getRelationCountQuery(Builder $query, Builder $parent)
{
+ if ($parent->getQuery()->from == $query->getQuery()->from)
+ {
+ return $this->getRelationCountQueryForSelfJoin($query, $parent);
+ }
+
$this->setJoin($query);
- return parent::getRelationCountQuery($query);
+ return parent::getRelationCountQuery($query, $parent);
+ }
+
+ /**
+ * Add the constraints for a relationship count query on the same table.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Builder $parent
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function getRelationCountQueryForSelfJoin(Builder $query, Builder $parent)
+ {
+ $query->select(new Expression('count(*)'));
+
+ $tablePrefix = $this->query->getQuery()->getConnection()->getTablePrefix();
+
+ $query->from($this->table.' as '.$tablePrefix.$hash = $this->getRelationCountHash());
+
+ $key = $this->wrap($this->getQualifiedParentKeyName());
+
+ return $query->where($hash.'.'.$this->foreignKey, '=', new Expression($key));
+ }
+
+ /**
+ * Get a relationship join table hash.
+ *
+ * @return string
+ */
+ public function getRelationCountHash()
+ {
+ return 'self_'.md5(microtime(true));
}
/**
* Set the select clause for the relation query.
*
+ * @param array $columns
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function getSelectColumns(array $columns = array('*'))
@@ -285,11 +312,22 @@ protected function getAliasedPivotColumns()
return array_unique($columns);
}
+ /**
+ * Determine whether the given column is defined as a pivot column.
+ *
+ * @param string $column
+ * @return bool
+ */
+ protected function hasPivotColumn($column)
+ {
+ return in_array($column, $this->pivotColumns);
+ }
+
/**
* Set the join clause for the relation query.
*
* @param \Illuminate\Database\Eloquent\Builder|null
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ * @return $this
*/
protected function setJoin($query = null)
{
@@ -310,7 +348,7 @@ protected function setJoin($query = null)
/**
* Set the where clause for the relation query.
*
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ * @return $this
*/
protected function setWhere()
{
@@ -337,7 +375,7 @@ public function addEagerConstraints(array $models)
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
public function initRelation(array $models, $relation)
{
@@ -474,6 +512,76 @@ public function saveMany(array $models, array $joinings = array())
return $models;
}
+ /**
+ * Find a related model by its primary key or return new instance of the related model.
+ *
+ * @param mixed $id
+ * @param array $columns
+ * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
+ */
+ public function findOrNew($id, $columns = ['*'])
+ {
+ if (is_null($instance = $this->find($id, $columns)))
+ {
+ $instance = $this->getRelated()->newInstance();
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get the first related model record matching the attributes or instantiate it.
+ *
+ * @param array $attributes
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function firstOrNew(array $attributes)
+ {
+ if (is_null($instance = $this->where($attributes)->first()))
+ {
+ $instance = $this->related->newInstance();
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get the first related record matching the attributes or create it.
+ *
+ * @param array $attributes
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function firstOrCreate(array $attributes, array $joining = [], $touch = true)
+ {
+ if (is_null($instance = $this->where($attributes)->first()))
+ {
+ $instance = $this->create($attributes, $joining, $touch);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Create or update a related record matching the attributes, and fill it with values.
+ *
+ * @param array $attributes
+ * @param array $values
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true)
+ {
+ if (is_null($instance = $this->where($attributes)->first()))
+ {
+ return $this->create($values, $joining, $touch);
+ }
+
+ $instance->fill($values);
+
+ $instance->save(['touch' => false]);
+
+ return $instance;
+ }
+
/**
* Create a new instance of the related model.
*
@@ -518,14 +626,20 @@ public function createMany(array $records, array $joinings = array())
}
/**
- * Sync the intermediate tables with a list of IDs.
+ * Sync the intermediate tables with a list of IDs or collection of models.
*
* @param array $ids
* @param bool $detaching
- * @return void
+ * @return array
*/
- public function sync(array $ids, $detaching = true)
+ public function sync($ids, $detaching = true)
{
+ $changes = array(
+ 'attached' => array(), 'detached' => array(), 'updated' => array()
+ );
+
+ if ($ids instanceof Collection) $ids = $ids->modelKeys();
+
// 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.
@@ -541,14 +655,23 @@ public function sync(array $ids, $detaching = true)
if ($detaching && count($detach) > 0)
{
$this->detach($detach);
+
+ $changes['detached'] = (array) array_map(function($v) { return (int) $v; }, $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);
+ $changes = array_merge(
+ $changes, $this->attachNew($records, $current, false)
+ );
- $this->touchIfTouching();
+ if (count($changes['attached']) || count($changes['updated']))
+ {
+ $this->touchIfTouching();
+ }
+
+ return $changes;
}
/**
@@ -580,10 +703,12 @@ protected function formatSyncList(array $records)
* @param array $records
* @param array $current
* @param bool $touch
- * @return void
+ * @return array
*/
protected function attachNew(array $records, array $current, $touch = true)
{
+ $changes = array('attached' => array(), 'updated' => array());
+
foreach ($records as $id => $attributes)
{
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
@@ -592,12 +717,21 @@ protected function attachNew(array $records, array $current, $touch = true)
if ( ! in_array($id, $current))
{
$this->attach($id, $attributes, $touch);
+
+ $changes['attached'][] = (int) $id;
}
- elseif (count($attributes) > 0)
+
+ // Now we'll try to update an existing pivot record with the attributes that were
+ // given to the method. If the model is actually updated we will add it to the
+ // list of updated pivot records so we return them back out to the consumer.
+ elseif (count($attributes) > 0 &&
+ $this->updateExistingPivot($id, $attributes, $touch))
{
- $this->updateExistingPivot($id, $attributes, $touch);
+ $changes['updated'][] = (int) $id;
}
}
+
+ return $changes;
}
/**
@@ -608,16 +742,18 @@ protected function attachNew(array $records, array $current, $touch = true)
* @param bool $touch
* @return void
*/
- public function updateExistingPivot($id, array $attributes, $touch)
+ public function updateExistingPivot($id, array $attributes, $touch = true)
{
if (in_array($this->updatedAt(), $this->pivotColumns))
{
$attributes = $this->setTimestampsOnAttach($attributes, true);
}
- $this->newPivotStatementForId($id)->update($attributes);
+ $updated = $this->newPivotStatementForId($id)->update($attributes);
if ($touch) $this->touchIfTouching();
+
+ return $updated;
}
/**
@@ -643,13 +779,15 @@ public function attach($id, array $attributes = array(), $touch = true)
* Create an array of records to insert into the pivot table.
*
* @param array $ids
- * @return void
+ * @param array $attributes
+ * @return array
*/
protected function createAttachRecords($ids, array $attributes)
{
$records = array();
- $timed = in_array($this->createdAt(), $this->pivotColumns);
+ $timed = ($this->hasPivotColumn($this->createdAt()) ||
+ $this->hasPivotColumn($this->updatedAt()));
// 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
@@ -697,10 +835,8 @@ protected function getAttachId($key, $value, array $attributes)
{
return array($key, array_merge($value, $attributes));
}
- else
- {
- return array($value, $attributes);
- }
+
+ return array($value, $attributes);
}
/**
@@ -728,7 +864,7 @@ protected function createAttachRecord($id, $timed)
}
/**
- * Set the creation and update timstamps on an attach record.
+ * Set the creation and update timestamps on an attach record.
*
* @param array $record
* @param bool $exists
@@ -738,9 +874,15 @@ protected function setTimestampsOnAttach(array $record, $exists = false)
{
$fresh = $this->parent->freshTimestamp();
- if ( ! $exists) $record[$this->createdAt()] = $fresh;
+ if ( ! $exists && $this->hasPivotColumn($this->createdAt()))
+ {
+ $record[$this->createdAt()] = $fresh;
+ }
- $record[$this->updatedAt()] = $fresh;
+ if ($this->hasPivotColumn($this->updatedAt()))
+ {
+ $record[$this->updatedAt()] = $fresh;
+ }
return $record;
}
@@ -765,7 +907,7 @@ public function detach($ids = array(), $touch = true)
if (count($ids) > 0)
{
- $query->whereIn($this->otherKey, $ids);
+ $query->whereIn($this->otherKey, (array) $ids);
}
if ($touch) $this->touchIfTouching();
@@ -785,9 +927,9 @@ public function detach($ids = array(), $touch = true)
*/
public function touchIfTouching()
{
- if ($this->touchingParent()) $this->getParent()->touch();
+ if ($this->touchingParent()) $this->getParent()->touch();
- if ($this->getParent()->touches($this->relationName)) $this->touch();
+ if ($this->getParent()->touches($this->relationName)) $this->touch();
}
/**
@@ -807,7 +949,7 @@ protected function touchingParent()
*/
protected function guessInverseRelation()
{
- return strtolower(str_plural(class_basename($this->getParent())));
+ return camel_case(str_plural(class_basename($this->getParent())));
}
/**
@@ -819,6 +961,11 @@ protected function newPivotQuery()
{
$query = $this->newPivotStatement();
+ foreach ($this->pivotWheres as $whereArgs)
+ {
+ call_user_func_array([$query, 'where'], $whereArgs);
+ }
+
return $query->where($this->foreignKey, $this->parent->getKey());
}
@@ -840,11 +987,7 @@ public function newPivotStatement()
*/
public function newPivotStatementForId($id)
{
- $pivot = $this->newPivotStatement();
-
- $key = $this->parent->getKey();
-
- return $pivot->where($this->foreignKey, $key)->where($this->otherKey, $id);
+ return $this->newPivotQuery()->where($this->otherKey, $id);
}
/**
@@ -852,7 +995,7 @@ public function newPivotStatementForId($id)
*
* @param array $attributes
* @param bool $exists
- * @return \Illuminate\Database\Eloquent\Relation\Pivot
+ * @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
public function newPivot(array $attributes = array(), $exists = false)
{
@@ -875,8 +1018,8 @@ public function newExistingPivot(array $attributes = array())
/**
* Set the columns on the pivot table to retrieve.
*
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ * @param mixed $columns
+ * @return $this
*/
public function withPivot($columns)
{
@@ -890,11 +1033,13 @@ public function withPivot($columns)
/**
* Specify that the pivot table has creation and update timestamps.
*
+ * @param mixed $createdAt
+ * @param mixed $updatedAt
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
- public function withTimestamps()
+ public function withTimestamps($createdAt = null, $updatedAt = null)
{
- return $this->withPivot($this->createdAt(), $this->updatedAt());
+ return $this->withPivot($createdAt ?: $this->createdAt(), $updatedAt ?: $this->updatedAt());
}
/**
@@ -908,7 +1053,7 @@ public function getRelatedFreshUpdate()
}
/**
- * Get the key for comparing against the pareny key in "has" query.
+ * Get the key for comparing against the parent key in "has" query.
*
* @return string
*/
@@ -938,23 +1083,23 @@ public function getOtherKey()
}
/**
- * Get the fully qualified parent key naem.
+ * Get the intermediate table for the relationship.
*
* @return string
*/
- protected function getQualifiedParentKeyName()
+ public function getTable()
{
- return $this->parent->getQualifiedKeyName();
+ return $this->table;
}
/**
- * Get the intermediate table for the relationship.
+ * Get the relationship name for the relationship.
*
* @return string
*/
- public function getTable()
+ public function getRelationName()
{
- return $this->table;
+ return $this->relationName;
}
}
diff --git a/Eloquent/Relations/HasMany.php b/Eloquent/Relations/HasMany.php
index 171858258..159a65820 100755
--- a/Eloquent/Relations/HasMany.php
+++ b/Eloquent/Relations/HasMany.php
@@ -19,7 +19,7 @@ public function getResults()
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
public function initRelation(array $models, $relation)
{
@@ -44,4 +44,4 @@ public function match(array $models, Collection $results, $relation)
return $this->matchMany($models, $results, $relation);
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/HasManyThrough.php b/Eloquent/Relations/HasManyThrough.php
index 3f6bfed55..a53a24674 100644
--- a/Eloquent/Relations/HasManyThrough.php
+++ b/Eloquent/Relations/HasManyThrough.php
@@ -2,22 +2,40 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Collection;
class HasManyThrough extends Relation {
+ /**
+ * The distance parent model instance.
+ *
+ * @var \Illuminate\Database\Eloquent\Model
+ */
protected $farParent;
+ /**
+ * The near key on the relationship.
+ *
+ * @var string
+ */
protected $firstKey;
+ /**
+ * The far key on the relationship.
+ *
+ * @var string
+ */
protected $secondKey;
/**
- * Create a new has many relationship instance.
+ * Create a new has many through relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Model $farParent
* @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $foreignKey
+ * @param string $firstKey
+ * @param string $secondKey
* @return void
*/
public function __construct(Builder $query, Model $farParent, Model $parent, $firstKey, $secondKey)
@@ -50,19 +68,26 @@ public function addConstraints()
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Builder $parent
* @return \Illuminate\Database\Eloquent\Builder
*/
- public function getRelationCountQuery(Builder $query)
+ public function getRelationCountQuery(Builder $query, Builder $parent)
{
+ $parentTable = $this->parent->getTable();
+
$this->setJoin($query);
- return parent::getRelationCountQuery($query);
+ $query->select(new Expression('count(*)'));
+
+ $key = $this->wrap($parentTable.'.'.$this->firstKey);
+
+ return $query->where($this->getHasCompareKey(), '=', new Expression($key));
}
/**
* Set the join clause on the query.
*
- * @param \Illuminate\Databaes\Eloquent\Builder|null $query
+ * @param \Illuminate\Database\Eloquent\Builder|null $query
* @return void
*/
protected function setJoin(Builder $query = null)
@@ -92,7 +117,7 @@ public function addEagerConstraints(array $models)
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
public function initRelation(array $models, $relation)
{
@@ -144,7 +169,7 @@ protected function buildDictionary(Collection $results)
{
$dictionary = array();
- $foreign = $this->farParent->getForeignKey();
+ $foreign = $this->firstKey;
// 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
@@ -196,6 +221,7 @@ public function get($columns = array('*'))
/**
* Set the select clause for the relation query.
*
+ * @param array $columns
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function getSelectColumns(array $columns = array('*'))
@@ -208,18 +234,22 @@ protected function getSelectColumns(array $columns = array('*'))
return array_merge($columns, array($this->parent->getTable().'.'.$this->firstKey));
}
- /**
- * Get the key name of the parent model.
+ /*
+ * Get a paginator for the "select" statement.
*
- * @return string
+ * @param int $perPage
+ * @param array $columns
+ * @return \Illuminate\Pagination\Paginator
*/
- protected function getQualifiedParentKeyName()
+ public function paginate($perPage = null, $columns = array('*'))
{
- return $this->parent->getQualifiedKeyName();
+ $this->query->addSelect($this->getSelectColumns($columns));
+
+ return $this->query->paginate($perPage, $columns);
}
/**
- * Get the key for comparing against the pareny key in "has" query.
+ * Get the key for comparing against the parent key in "has" query.
*
* @return string
*/
@@ -228,4 +258,4 @@ public function getHasCompareKey()
return $this->farParent->getQualifiedKeyName();
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/HasOne.php b/Eloquent/Relations/HasOne.php
index 69437c2ad..fd0f9a022 100755
--- a/Eloquent/Relations/HasOne.php
+++ b/Eloquent/Relations/HasOne.php
@@ -19,7 +19,7 @@ public function getResults()
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
public function initRelation(array $models, $relation)
{
@@ -44,4 +44,4 @@ public function match(array $models, Collection $results, $relation)
return $this->matchOne($models, $results, $relation);
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/HasOneOrMany.php b/Eloquent/Relations/HasOneOrMany.php
index 9d2664540..db6d1fdf6 100755
--- a/Eloquent/Relations/HasOneOrMany.php
+++ b/Eloquent/Relations/HasOneOrMany.php
@@ -21,11 +21,12 @@ abstract class HasOneOrMany extends Relation {
protected $localKey;
/**
- * Create a new has many relationship instance.
+ * Create a new has one or many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
+ * @param string $localKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $foreignKey, $localKey)
@@ -57,7 +58,7 @@ public function addConstraints()
*/
public function addEagerConstraints(array $models)
{
- $this->query->whereIn($this->foreignKey, $this->getKeys($models));
+ $this->query->whereIn($this->foreignKey, $this->getKeys($models, $this->localKey));
}
/**
@@ -181,6 +182,77 @@ public function saveMany(array $models)
return $models;
}
+ /**
+ * Find a model by its primary key or return new instance of the related model.
+ *
+ * @param mixed $id
+ * @param array $columns
+ * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
+ */
+ public function findOrNew($id, $columns = ['*'])
+ {
+ if (is_null($instance = $this->find($id, $columns)))
+ {
+ $instance = $this->related->newInstance();
+
+ $instance->setAttribute($this->getPlainForeignKey(), $this->getParentKey());
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get the first related model record matching the attributes or instantiate it.
+ *
+ * @param array $attributes
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function firstOrNew(array $attributes)
+ {
+ if (is_null($instance = $this->where($attributes)->first()))
+ {
+ $instance = $this->related->newInstance();
+
+ $instance->setAttribute($this->getPlainForeignKey(), $this->getParentKey());
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get the first related record matching the attributes or create it.
+ *
+ * @param array $attributes
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function firstOrCreate(array $attributes)
+ {
+ if (is_null($instance = $this->where($attributes)->first()))
+ {
+ $instance = $this->create($attributes);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Create or update a related record matching the attributes, and fill it with values.
+ *
+ * @param array $attributes
+ * @param array $values
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function updateOrCreate(array $attributes, array $values = [])
+ {
+ $instance = $this->firstOrNew($attributes);
+
+ $instance->fill($values);
+
+ $instance->save();
+
+ return $instance;
+ }
+
/**
* Create a new instance of the related model.
*
@@ -189,16 +261,12 @@ public function saveMany(array $models)
*/
public function create(array $attributes)
{
- $foreign = array(
- $this->getPlainForeignKey() => $this->getParentKey(),
- );
-
// 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 = $this->related->newInstance($attributes);
- $instance->setRawAttributes(array_merge($attributes, $foreign));
+ $instance->setAttribute($this->getPlainForeignKey(), $this->getParentKey());
$instance->save();
@@ -233,14 +301,14 @@ public function update(array $attributes)
{
if ($this->related->usesTimestamps())
{
- $attributes[$this->relatedUpdatedAt()] = $this->related->freshTimestamp();
+ $attributes[$this->relatedUpdatedAt()] = $this->related->freshTimestampString();
}
return $this->query->update($attributes);
}
/**
- * Get the key for comparing against the pareny key in "has" query.
+ * Get the key for comparing against the parent key in "has" query.
*
* @return string
*/
@@ -272,23 +340,23 @@ public function getPlainForeignKey()
}
/**
- * Get the key value of the paren's local key.
+ * Get the key value of the parent's local key.
*
* @return mixed
*/
- protected function getParentKey()
+ public function getParentKey()
{
return $this->parent->getAttribute($this->localKey);
}
/**
- * Get the fully qualified parent key naem.
+ * Get the fully qualified parent key name.
*
* @return string
*/
- protected function getQualifiedParentKeyName()
+ public function getQualifiedParentKeyName()
{
return $this->parent->getTable().'.'.$this->localKey;
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/MorphMany.php b/Eloquent/Relations/MorphMany.php
index 710fab5fb..1abdf3797 100755
--- a/Eloquent/Relations/MorphMany.php
+++ b/Eloquent/Relations/MorphMany.php
@@ -19,7 +19,7 @@ public function getResults()
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
public function initRelation(array $models, $relation)
{
@@ -44,4 +44,4 @@ public function match(array $models, Collection $results, $relation)
return $this->matchMany($models, $results, $relation);
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/MorphOne.php b/Eloquent/Relations/MorphOne.php
index 9d00c539b..fdebc24ee 100755
--- a/Eloquent/Relations/MorphOne.php
+++ b/Eloquent/Relations/MorphOne.php
@@ -19,7 +19,7 @@ public function getResults()
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
public function initRelation(array $models, $relation)
{
@@ -44,4 +44,4 @@ public function match(array $models, Collection $results, $relation)
return $this->matchOne($models, $results, $relation);
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/MorphOneOrMany.php b/Eloquent/Relations/MorphOneOrMany.php
index cf18da984..19a98172c 100755
--- a/Eloquent/Relations/MorphOneOrMany.php
+++ b/Eloquent/Relations/MorphOneOrMany.php
@@ -20,7 +20,7 @@ abstract class MorphOneOrMany extends HasOneOrMany {
protected $morphClass;
/**
- * Create a new has many relationship instance.
+ * Create a new morph one or many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
@@ -33,7 +33,7 @@ public function __construct(Builder $query, Model $parent, $type, $id, $localKey
{
$this->morphType = $type;
- $this->morphClass = get_class($parent);
+ $this->morphClass = $parent->getMorphClass();
parent::__construct($query, $parent, $id, $localKey);
}
@@ -54,14 +54,15 @@ public function addConstraints()
}
/**
- * Add the constraints for a relationship count query.
+ * Get the relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Builder $parent
* @return \Illuminate\Database\Eloquent\Builder
*/
- public function getRelationCountQuery(Builder $query)
+ public function getRelationCountQuery(Builder $query, Builder $parent)
{
- $query = parent::getRelationCountQuery($query);
+ $query = parent::getRelationCountQuery($query, $parent);
return $query->where($this->morphType, $this->morphClass);
}
@@ -92,6 +93,83 @@ public function save(Model $model)
return parent::save($model);
}
+ /**
+ * Find a related model by its primary key or return new instance of the related model.
+ *
+ * @param mixed $id
+ * @param array $columns
+ * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model
+ */
+ public function findOrNew($id, $columns = ['*'])
+ {
+ if (is_null($instance = $this->find($id, $columns)))
+ {
+ $instance = $this->related->newInstance();
+
+ // 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.
+ $this->setForeignAttributesForCreate($instance);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get the first related model record matching the attributes or instantiate it.
+ *
+ * @param array $attributes
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function firstOrNew(array $attributes)
+ {
+ if (is_null($instance = $this->where($attributes)->first()))
+ {
+ $instance = $this->related->newInstance();
+
+ // 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.
+ $this->setForeignAttributesForCreate($instance);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get the first related record matching the attributes or create it.
+ *
+ * @param array $attributes
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function firstOrCreate(array $attributes)
+ {
+ if (is_null($instance = $this->where($attributes)->first()))
+ {
+ $instance = $this->create($attributes);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Create or update a related record matching the attributes, and fill it with values.
+ *
+ * @param array $attributes
+ * @param array $values
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function updateOrCreate(array $attributes, array $values = [])
+ {
+ $instance = $this->firstOrNew($attributes);
+
+ $instance->fill($values);
+
+ $instance->save();
+
+ return $instance;
+ }
+
/**
* Create a new instance of the related model.
*
@@ -100,14 +178,12 @@ public function save(Model $model)
*/
public function create(array $attributes)
{
- $foreign = $this->getForeignAttributesForCreate();
+ $instance = $this->related->newInstance($attributes);
// 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);
+ $this->setForeignAttributesForCreate($instance);
$instance->save();
@@ -115,17 +191,16 @@ public function create(array $attributes)
}
/**
- * Get the foreign ID and type for creating a related model.
+ * Set the foreign ID and type for creating a related model.
*
- * @return array
+ * @param \Illuminate\Database\Eloquent\Model $model
+ * @return void
*/
- protected function getForeignAttributesForCreate()
+ protected function setForeignAttributesForCreate(Model $model)
{
- $foreign = array($this->getPlainForeignKey() => $this->getParentKey());
-
- $foreign[last(explode('.', $this->morphType))] = $this->morphClass;
+ $model->{$this->getPlainForeignKey()} = $this->getParentKey();
- return $foreign;
+ $model->{last(explode('.', $this->morphType))} = $this->morphClass;
}
/**
@@ -158,4 +233,4 @@ public function getMorphClass()
return $this->morphClass;
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/MorphPivot.php b/Eloquent/Relations/MorphPivot.php
index 85ffbc8aa..fe8c644b2 100644
--- a/Eloquent/Relations/MorphPivot.php
+++ b/Eloquent/Relations/MorphPivot.php
@@ -4,15 +4,33 @@
class MorphPivot extends Pivot {
+ /**
+ * The type of the polymorphic relation.
+ *
+ * Explicitly define this so it's not included in saved attributes.
+ *
+ * @var string
+ */
+ protected $morphType;
+
+ /**
+ * The value of the polymorphic relation.
+ *
+ * Explicitly define this so it's not included in saved attributes.
+ *
+ * @var string
+ */
+ protected $morphClass;
+
/**
* Set the keys for a save update query.
*
- * @param \Illuminate\Database\Eloquent\Builder
+ * @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function setKeysForSaveQuery(Builder $query)
{
- $query->where($this->morphType, $this->getAttribute($this->morphType));
+ $query->where($this->morphType, $this->morphClass);
return parent::setKeysForSaveQuery($query);
}
@@ -26,7 +44,7 @@ public function delete()
{
$query = $this->getDeleteQuery();
- $query->where($this->morphType, $this->getAttribute($this->morphType));
+ $query->where($this->morphType, $this->morphClass);
return $query->delete();
}
@@ -35,7 +53,7 @@ public function delete()
* Set the morph type for the pivot.
*
* @param string $morphType
- * @return \Illuminate\Database\Eloquent\Relations\MorphPivot
+ * @return $this
*/
public function setMorphType($morphType)
{
@@ -44,4 +62,17 @@ public function setMorphType($morphType)
return $this;
}
+ /**
+ * Set the morph class for the pivot.
+ *
+ * @param string $morphClass
+ * @return \Illuminate\Database\Eloquent\Relations\MorphPivot
+ */
+ public function setMorphClass($morphClass)
+ {
+ $this->morphClass = $morphClass;
+
+ return $this;
+ }
+
}
diff --git a/Eloquent/Relations/MorphTo.php b/Eloquent/Relations/MorphTo.php
new file mode 100644
index 000000000..c401a9c0c
--- /dev/null
+++ b/Eloquent/Relations/MorphTo.php
@@ -0,0 +1,246 @@
+morphType = $type;
+
+ parent::__construct($query, $parent, $foreignKey, $otherKey, $relation);
+ }
+
+ /**
+ * Set the constraints for an eager load of the relation.
+ *
+ * @param array $models
+ * @return void
+ */
+ public function addEagerConstraints(array $models)
+ {
+ $this->buildDictionary($this->models = Collection::make($models));
+ }
+
+ /**
+ * Build a dictionary with the models.
+ *
+ * @param \Illuminate\Database\Eloquent\Collection $models
+ * @return void
+ */
+ protected function buildDictionary(Collection $models)
+ {
+ foreach ($models as $model)
+ {
+ if ($model->{$this->morphType})
+ {
+ $this->dictionary[$model->{$this->morphType}][$model->{$this->foreignKey}][] = $model;
+ }
+ }
+ }
+
+ /**
+ * 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 $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());
+
+ $this->parent->setAttribute($this->morphType, $model->getMorphClass());
+
+ return $this->parent->setRelation($this->relation, $model);
+ }
+
+ /**
+ * Get the results of the relationship.
+ *
+ * Called via eager load method of Eloquent query builder.
+ *
+ * @return mixed
+ */
+ public function getEager()
+ {
+ foreach (array_keys($this->dictionary) as $type)
+ {
+ $this->matchToMorphParents($type, $this->getResultsByType($type));
+ }
+
+ return $this->models;
+ }
+
+ /**
+ * Match the results for a given type to their parents.
+ *
+ * @param string $type
+ * @param \Illuminate\Database\Eloquent\Collection $results
+ * @return void
+ */
+ protected function matchToMorphParents($type, Collection $results)
+ {
+ foreach ($results as $result)
+ {
+ if (isset($this->dictionary[$type][$result->getKey()]))
+ {
+ foreach ($this->dictionary[$type][$result->getKey()] as $model)
+ {
+ $model->setRelation($this->relation, $result);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get all of the relation results for a type.
+ *
+ * @param string $type
+ * @return \Illuminate\Database\Eloquent\Collection
+ */
+ protected function getResultsByType($type)
+ {
+ $instance = $this->createModelByType($type);
+
+ $key = $instance->getKeyName();
+
+ $query = $instance->newQuery();
+
+ $query = $this->useWithTrashed($query);
+
+ return $query->whereIn($key, $this->gatherKeysByType($type)->all())->get();
+ }
+
+ /**
+ * Gather all of the foreign keys for a given type.
+ *
+ * @param string $type
+ * @return array
+ */
+ protected function gatherKeysByType($type)
+ {
+ $foreign = $this->foreignKey;
+
+ return BaseCollection::make($this->dictionary[$type])->map(function($models) use ($foreign)
+ {
+ return head($models)->{$foreign};
+
+ })->unique();
+ }
+
+ /**
+ * Create a new model instance by type.
+ *
+ * @param string $type
+ * @return \Illuminate\Database\Eloquent\Model
+ */
+ public function createModelByType($type)
+ {
+ return new $type;
+ }
+
+ /**
+ * Get the foreign key "type" name.
+ *
+ * @return string
+ */
+ public function getMorphType()
+ {
+ return $this->morphType;
+ }
+
+ /**
+ * Get the dictionary used by the relationship.
+ *
+ * @return array
+ */
+ public function getDictionary()
+ {
+ return $this->dictionary;
+ }
+
+ /**
+ * Fetch soft-deleted model instances with query
+ *
+ * @return $this
+ */
+ public function withTrashed()
+ {
+ $this->withTrashed = true;
+
+ $this->query = $this->useWithTrashed($this->query);
+
+ return $this;
+ }
+
+ /**
+ * Return trashed models with query if told so
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ protected function useWithTrashed(Builder $query)
+ {
+ if ($this->withTrashed && $query->getMacro('withTrashed') !== null)
+ {
+ return $query->withTrashed();
+ }
+ return $query;
+ }
+
+}
diff --git a/Eloquent/Relations/MorphToMany.php b/Eloquent/Relations/MorphToMany.php
index 26b5dc852..18d970d92 100644
--- a/Eloquent/Relations/MorphToMany.php
+++ b/Eloquent/Relations/MorphToMany.php
@@ -29,7 +29,7 @@ class MorphToMany extends BelongsToMany {
protected $inverse;
/**
- * Create a new has many relationship instance.
+ * Create a new morph to many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
@@ -45,7 +45,7 @@ public function __construct(Builder $query, Model $parent, $name, $table, $forei
{
$this->inverse = $inverse;
$this->morphType = $name.'_type';
- $this->morphClass = $inverse ? get_class($query->getModel()) : get_class($parent);
+ $this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass();
parent::__construct($query, $parent, $table, $foreignKey, $otherKey, $relationName);
}
@@ -53,7 +53,7 @@ public function __construct(Builder $query, Model $parent, $name, $table, $forei
/**
* Set the where clause for the relation query.
*
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ * @return $this
*/
protected function setWhere()
{
@@ -64,6 +64,20 @@ protected function setWhere()
return $this;
}
+ /**
+ * Add the constraints for a relationship count query.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Builder $parent
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function getRelationCountQuery(Builder $query, Builder $parent)
+ {
+ $query = parent::getRelationCountQuery($query, $parent);
+
+ return $query->where($this->table.'.'.$this->morphType, $this->morphClass);
+ }
+
/**
* Set the constraints for an eager load of the relation.
*
@@ -108,17 +122,37 @@ protected function newPivotQuery()
*
* @param array $attributes
* @param bool $exists
- * @return \Illuminate\Database\Eloquent\Relation\Pivot
+ * @return \Illuminate\Database\Eloquent\Relations\Pivot
*/
public function newPivot(array $attributes = array(), $exists = false)
{
$pivot = new MorphPivot($this->parent, $attributes, $this->table, $exists);
- $pivot->setPivotKeys($this->foreignKey, $this->otherKey);
-
- $pivot->setMorphType($this->morphType);
+ $pivot->setPivotKeys($this->foreignKey, $this->otherKey)
+ ->setMorphType($this->morphType)
+ ->setMorphClass($this->morphClass);
return $pivot;
}
-}
\ No newline at end of file
+ /**
+ * Get the foreign key "type" name.
+ *
+ * @return string
+ */
+ public function getMorphType()
+ {
+ return $this->morphType;
+ }
+
+ /**
+ * Get the class name of the parent model.
+ *
+ * @return string
+ */
+ public function getMorphClass()
+ {
+ return $this->morphClass;
+ }
+
+}
diff --git a/Eloquent/Relations/Pivot.php b/Eloquent/Relations/Pivot.php
index b36aafac5..365477e05 100755
--- a/Eloquent/Relations/Pivot.php
+++ b/Eloquent/Relations/Pivot.php
@@ -49,7 +49,7 @@ public function __construct(Model $parent, $attributes, $table, $exists = false)
// 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->setRawAttributes($attributes, true);
$this->setTable($table);
@@ -99,7 +99,7 @@ protected function getDeleteQuery()
$query = $this->newQuery()->where($this->foreignKey, $foreign);
- return $query->where($this->otherKey, $this->getAttribute($this->otherKey));
+ return $query->where($this->otherKey, $this->getAttribute($this->otherKey));
}
/**
@@ -127,7 +127,7 @@ public function getOtherKey()
*
* @param string $foreignKey
* @param string $otherKey
- * @return \Illuminate\Database\Eloquent\Relations\Pivot
+ * @return $this
*/
public function setPivotKeys($foreignKey, $otherKey)
{
@@ -168,4 +168,4 @@ public function getUpdatedAtColumn()
return $this->parent->getUpdatedAtColumn();
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/Relations/Relation.php b/Eloquent/Relations/Relation.php
index 953815e9e..86d05392d 100755
--- a/Eloquent/Relations/Relation.php
+++ b/Eloquent/Relations/Relation.php
@@ -72,7 +72,7 @@ abstract public function addEagerConstraints(array $models);
*
* @param array $models
* @param string $relation
- * @return void
+ * @return array
*/
abstract public function initRelation(array $models, $relation);
@@ -94,25 +94,25 @@ abstract public function match(array $models, Collection $results, $relation);
abstract public function getResults();
/**
- * Touch all of the related models for the relationship.
+ * Get the relationship for eager loading.
*
- * @return void
+ * @return \Illuminate\Database\Eloquent\Collection
*/
- public function touch()
+ public function getEager()
{
- $column = $this->getRelated()->getUpdatedAtColumn();
-
- $this->rawUpdate(array($column => $this->getRelated()->freshTimestampString()));
+ return $this->get();
}
/**
- * Restore all of the soft deleted related models.
+ * Touch all of the related models for the relationship.
*
- * @return int
+ * @return void
*/
- public function restore()
+ public function touch()
{
- return $this->query->withTrashed()->restore();
+ $column = $this->getRelated()->getUpdatedAtColumn();
+
+ $this->rawUpdate(array($column => $this->getRelated()->freshTimestampString()));
}
/**
@@ -130,9 +130,10 @@ public function rawUpdate(array $attributes = array())
* Add the constraints for a relationship count query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
+ * @param \Illuminate\Database\Eloquent\Builder $parent
* @return \Illuminate\Database\Eloquent\Builder
*/
- public function getRelationCountQuery(Builder $query)
+ public function getRelationCountQuery(Builder $query, Builder $parent)
{
$query->select(new Expression('count(*)'));
@@ -142,7 +143,7 @@ public function getRelationCountQuery(Builder $query)
}
/**
- * Run a callback with constrains disabled on the relation.
+ * Run a callback with constraints disabled on the relation.
*
* @param \Closure $callback
* @return mixed
@@ -164,16 +165,17 @@ public static function noConstraints(Closure $callback)
/**
* Get all of the primary keys for an array of models.
*
- * @param array $models
+ * @param array $models
+ * @param string $key
* @return array
*/
- protected function getKeys(array $models)
+ protected function getKeys(array $models, $key = null)
{
- return array_values(array_map(function($value)
+ return array_unique(array_values(array_map(function($value) use ($key)
{
- return $value->getKey();
+ return $key ? $value->getAttribute($key) : $value->getKey();
- }, $models));
+ }, $models)));
}
/**
@@ -207,11 +209,11 @@ public function getParent()
}
/**
- * Get the fully qualified parent key naem.
+ * Get the fully qualified parent key name.
*
* @return string
*/
- protected function getQualifiedParentKeyName()
+ public function getQualifiedParentKeyName()
{
return $this->parent->getQualifiedKeyName();
}
@@ -283,4 +285,4 @@ public function __call($method, $parameters)
return $result;
}
-}
\ No newline at end of file
+}
diff --git a/Eloquent/ScopeInterface.php b/Eloquent/ScopeInterface.php
new file mode 100644
index 000000000..7cc13494f
--- /dev/null
+++ b/Eloquent/ScopeInterface.php
@@ -0,0 +1,24 @@
+forceDeleting = true;
+
+ $this->delete();
+
+ $this->forceDeleting = false;
+ }
+
+ /**
+ * Perform the actual delete query on this model instance.
+ *
+ * @return void
+ */
+ protected function performDeleteOnModel()
+ {
+ if ($this->forceDeleting)
+ {
+ return $this->withTrashed()->where($this->getKeyName(), $this->getKey())->forceDelete();
+ }
+
+ return $this->runSoftDelete();
+ }
+
+ /**
+ * Perform the actual delete query on this model instance.
+ *
+ * @return void
+ */
+ protected function runSoftDelete()
+ {
+ $query = $this->newQuery()->where($this->getKeyName(), $this->getKey());
+
+ $this->{$this->getDeletedAtColumn()} = $time = $this->freshTimestamp();
+
+ $query->update(array($this->getDeletedAtColumn() => $this->fromDateTime($time)));
+ }
+
+ /**
+ * Restore a soft-deleted model instance.
+ *
+ * @return bool|null
+ */
+ public function restore()
+ {
+ // If the restoring event does not return false, we will proceed with this
+ // restore operation. Otherwise, we bail out so the developer will stop
+ // the restore totally. We will clear the deleted timestamp and save.
+ if ($this->fireModelEvent('restoring') === false)
+ {
+ return false;
+ }
+
+ $this->{$this->getDeletedAtColumn()} = null;
+
+ // Once we have saved the model, we will fire the "restored" event so this
+ // developer will do anything they need to after a restore operation is
+ // totally finished. Then we will return the result of the save call.
+ $this->exists = true;
+
+ $result = $this->save();
+
+ $this->fireModelEvent('restored', false);
+
+ return $result;
+ }
+
+ /**
+ * Determine if the model instance has been soft-deleted.
+ *
+ * @return bool
+ */
+ public function trashed()
+ {
+ return ! is_null($this->{$this->getDeletedAtColumn()});
+ }
+
+ /**
+ * Get a new query builder that includes soft deletes.
+ *
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ public static function withTrashed()
+ {
+ return (new static)->newQueryWithoutScope(new SoftDeletingScope);
+ }
+
+ /**
+ * Get a new query builder that only includes soft deletes.
+ *
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ public static function onlyTrashed()
+ {
+ $instance = new static;
+
+ $column = $instance->getQualifiedDeletedAtColumn();
+
+ return $instance->newQueryWithoutScope(new SoftDeletingScope)->whereNotNull($column);
+ }
+
+ /**
+ * Register a restoring model event with the dispatcher.
+ *
+ * @param \Closure|string $callback
+ * @return void
+ */
+ public static function restoring($callback)
+ {
+ static::registerModelEvent('restoring', $callback);
+ }
+
+ /**
+ * Register a restored model event with the dispatcher.
+ *
+ * @param \Closure|string $callback
+ * @return void
+ */
+ public static function restored($callback)
+ {
+ static::registerModelEvent('restored', $callback);
+ }
+
+ /**
+ * Get the name of the "deleted at" column.
+ *
+ * @return string
+ */
+ public function getDeletedAtColumn()
+ {
+ return defined('static::DELETED_AT') ? static::DELETED_AT : 'deleted_at';
+ }
+
+ /**
+ * Get the fully qualified "deleted at" column.
+ *
+ * @return string
+ */
+ public function getQualifiedDeletedAtColumn()
+ {
+ return $this->getTable().'.'.$this->getDeletedAtColumn();
+ }
+
+}
diff --git a/Eloquent/SoftDeletingScope.php b/Eloquent/SoftDeletingScope.php
new file mode 100644
index 000000000..b4d26ac62
--- /dev/null
+++ b/Eloquent/SoftDeletingScope.php
@@ -0,0 +1,172 @@
+whereNull($model->getQualifiedDeletedAtColumn());
+
+ $this->extend($builder);
+ }
+
+ /**
+ * Remove the scope from the given Eloquent query builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @param \Illuminate\Database\Eloquent\Model $model
+ * @return void
+ */
+ public function remove(Builder $builder, Model $model)
+ {
+ $column = $model->getQualifiedDeletedAtColumn();
+
+ $query = $builder->getQuery();
+
+ foreach ((array) $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($query->wheres[$key]);
+
+ $query->wheres = array_values($query->wheres);
+ }
+ }
+ }
+
+ /**
+ * Extend the query builder with the needed functions.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ public function extend(Builder $builder)
+ {
+ foreach ($this->extensions as $extension)
+ {
+ $this->{"add{$extension}"}($builder);
+ }
+
+ $builder->onDelete(function(Builder $builder)
+ {
+ $column = $this->getDeletedAtColumn($builder);
+
+ return $builder->update(array(
+ $column => $builder->getModel()->freshTimestampString()
+ ));
+ });
+ }
+
+ /**
+ * Get the "deleted at" column for the builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return string
+ */
+ protected function getDeletedAtColumn(Builder $builder)
+ {
+ if (count($builder->getQuery()->joins) > 0)
+ {
+ return $builder->getModel()->getQualifiedDeletedAtColumn();
+ }
+ else
+ {
+ return $builder->getModel()->getDeletedAtColumn();
+ }
+ }
+
+ /**
+ * Add the force delete extension to the builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ protected function addForceDelete(Builder $builder)
+ {
+ $builder->macro('forceDelete', function(Builder $builder)
+ {
+ return $builder->getQuery()->delete();
+ });
+ }
+
+ /**
+ * Add the restore extension to the builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ protected function addRestore(Builder $builder)
+ {
+ $builder->macro('restore', function(Builder $builder)
+ {
+ $builder->withTrashed();
+
+ return $builder->update(array($builder->getModel()->getDeletedAtColumn() => null));
+ });
+ }
+
+ /**
+ * Add the with-trashed extension to the builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ protected function addWithTrashed(Builder $builder)
+ {
+ $builder->macro('withTrashed', function(Builder $builder)
+ {
+ $this->remove($builder, $builder->getModel());
+
+ return $builder;
+ });
+ }
+
+ /**
+ * Add the only-trashed extension to the builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ protected function addOnlyTrashed(Builder $builder)
+ {
+ $builder->macro('onlyTrashed', function(Builder $builder)
+ {
+ $model = $builder->getModel();
+
+ $this->remove($builder, $model);
+
+ $builder->getQuery()->whereNotNull($model->getQualifiedDeletedAtColumn());
+
+ return $builder;
+ });
+ }
+
+ /**
+ * 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['type'] == 'Null' && $where['column'] == $column;
+ }
+
+}
diff --git a/Grammar.php b/Grammar.php
index 32594bf03..e1e600691 100755
--- a/Grammar.php
+++ b/Grammar.php
@@ -30,16 +30,17 @@ public function wrapTable($table)
{
if ($this->isExpression($table)) return $this->getValue($table);
- return $this->wrap($this->tablePrefix.$table);
+ return $this->wrap($this->tablePrefix.$table, true);
}
/**
* Wrap a value in keyword identifiers.
*
* @param string $value
+ * @param bool $prefixAlias
* @return string
*/
- public function wrap($value)
+ public function wrap($value, $prefixAlias = false)
{
if ($this->isExpression($value)) return $this->getValue($value);
@@ -50,7 +51,9 @@ public function wrap($value)
{
$segments = explode(' ', $value);
- return $this->wrap($segments[0]).' as '.$this->wrap($segments[2]);
+ if ($prefixAlias) $segments[2] = $this->tablePrefix.$segments[2];
+
+ return $this->wrap($segments[0]).' as '.$this->wrapValue($segments[2]);
}
$wrapped = array();
@@ -83,7 +86,9 @@ public function wrap($value)
*/
protected function wrapValue($value)
{
- return $value !== '*' ? sprintf($this->wrapper, $value) : $value;
+ if ($value === '*') return $value;
+
+ return '"'.str_replace('"', '""', $value).'"';
}
/**
@@ -165,7 +170,7 @@ public function getTablePrefix()
* Set the grammar's table prefix.
*
* @param string $prefix
- * @return \Illuminate\Database\Grammar
+ * @return $this
*/
public function setTablePrefix($prefix)
{
@@ -174,4 +179,4 @@ public function setTablePrefix($prefix)
return $this;
}
-}
\ No newline at end of file
+}
diff --git a/MigrationServiceProvider.php b/MigrationServiceProvider.php
index 6915d9375..aeca8c52f 100755
--- a/MigrationServiceProvider.php
+++ b/MigrationServiceProvider.php
@@ -1,207 +1,226 @@
-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->bindShared('migration.repository', 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->bindShared('migrator', 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->bindShared('command.migrate', 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->bindShared('command.migrate.rollback', function($app)
- {
- return new RollbackCommand($app['migrator']);
- });
- }
-
- /**
- * Register the "reset" migration command.
- *
- * @return void
- */
- protected function registerResetCommand()
- {
- $this->app->bindShared('command.migrate.reset', function($app)
- {
- return new ResetCommand($app['migrator']);
- });
- }
-
- /**
- * Register the "refresh" migration command.
- *
- * @return void
- */
- protected function registerRefreshCommand()
- {
- $this->app->bindShared('command.migrate.refresh', function($app)
- {
- return new RefreshCommand;
- });
- }
-
- /**
- * Register the "install" migration command.
- *
- * @return void
- */
- protected function registerInstallCommand()
- {
- $this->app->bindShared('command.migrate.install', function($app)
- {
- return new InstallCommand($app['migration.repository']);
- });
- }
-
- /**
- * Register the "install" migration command.
- *
- * @return void
- */
- protected function registerMakeCommand()
- {
- $this->app->bindShared('migration.creator', function($app)
- {
- return new MigrationCreator($app['files']);
- });
-
- $this->app->bindShared('command.migrate.make', 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',
- );
- }
-
-}
\ No newline at end of file
+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->singleton('migration.repository', 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->singleton('migrator', 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', 'Status');
+
+ // 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',
+ 'command.migrate.status'
+ );
+ }
+
+ /**
+ * Register the "migrate" migration command.
+ *
+ * @return void
+ */
+ protected function registerMigrateCommand()
+ {
+ $this->app->singleton('command.migrate', function($app)
+ {
+ return new MigrateCommand($app['migrator']);
+ });
+ }
+
+ /**
+ * Register the "rollback" migration command.
+ *
+ * @return void
+ */
+ protected function registerRollbackCommand()
+ {
+ $this->app->singleton('command.migrate.rollback', function($app)
+ {
+ return new RollbackCommand($app['migrator']);
+ });
+ }
+
+ /**
+ * Register the "reset" migration command.
+ *
+ * @return void
+ */
+ protected function registerResetCommand()
+ {
+ $this->app->singleton('command.migrate.reset', function($app)
+ {
+ return new ResetCommand($app['migrator']);
+ });
+ }
+
+ /**
+ * Register the "refresh" migration command.
+ *
+ * @return void
+ */
+ protected function registerRefreshCommand()
+ {
+ $this->app->singleton('command.migrate.refresh', function()
+ {
+ return new RefreshCommand;
+ });
+ }
+
+ protected function registerStatusCommand()
+ {
+ $this->app->singleton('command.migrate.status', function($app)
+ {
+ return new StatusCommand($app['migrator']);
+ });
+ }
+
+ /**
+ * Register the "install" migration command.
+ *
+ * @return void
+ */
+ protected function registerInstallCommand()
+ {
+ $this->app->singleton('command.migrate.install', function($app)
+ {
+ return new InstallCommand($app['migration.repository']);
+ });
+ }
+
+ /**
+ * Register the "make" migration command.
+ *
+ * @return void
+ */
+ protected function registerMakeCommand()
+ {
+ $this->registerCreator();
+
+ $this->app->singleton('command.migrate.make', 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'];
+
+ $composer = $app['composer'];
+
+ return new MigrateMakeCommand($creator, $composer);
+ });
+ }
+
+ /**
+ * Register the migration creator.
+ *
+ * @return void
+ */
+ protected function registerCreator()
+ {
+ $this->app->singleton('migration.creator', function($app)
+ {
+ return new MigrationCreator($app['files']);
+ });
+ }
+
+ /**
+ * 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',
+ 'command.migrate.status', 'migration.creator',
+ 'command.migrate.make',
+ );
+ }
+
+}
diff --git a/Migrations/DatabaseMigrationRepository.php b/Migrations/DatabaseMigrationRepository.php
index d7104b791..88947100d 100755
--- a/Migrations/DatabaseMigrationRepository.php
+++ b/Migrations/DatabaseMigrationRepository.php
@@ -82,7 +82,7 @@ public function log($file, $batch)
*/
public function delete($migration)
{
- $query = $this->table()->where('migration', $migration->migration)->delete();
+ $this->table()->where('migration', $migration->migration)->delete();
}
/**
@@ -178,4 +178,4 @@ public function setSource($name)
$this->connection = $name;
}
-}
\ No newline at end of file
+}
diff --git a/Migrations/Migration.php b/Migrations/Migration.php
index 05a6b9402..eb75d1430 100755
--- a/Migrations/Migration.php
+++ b/Migrations/Migration.php
@@ -19,4 +19,4 @@ public function getConnection()
return $this->connection;
}
-}
\ No newline at end of file
+}
diff --git a/Migrations/MigrationCreator.php b/Migrations/MigrationCreator.php
index daf24df05..e70b627b5 100755
--- a/Migrations/MigrationCreator.php
+++ b/Migrations/MigrationCreator.php
@@ -59,7 +59,8 @@ public function create($name, $path, $table = null, $create = false)
* Get the migration stub file.
*
* @param string $table
- * @return void
+ * @param bool $create
+ * @return string
*/
protected function getStub($table, $create)
{
@@ -89,7 +90,7 @@ protected function getStub($table, $create)
*/
protected function populateStub($name, $stub, $table)
{
- $stub = str_replace('{{class}}', studly_case($name), $stub);
+ $stub = str_replace('{{class}}', $this->getClassName($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
@@ -102,6 +103,17 @@ protected function populateStub($name, $stub, $table)
return $stub;
}
+ /**
+ * Get the class name of a migration name.
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function getClassName($name)
+ {
+ return studly_case($name);
+ }
+
/**
* Fire the registered post create hooks.
*
@@ -118,7 +130,7 @@ protected function firePostCreateHooks()
/**
* Register a post migration create hook.
*
- * @param Closure $callback
+ * @param \Closure $callback
* @return void
*/
public function afterCreate(Closure $callback)
@@ -168,4 +180,4 @@ public function getFilesystem()
return $this->files;
}
-}
\ No newline at end of file
+}
diff --git a/Migrations/Migrator.php b/Migrations/Migrator.php
index 386532e51..fcb0e41bd 100755
--- a/Migrations/Migrator.php
+++ b/Migrations/Migrator.php
@@ -68,15 +68,17 @@ public function run($path, $pretend = false)
{
$this->notes = array();
- $this->requireFiles($path, $files = $this->getMigrationFiles($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.
+ // run each of the outstanding migrations against a database connection.
$ran = $this->repository->getRan();
$migrations = array_diff($files, $ran);
+ $this->requireFiles($path, $migrations);
+
$this->runMigrationList($migrations, $pretend);
}
@@ -236,7 +238,8 @@ public function getMigrationFiles($path)
/**
* Require in all the migration files in a given path.
*
- * @param array $files
+ * @param string $path
+ * @param array $files
* @return void
*/
public function requireFiles($path, array $files)
@@ -248,6 +251,7 @@ public function requireFiles($path, array $files)
* Pretend to run the migrations.
*
* @param object $migration
+ * @param string $method
* @return void
*/
protected function pretendToRun($migration, $method)
@@ -321,11 +325,12 @@ public function getNotes()
/**
* Resolve the database connection instance.
*
+ * @param string $connection
* @return \Illuminate\Database\Connection
*/
- public function resolveConnection()
+ public function resolveConnection($connection)
{
- return $this->resolver->connection($this->connection);
+ return $this->resolver->connection($connection);
}
/**
@@ -376,4 +381,4 @@ public function getFilesystem()
return $this->files;
}
-}
\ No newline at end of file
+}
diff --git a/Migrations/stubs/blank.stub b/Migrations/stubs/blank.stub
index 5ce40164c..a71120195 100755
--- a/Migrations/stubs/blank.stub
+++ b/Migrations/stubs/blank.stub
@@ -1,5 +1,6 @@
[],
+ 'join' => [],
+ 'where' => [],
+ 'having' => [],
+ 'order' => [],
+ );
/**
* An aggregate function and column to be run.
@@ -121,32 +131,39 @@ class Builder {
public $unions;
/**
- * Indicates whether row locking is being used.
+ * The maximum number of union records to return.
*
- * @var string|bool
+ * @var int
*/
- public $lock;
+ public $unionLimit;
/**
- * The key that should be used when caching the query.
+ * The number of union records to skip.
*
- * @var string
+ * @var int
*/
- protected $cacheKey;
+ public $unionOffset;
/**
- * The number of minutes to cache the query.
+ * The orderings for the union query.
*
- * @var int
+ * @var array
+ */
+ public $unionOrders;
+
+ /**
+ * Indicates whether row locking is being used.
+ *
+ * @var string|bool
*/
- protected $cacheMinutes;
+ public $lock;
/**
- * The tags for the query cache.
+ * The field backups currently in use.
*
* @var array
*/
- protected $cacheTags;
+ protected $backups = [];
/**
* All of the available clause operators.
@@ -157,8 +174,17 @@ class Builder {
'=', '<', '>', '<=', '>=', '<>', '!=',
'like', 'not like', 'between', 'ilike',
'&', '|', '^', '<<', '>>',
+ 'rlike', 'regexp', 'not regexp',
+ '~', '~*', '!~', '!~*',
);
+ /**
+ * Whether use write pdo for select.
+ *
+ * @var bool
+ */
+ protected $useWritePdo = false;
+
/**
* Create a new query builder instance.
*
@@ -180,7 +206,7 @@ public function __construct(ConnectionInterface $connection,
* Set the columns to be selected.
*
* @param array $columns
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function select($columns = array('*'))
{
@@ -189,11 +215,64 @@ public function select($columns = array('*'))
return $this;
}
+ /**
+ * Add a new "raw" select expression to the query.
+ *
+ * @param string $expression
+ * @param array $bindings
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function selectRaw($expression, array $bindings = array())
+ {
+ $this->addSelect(new Expression($expression));
+
+ if ($bindings)
+ {
+ $this->addBinding($bindings, 'select');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a subselect expression to the query.
+ *
+ * @param \Closure|\Illuminate\Database\Query\Builder|string $query
+ * @param string $as
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function selectSub($query, $as)
+ {
+ if ($query instanceof Closure)
+ {
+ $callback = $query;
+
+ $callback($query = $this->newQuery());
+ }
+
+ if ($query instanceof Builder)
+ {
+ $bindings = $query->getBindings();
+
+ $query = $query->toSql();
+ }
+ elseif (is_string($query))
+ {
+ $bindings = [];
+ }
+ else
+ {
+ throw new InvalidArgumentException;
+ }
+
+ return $this->selectRaw('('.$query.') as '.$this->grammar->wrap($as), $bindings);
+ }
+
/**
* Add a new select column to the query.
*
* @param mixed $column
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function addSelect($column)
{
@@ -207,7 +286,7 @@ public function addSelect($column)
/**
* Force the query to only return distinct results.
*
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function distinct()
{
@@ -220,7 +299,7 @@ public function distinct()
* Set the table which the query is targeting.
*
* @param string $table
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function from($table)
{
@@ -233,12 +312,12 @@ public function from($table)
* Add a join clause to the query.
*
* @param string $table
- * @param string $first
+ * @param string $one
* @param string $operator
* @param string $two
* @param string $type
- * @param bool $where
- * @return \Illuminate\Database\Query\Builder|static
+ * @param bool $where
+ * @return $this
*/
public function join($table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
{
@@ -247,7 +326,7 @@ public function join($table, $one, $operator = null, $two = null, $type = 'inner
// one condition, so we'll add the join and call a Closure with the query.
if ($one instanceof Closure)
{
- $this->joins[] = new JoinClause($this, $type, $table);
+ $this->joins[] = new JoinClause($type, $table);
call_user_func($one, end($this->joins));
}
@@ -257,7 +336,7 @@ public function join($table, $one, $operator = null, $two = null, $type = 'inner
// this simple join clauses attached to it. There is not a join callback.
else
{
- $join = new JoinClause($this, $type, $table);
+ $join = new JoinClause($type, $table);
$this->joins[] = $join->on(
$one, $operator, $two, 'and', $where
@@ -271,7 +350,7 @@ public function join($table, $one, $operator = null, $two = null, $type = 'inner
* Add a "join where" clause to the query.
*
* @param string $table
- * @param string $first
+ * @param string $one
* @param string $operator
* @param string $two
* @param string $type
@@ -300,7 +379,7 @@ public function leftJoin($table, $first, $operator = null, $second = null)
* Add a "join where" clause to the query.
*
* @param string $table
- * @param string $first
+ * @param string $one
* @param string $operator
* @param string $two
* @return \Illuminate\Database\Query\Builder|static
@@ -310,6 +389,34 @@ public function leftJoinWhere($table, $one, $operator, $two)
return $this->joinWhere($table, $one, $operator, $two, 'left');
}
+ /**
+ * Add a right join to the query.
+ *
+ * @param string $table
+ * @param string $first
+ * @param string $operator
+ * @param string $second
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function rightJoin($table, $first, $operator = null, $second = null)
+ {
+ return $this->join($table, $first, $operator, $second, 'right');
+ }
+
+ /**
+ * Add a "right join where" clause to the query.
+ *
+ * @param string $table
+ * @param string $one
+ * @param string $operator
+ * @param string $two
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function rightJoinWhere($table, $one, $operator, $two)
+ {
+ return $this->joinWhere($table, $one, $operator, $two, 'right');
+ }
+
/**
* Add a basic where clause to the query.
*
@@ -317,19 +424,36 @@ public function leftJoinWhere($table, $one, $operator, $two)
* @param string $operator
* @param mixed $value
* @param string $boolean
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*
* @throws \InvalidArgumentException
*/
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
+ // If the column is an array, we will assume it is an array of key-value pairs
+ // and can add them each as a where clause. We will maintain the boolean we
+ // received when the method was called and pass it into the nested where.
+ if (is_array($column))
+ {
+ return $this->whereNested(function($query) use ($column)
+ {
+ foreach ($column as $key => $value)
+ {
+ $query->where($key, '=', $value);
+ }
+ }, $boolean);
+ }
+
+ // Here we will make some assumptions about the operator. If only 2 values are
+ // passed to the method, we will assume that the operator is an equals sign
+ // and keep going. Otherwise, we'll require the operator to be passed in.
if (func_num_args() == 2)
{
list($value, $operator) = array($operator, '=');
}
elseif ($this->invalidOperatorAndValue($operator, $value))
{
- throw new \InvalidArgumentException("Value must be provided.");
+ throw new InvalidArgumentException("Value must be provided.");
}
// If the columns is actually a Closure instance, we will assume the developer
@@ -373,7 +497,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
if ( ! $value instanceof Expression)
{
- $this->bindings[] = $value;
+ $this->addBinding($value, 'where');
}
return $this;
@@ -396,7 +520,7 @@ public function orWhere($column, $operator = null, $value = null)
* Determine if the given operator and value combination is legal.
*
* @param string $operator
- * @param mxied $value
+ * @param mixed $value
* @return bool
*/
protected function invalidOperatorAndValue($operator, $value)
@@ -412,7 +536,7 @@ protected function invalidOperatorAndValue($operator, $value)
* @param string $sql
* @param array $bindings
* @param string $boolean
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function whereRaw($sql, array $bindings = array(), $boolean = 'and')
{
@@ -420,7 +544,7 @@ public function whereRaw($sql, array $bindings = array(), $boolean = 'and')
$this->wheres[] = compact('type', 'sql', 'boolean');
- $this->bindings = array_merge($this->bindings, $bindings);
+ $this->addBinding($bindings, 'where');
return $this;
}
@@ -444,7 +568,7 @@ public function orWhereRaw($sql, array $bindings = array())
* @param array $values
* @param string $boolean
* @param bool $not
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function whereBetween($column, array $values, $boolean = 'and', $not = false)
{
@@ -452,7 +576,7 @@ public function whereBetween($column, array $values, $boolean = 'and', $not = fa
$this->wheres[] = compact('column', 'type', 'boolean', 'not');
- $this->bindings = array_merge($this->bindings, $values);
+ $this->addBinding($values, 'where');
return $this;
}
@@ -462,10 +586,9 @@ public function whereBetween($column, array $values, $boolean = 'and', $not = fa
*
* @param string $column
* @param array $values
- * @param bool $not
* @return \Illuminate\Database\Query\Builder|static
*/
- public function orWhereBetween($column, array $values, $not = false)
+ public function orWhereBetween($column, array $values)
{
return $this->whereBetween($column, $values, 'or');
}
@@ -507,19 +630,28 @@ public function whereNested(Closure $callback, $boolean = 'and')
// To handle nested queries we'll actually create a brand new query instance
// and pass it off to the Closure that we have. The Closure can simply do
// do whatever it wants to a query then we will store it for compiling.
- $type = 'Nested';
-
$query = $this->newQuery();
$query->from($this->from);
call_user_func($callback, $query);
- // Once we have let the Closure do its things, we can gather the bindings on
- // the nested query builder and merge them into these bindings since they
- // need to get extracted out of the children and assigned to the array.
+ return $this->addNestedWhereQuery($query, $boolean);
+ }
+
+ /**
+ * Add another query builder as a nested where to the query builder.
+ *
+ * @param \Illuminate\Database\Query\Builder|static $query
+ * @param string $boolean
+ * @return $this
+ */
+ public function addNestedWhereQuery($query, $boolean = 'and')
+ {
if (count($query->wheres))
{
+ $type = 'Nested';
+
$this->wheres[] = compact('type', 'query', 'boolean');
$this->mergeBindings($query);
@@ -535,7 +667,7 @@ public function whereNested(Closure $callback, $boolean = 'and')
* @param string $operator
* @param \Closure $callback
* @param string $boolean
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
protected function whereSub($column, $operator, Closure $callback, $boolean)
{
@@ -561,7 +693,7 @@ protected function whereSub($column, $operator, Closure $callback, $boolean)
* @param \Closure $callback
* @param string $boolean
* @param bool $not
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function whereExists(Closure $callback, $boolean = 'and', $not = false)
{
@@ -623,7 +755,7 @@ public function orWhereNotExists(Closure $callback)
* @param mixed $values
* @param string $boolean
* @param bool $not
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function whereIn($column, $values, $boolean = 'and', $not = false)
{
@@ -639,7 +771,7 @@ public function whereIn($column, $values, $boolean = 'and', $not = false)
$this->wheres[] = compact('type', 'column', 'values', 'boolean');
- $this->bindings = array_merge($this->bindings, $values);
+ $this->addBinding($values, 'where');
return $this;
}
@@ -688,7 +820,7 @@ public function orWhereNotIn($column, $values)
* @param \Closure $callback
* @param string $boolean
* @param bool $not
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
protected function whereInSub($column, Closure $callback, $boolean, $not)
{
@@ -712,7 +844,7 @@ protected function whereInSub($column, Closure $callback, $boolean, $not)
* @param string $column
* @param string $boolean
* @param bool $not
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function whereNull($column, $boolean = 'and', $not = false)
{
@@ -757,12 +889,87 @@ public function orWhereNotNull($column)
return $this->whereNotNull($column, 'or');
}
+ /**
+ * Add a "where date" statement to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param int $value
+ * @param string $boolean
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function whereDate($column, $operator, $value, $boolean = 'and')
+ {
+ return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean);
+ }
+
+ /**
+ * Add a "where day" statement to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param int $value
+ * @param string $boolean
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function whereDay($column, $operator, $value, $boolean = 'and')
+ {
+ return $this->addDateBasedWhere('Day', $column, $operator, $value, $boolean);
+ }
+
+ /**
+ * Add a "where month" statement to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param int $value
+ * @param string $boolean
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function whereMonth($column, $operator, $value, $boolean = 'and')
+ {
+ return $this->addDateBasedWhere('Month', $column, $operator, $value, $boolean);
+ }
+
+ /**
+ * Add a "where year" statement to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param int $value
+ * @param string $boolean
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function whereYear($column, $operator, $value, $boolean = 'and')
+ {
+ return $this->addDateBasedWhere('Year', $column, $operator, $value, $boolean);
+ }
+
+ /**
+ * Add a date based (year, month, day) statement to the query.
+ *
+ * @param string $type
+ * @param string $column
+ * @param string $operator
+ * @param int $value
+ * @param string $boolean
+ * @return $this
+ */
+ protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
+ {
+ $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
+
+ $this->addBinding($value, 'where');
+
+ return $this;
+ }
+
/**
* Handles dynamic "where" clauses to the query.
*
* @param string $method
* @param string $parameters
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function dynamicWhere($method, $parameters)
{
@@ -823,12 +1030,15 @@ protected function addDynamic($segment, $connector, $parameters, $index)
/**
* Add a "group by" clause to the query.
*
- * @param dynamic $columns
- * @return \Illuminate\Database\Query\Builder|static
+ * @param array|string $column,...
+ * @return $this
*/
public function groupBy()
{
- $this->groups = array_merge((array) $this->groups, func_get_args());
+ foreach (func_get_args() as $arg)
+ {
+ $this->groups = array_merge((array) $this->groups, is_array($arg) ? $arg : [$arg]);
+ }
return $this;
}
@@ -839,26 +1049,40 @@ public function groupBy()
* @param string $column
* @param string $operator
* @param string $value
- * @return \Illuminate\Database\Query\Builder|static
+ * @param string $boolean
+ * @return $this
*/
- public function having($column, $operator = null, $value = null)
+ public function having($column, $operator = null, $value = null, $boolean = 'and')
{
$type = 'basic';
- $this->havings[] = compact('type', 'column', 'operator', 'value');
+ $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');
- $this->bindings[] = $value;
+ $this->addBinding($value, 'having');
return $this;
}
+ /**
+ * Add a "or having" clause to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param string $value
+ * @return \Illuminate\Database\Query\Builder|static
+ */
+ public function orHaving($column, $operator = null, $value = null)
+ {
+ return $this->having($column, $operator, $value, 'or');
+ }
+
/**
* Add a raw having clause to the query.
*
* @param string $sql
* @param array $bindings
* @param string $boolean
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function havingRaw($sql, array $bindings = array(), $boolean = 'and')
{
@@ -866,7 +1090,7 @@ public function havingRaw($sql, array $bindings = array(), $boolean = 'and')
$this->havings[] = compact('type', 'sql', 'boolean');
- $this->bindings = array_merge($this->bindings, $bindings);
+ $this->addBinding($bindings, 'having');
return $this;
}
@@ -888,13 +1112,14 @@ public function orHavingRaw($sql, array $bindings = array())
*
* @param string $column
* @param string $direction
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function orderBy($column, $direction = 'asc')
{
+ $property = $this->unions ? 'unionOrders' : 'orders';
$direction = strtolower($direction) == 'asc' ? 'asc' : 'desc';
- $this->orders[] = compact('column', 'direction');
+ $this->{$property}[] = compact('column', 'direction');
return $this;
}
@@ -921,12 +1146,12 @@ public function oldest($column = 'created_at')
return $this->orderBy($column, 'asc');
}
- /*
+ /**
* Add a raw "order by" clause to the query.
*
* @param string $sql
* @param array $bindings
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function orderByRaw($sql, $bindings = array())
{
@@ -934,7 +1159,7 @@ public function orderByRaw($sql, $bindings = array())
$this->orders[] = compact('type', 'sql');
- $this->bindings = array_merge($this->bindings, $bindings);
+ $this->addBinding($bindings, 'order');
return $this;
}
@@ -943,11 +1168,13 @@ public function orderByRaw($sql, $bindings = array())
* Set the "offset" value of the query.
*
* @param int $value
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function offset($value)
{
- $this->offset = $value;
+ $property = $this->unions ? 'unionOffset' : 'offset';
+
+ $this->$property = max(0, $value);
return $this;
}
@@ -967,11 +1194,13 @@ public function skip($value)
* Set the "limit" value of the query.
*
* @param int $value
- * @return \Illuminate\Database\Query\Builder|static
+ * @return $this
*/
public function limit($value)
{
- if ($value > 0) $this->limit = $value;
+ $property = $this->unions ? 'unionLimit' : 'limit';
+
+ if ($value > 0) $this->$property = $value;
return $this;
}
@@ -1003,7 +1232,7 @@ public function forPage($page, $perPage = 15)
* Add a union statement to the query.
*
* @param \Illuminate\Database\Query\Builder|\Closure $query
- * @param bool $all
+ * @param bool $all
* @return \Illuminate\Database\Query\Builder|static
*/
public function union($query, $all = false)
@@ -1032,8 +1261,8 @@ public function unionAll($query)
/**
* Lock the selected rows in the table.
*
- * @param bool $update
- * @return \Illuminate\Database\Query\Builder
+ * @param bool $value
+ * @return $this
*/
public function lock($value = true)
{
@@ -1072,46 +1301,6 @@ public function toSql()
return $this->grammar->compileSelect($this);
}
- /**
- * Indicate that the query results should be cached.
- *
- * @param \Carbon\Carbon|\Datetime|int $minutes
- * @param string $key
- * @return \Illuminate\Database\Query\Builder|static
- */
- public function remember($minutes, $key = null)
- {
- list($this->cacheMinutes, $this->cacheKey) = array($minutes, $key);
-
- return $this;
- }
-
- /**
- * Indicate that the query results should be cached forever.
- *
- * @param string $key
- * @return \Illuminate\Database\Query\Builder|static
- */
- public function rememberForever($key = null)
- {
- list($this->cacheMinutes, $this->cacheKey) = array(-1, $key);
-
- return $this;
- }
-
- /**
- * Indicate that the results, if cached, should use the given cache tags.
- *
- * @param array|dynamic $cacheTags
- * @return \Illuminate\Database\Query\Builder|static
- */
- public function cacheTags($cacheTags)
- {
- $this->cacheTags = $cacheTags;
-
- return $this;
- }
-
/**
* Execute a query for a single record by ID.
*
@@ -1158,8 +1347,6 @@ public function first($columns = array('*'))
*/
public function get($columns = array('*'))
{
- if ( ! is_null($this->cacheMinutes)) return $this->getCached($columns);
-
return $this->getFresh($columns);
}
@@ -1183,96 +1370,98 @@ public function getFresh($columns = array('*'))
*/
protected function runSelect()
{
- return $this->connection->select($this->toSql(), $this->bindings);
+ if ($this->useWritePdo)
+ {
+ return $this->connection->select($this->toSql(), $this->getBindings(), false);
+ }
+
+ return $this->connection->select($this->toSql(), $this->getBindings());
}
/**
- * Execute the query as a cached "select" statement.
+ * Paginate the given query into a simple paginator.
*
+ * @param int $perPage
* @param array $columns
- * @return array
+ * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
- public function getCached($columns = array('*'))
+ public function paginate($perPage = 15, $columns = ['*'])
{
- if (is_null($this->columns)) $this->columns = $columns;
-
- // If the query is requested ot be cached, we will cache it using a unique key
- // for this database connection and query statement, including the bindings
- // that are used on this query, providing great convenience when caching.
- list($key, $minutes) = $this->getCacheInfo();
+ $page = Paginator::resolveCurrentPage();
- $cache = $this->getCache();
+ $total = $this->getCountForPagination();
- $callback = $this->getCacheCallback($columns);
+ $results = $this->forPage($page, $perPage)->get($columns);
- // If the "minutes" value is less than zero, we will use that as the indicator
- // that the value should be remembered values should be stored indefinitely
- // and if we have minutes we will use the typical remember function here.
- if ($minutes < 0)
- {
- return $cache->rememberForever($key, $callback);
- }
- else
- {
- return $cache->remember($key, $minutes, $callback);
- }
+ return new LengthAwarePaginator($results, $total, $perPage, $page, [
+ 'path' => Paginator::resolveCurrentPath()
+ ]);
}
/**
- * Get the cache object with tags assigned, if applicable.
+ * Get a paginator only supporting simple next and previous links.
*
- * @return \Illuminate\Cache\CacheManager
+ * This is more efficient on larger data-sets, etc.
+ *
+ * @param int $perPage
+ * @param array $columns
+ * @return \Illuminate\Contracts\Pagination\Paginator
*/
- protected function getCache()
+ public function simplePaginate($perPage = 15, $columns = ['*'])
{
- $cache = $this->connection->getCacheManager();
+ $page = Paginator::resolveCurrentPage();
- return $this->cacheTags ? $cache->tags($this->cacheTags) : $cache;
- }
+ $this->skip(($page - 1) * $perPage)->take($perPage + 1);
- /**
- * Get the cache key and cache minutes as an array.
- *
- * @return array
- */
- protected function getCacheInfo()
- {
- return array($this->getCacheKey(), $this->cacheMinutes);
+ return new Paginator($this->get($columns), $page, $perPage, [
+ 'path' => Paginator::resolveCurrentPath()
+ ]);
}
/**
- * Get a unique cache key for the complete query.
+ * Get the count of the total records for the paginator.
*
- * @return string
+ * @return int
*/
- public function getCacheKey()
+ public function getCountForPagination()
{
- return $this->cacheKey ?: $this->generateCacheKey();
+ $this->backupFieldsForCount();
+
+ $total = $this->count();
+
+ $this->restoreFieldsForCount();
+
+ return $total;
}
/**
- * Generate the unique cache key for the query.
+ * Backup some fields for the pagination count.
*
- * @return string
+ * @return void
*/
- public function generateCacheKey()
+ protected function backupFieldsForCount()
{
- $name = $this->connection->getName();
+ foreach (['orders', 'limit', 'offset'] as $field)
+ {
+ $this->backups[$field] = $this->{$field};
- return md5($name.$this->toSql().serialize($this->bindings));
+ $this->{$field} = null;
+ }
}
/**
- * Get the Closure callback used when caching queries.
+ * Restore some fields after the pagination count.
*
- * @param array $columns
- * @return \Closure
+ * @return void
*/
- protected function getCacheCallback($columns)
+ protected function restoreFieldsForCount()
{
- $me = $this;
+ foreach (['orders', 'limit', 'offset'] as $field)
+ {
+ $this->{$field} = $this->backups[$field];
+ }
- return function() use ($me, $columns) { return $me->getFresh($columns); };
+ $this->backups = [];
}
/**
@@ -1282,7 +1471,7 @@ protected function getCacheCallback($columns)
* @param callable $callback
* @return void
*/
- public function chunk($count, $callback)
+ public function chunk($count, callable $callback)
{
$results = $this->forPage($page = 1, $count)->get();
@@ -1291,7 +1480,10 @@ public function chunk($count, $callback)
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
- call_user_func($callback, $results);
+ if (call_user_func($callback, $results) === false)
+ {
+ break;
+ }
$page++;
@@ -1310,24 +1502,9 @@ public function lists($column, $key = null)
{
$columns = $this->getListSelect($column, $key);
- // First we will just get all of the column values for the record result set
- // then we can associate those values with the column if it was specified
- // otherwise we can just give these values back without a specific key.
$results = new Collection($this->get($columns));
- $values = $results->fetch($columns[0])->all();
-
- // If a key was specified and we have results, we will go ahead and combine
- // the values with the keys of all of the records so that the values can
- // be accessed by the key of the rows instead of simply being numeric.
- if ( ! is_null($key) && count($results) > 0)
- {
- $keys = $results->fetch($key)->all();
-
- return array_combine($keys, $values);
- }
-
- return $values;
+ return $results->lists($columns[0], array_get($columns, 1));
}
/**
@@ -1344,12 +1521,12 @@ protected function getListSelect($column, $key)
// If the selected column contains a "dot", we will remove it so that the list
// operation can run normally. Specifying the table is not needed, since we
// really want the names of the columns as it is in this resulting array.
- if (($dot = strpos($select[0], '.')) !== false)
+ return array_map(function($column)
{
- $select[0] = substr($select[0], $dot + 1);
- }
+ $dot = strpos($column, '.');
- return $select;
+ return $dot === false ? $column : substr($column, $dot + 1);
+ }, $select);
}
/**
@@ -1366,110 +1543,6 @@ public function implode($column, $glue = null)
return implode($glue, $this->lists($column));
}
- /**
- * Get a paginator for the "select" statement.
- *
- * @param int $perPage
- * @param array $columns
- * @return \Illuminate\Pagination\Paginator
- */
- public function paginate($perPage = 15, $columns = array('*'))
- {
- $paginator = $this->connection->getPaginator();
-
- if (isset($this->groups))
- {
- return $this->groupedPaginate($paginator, $perPage, $columns);
- }
- else
- {
- return $this->ungroupedPaginate($paginator, $perPage, $columns);
- }
- }
-
- /**
- * Create a paginator for a grouped pagination 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);
-
- return $this->buildRawPaginator($paginator, $results, $perPage);
- }
-
- /**
- * Build a paginator instance from a raw result array.
- *
- * @param \Illuminate\Pagination\Environment $paginator
- * @param array $results
- * @param int $perPage
- * @return \Illuminate\Pagination\Paginator
- */
- public function buildRawPaginator($paginator, $results, $perPage)
- {
- // For queries which have a group by, we will actually retrieve the entire set
- // of rows from the table and "slice" them via PHP. This is inefficient and
- // the developer must be aware of this behavior; however, it's an option.
- $start = ($paginator->getCurrentPage() - 1) * $perPage;
-
- $sliced = array_slice($results, $start, $perPage);
-
- return $paginator->make($sliced, count($results), $perPage);
- }
-
- /**
- * Create a paginator for an un-grouped pagination statement.
- *
- * @param \Illuminate\Pagination\Environment $paginator
- * @param int $perPage
- * @param array $columns
- * @return \Illuminate\Pagination\Paginator
- */
- protected function ungroupedPaginate($paginator, $perPage, $columns)
- {
- $total = $this->getPaginationCount();
-
- // Once we have the total number of records to be paginated, we can grab the
- // current page and the result array. Then we are ready to create a brand
- // new Paginator instances for the results which will create the links.
- $page = $paginator->getCurrentPage($total);
-
- $results = $this->forPage($page, $perPage)->get($columns);
-
- return $paginator->make($results, $total, $perPage);
- }
-
- /**
- * Get the count of the total records for pagination.
- *
- * @return int
- */
- public function getPaginationCount()
- {
- list($orders, $this->orders) = array($this->orders, null);
-
- $columns = $this->columns;
-
- // Because some database engines may throw errors if we leave the ordering
- // statements on the query, we will "back them up" and remove them from
- // the query. Once we have the count we will put them back onto this.
- $total = $this->count();
-
- $this->orders = $orders;
-
- // Once the query is run we need to put the old select columns back on the
- // instance so that the select query will run properly. Otherwise, they
- // will be cleared, then the query will fire with all of the columns.
- $this->columns = $columns;
-
- return $total;
- }
-
/**
* Determine if any rows exist for the current query.
*
@@ -1477,18 +1550,29 @@ public function getPaginationCount()
*/
public function exists()
{
- return $this->count() > 0;
+ $limit = $this->limit;
+
+ $result = $this->limit(1)->count() > 0;
+
+ $this->limit($limit);
+
+ return $result;
}
/**
* Retrieve the "count" result of the query.
*
- * @param string $column
+ * @param string $columns
* @return int
*/
- public function count($column = '*')
+ public function count($columns = '*')
{
- return $this->aggregate(__FUNCTION__, array($column));
+ if ( ! is_array($columns))
+ {
+ $columns = array($columns);
+ }
+
+ return (int) $this->aggregate(__FUNCTION__, $columns);
}
/**
@@ -1521,7 +1605,9 @@ public function max($column)
*/
public function sum($column)
{
- return $this->aggregate(__FUNCTION__, array($column));
+ $result = $this->aggregate(__FUNCTION__, array($column));
+
+ return $result ?: 0;
}
/**
@@ -1546,16 +1632,20 @@ public function aggregate($function, $columns = array('*'))
{
$this->aggregate = compact('function', 'columns');
+ $previousColumns = $this->columns;
+
$results = $this->get($columns);
// Once we have executed the query, we will reset the aggregate property so
// that more select queries can be executed against the database without
// the aggregate value getting in the way when the grammar builds it.
- $this->columns = null; $this->aggregate = null;
+ $this->aggregate = null;
+
+ $this->columns = $previousColumns;
if (isset($results[0]))
{
- $result = (array) $results[0];
+ $result = array_change_key_case((array) $results[0]);
return $result['aggregate'];
}
@@ -1595,7 +1685,10 @@ public function insert(array $values)
foreach ($values as $record)
{
- $bindings = array_merge($bindings, array_values($record));
+ foreach ($record as $value)
+ {
+ $bindings[] = $value;
+ }
}
$sql = $this->grammar->compileInsert($this, $values);
@@ -1632,7 +1725,7 @@ public function insertGetId(array $values, $sequence = null)
*/
public function update(array $values)
{
- $bindings = array_values(array_merge($values, $this->bindings));
+ $bindings = array_values(array_merge($values, $this->getBindings()));
$sql = $this->grammar->compileUpdate($this, $values);
@@ -1688,7 +1781,7 @@ public function delete($id = null)
$sql = $this->grammar->compileDelete($this);
- return $this->connection->delete($sql, $this->bindings);
+ return $this->connection->delete($sql, $this->getBindings());
}
/**
@@ -1725,7 +1818,7 @@ public function mergeWheres($wheres, $bindings)
{
$this->wheres = array_merge((array) $this->wheres, (array) $wheres);
- $this->bindings = array_values(array_merge($this->bindings, (array) $bindings));
+ $this->bindings['where'] = array_values(array_merge($this->bindings['where'], (array) $bindings));
}
/**
@@ -1754,11 +1847,21 @@ public function raw($value)
}
/**
- * Get the current query value bindings.
+ * Get the current query value bindings in a flattened array.
*
* @return array
*/
public function getBindings()
+ {
+ return array_flatten($this->bindings);
+ }
+
+ /**
+ * Get the raw array of bindings.
+ *
+ * @return array
+ */
+ public function getRawBindings()
{
return $this->bindings;
}
@@ -1766,12 +1869,20 @@ public function getBindings()
/**
* Set the bindings on the query builder.
*
- * @param array $bindings
- * @return \Illuminate\Database\Query\Builder
+ * @param array $bindings
+ * @param string $type
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
*/
- public function setBindings(array $bindings)
+ public function setBindings(array $bindings, $type = 'where')
{
- $this->bindings = $bindings;
+ if ( ! array_key_exists($type, $this->bindings))
+ {
+ throw new InvalidArgumentException("Invalid binding type: {$type}.");
+ }
+
+ $this->bindings[$type] = $bindings;
return $this;
}
@@ -1779,12 +1890,27 @@ public function setBindings(array $bindings)
/**
* Add a binding to the query.
*
- * @param mixed $value
- * @return \Illuminate\Database\Query\Builder
+ * @param mixed $value
+ * @param string $type
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
*/
- public function addBinding($value)
+ public function addBinding($value, $type = 'where')
{
- $this->bindings[] = $value;
+ if ( ! array_key_exists($type, $this->bindings))
+ {
+ throw new InvalidArgumentException("Invalid binding type: {$type}.");
+ }
+
+ if (is_array($value))
+ {
+ $this->bindings[$type] = array_values(array_merge($this->bindings[$type], $value));
+ }
+ else
+ {
+ $this->bindings[$type][] = $value;
+ }
return $this;
}
@@ -1793,11 +1919,11 @@ public function addBinding($value)
* Merge an array of bindings into our bindings.
*
* @param \Illuminate\Database\Query\Builder $query
- * @return \Illuminate\Database\Query\Builder
+ * @return $this
*/
public function mergeBindings(Builder $query)
{
- $this->bindings = array_values(array_merge($this->bindings, $query->bindings));
+ $this->bindings = array_merge_recursive($this->bindings, $query->bindings);
return $this;
}
@@ -1832,6 +1958,18 @@ public function getGrammar()
return $this->grammar;
}
+ /**
+ * Use the write pdo for query.
+ *
+ * @return $this
+ */
+ public function useWritePdo()
+ {
+ $this->useWritePdo = true;
+
+ return $this;
+ }
+
/**
* Handle dynamic method calls into the method.
*
@@ -1850,7 +1988,7 @@ public function __call($method, $parameters)
$className = get_class($this);
- throw new \BadMethodCallException("Call to undefined method {$className}::{$method}()");
+ throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
}
}
diff --git a/Query/Expression.php b/Query/Expression.php
index 82cdba6b8..68d223656 100755
--- a/Query/Expression.php
+++ b/Query/Expression.php
@@ -40,4 +40,4 @@ public function __toString()
return (string) $this->getValue();
}
-}
\ No newline at end of file
+}
diff --git a/Query/Grammars/Grammar.php b/Query/Grammars/Grammar.php
index e5e2cb688..6998370bc 100755
--- a/Query/Grammars/Grammar.php
+++ b/Query/Grammars/Grammar.php
@@ -5,13 +5,6 @@
class Grammar extends BaseGrammar {
- /**
- * The keyword identifier wrapper format.
- *
- * @var string
- */
- protected $wrapper = '"%s"';
-
/**
* The components that make up a select clause.
*
@@ -135,6 +128,8 @@ protected function compileJoins(Builder $query, $joins)
{
$sql = array();
+ $query->setBindings(array(), 'join');
+
foreach ($joins as $join)
{
$table = $this->wrapTable($join->table);
@@ -149,6 +144,11 @@ protected function compileJoins(Builder $query, $joins)
$clauses[] = $this->compileJoinConstraint($clause);
}
+ foreach ($join->bindings as $binding)
+ {
+ $query->addBinding($binding, 'join');
+ }
+
// 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.
@@ -306,6 +306,8 @@ protected function whereNotExists(Builder $query, $where)
*/
protected function whereIn(Builder $query, $where)
{
+ if (empty($where['values'])) return '0 = 1';
+
$values = $this->parameterize($where['values']);
return $this->wrap($where['column']).' in ('.$values.')';
@@ -320,6 +322,8 @@ protected function whereIn(Builder $query, $where)
*/
protected function whereNotIn(Builder $query, $where)
{
+ if (empty($where['values'])) return '1 = 1';
+
$values = $this->parameterize($where['values']);
return $this->wrap($where['column']).' not in ('.$values.')';
@@ -377,6 +381,69 @@ protected function whereNotNull(Builder $query, $where)
return $this->wrap($where['column']).' is not null';
}
+ /**
+ * Compile a "where date" clause.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function whereDate(Builder $query, $where)
+ {
+ return $this->dateBasedWhere('date', $query, $where);
+ }
+
+ /**
+ * Compile a "where day" clause.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function whereDay(Builder $query, $where)
+ {
+ return $this->dateBasedWhere('day', $query, $where);
+ }
+
+ /**
+ * Compile a "where month" clause.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function whereMonth(Builder $query, $where)
+ {
+ return $this->dateBasedWhere('month', $query, $where);
+ }
+
+ /**
+ * Compile a "where year" clause.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function whereYear(Builder $query, $where)
+ {
+ return $this->dateBasedWhere('year', $query, $where);
+ }
+
+ /**
+ * Compile a date based where clause.
+ *
+ * @param string $type
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function dateBasedWhere($type, Builder $query, $where)
+ {
+ $value = $this->parameter($where['value']);
+
+ return $type.'('.$this->wrap($where['column']).') '.$where['operator'].' '.$value;
+ }
+
/**
* Compile a raw where clause.
*
@@ -410,11 +477,9 @@ protected function compileGroups(Builder $query, $groups)
*/
protected function compileHavings(Builder $query, $havings)
{
- $me = $this;
-
$sql = implode(' ', array_map(array($this, 'compileHaving'), $havings));
- return 'having '.preg_replace('/and /', '', $sql, 1);
+ return 'having '.preg_replace('/and |or /', '', $sql, 1);
}
/**
@@ -448,7 +513,7 @@ protected function compileBasicHaving($having)
$parameter = $this->parameter($having['value']);
- return 'and '.$column.' '.$having['operator'].' '.$parameter;
+ return $having['boolean'].' '.$column.' '.$having['operator'].' '.$parameter;
}
/**
@@ -460,13 +525,11 @@ protected function compileBasicHaving($having)
*/
protected function compileOrders(Builder $query, $orders)
{
- $me = $this;
-
- return 'order by '.implode(', ', array_map(function($order) use ($me)
+ return 'order by '.implode(', ', array_map(function($order)
{
if (isset($order['sql'])) return $order['sql'];
- return $me->wrap($order['column']).' '.$order['direction'];
+ return $this->wrap($order['column']).' '.$order['direction'];
}
, $orders));
}
@@ -510,6 +573,21 @@ protected function compileUnions(Builder $query)
$sql .= $this->compileUnion($union);
}
+ if (isset($query->unionOrders))
+ {
+ $sql .= ' '.$this->compileOrders($query, $query->unionOrders);
+ }
+
+ if (isset($query->unionLimit))
+ {
+ $sql .= ' '.$this->compileLimit($query, $query->unionLimit);
+ }
+
+ if (isset($query->unionOffset))
+ {
+ $sql .= ' '.$this->compileOffset($query, $query->unionOffset);
+ }
+
return ltrim($sql);
}
@@ -619,7 +697,6 @@ public function compileUpdate(Builder $query, $values)
* Compile a delete statement into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
- * @param array $values
* @return string
*/
public function compileDelete(Builder $query)
@@ -679,4 +756,4 @@ protected function removeLeadingBoolean($value)
return preg_replace('/and |or /', '', $value, 1);
}
-}
\ No newline at end of file
+}
diff --git a/Query/Grammars/MySqlGrammar.php b/Query/Grammars/MySqlGrammar.php
index c801cf84d..b068e2b0d 100755
--- a/Query/Grammars/MySqlGrammar.php
+++ b/Query/Grammars/MySqlGrammar.php
@@ -4,13 +4,6 @@
class MySqlGrammar extends Grammar {
- /**
- * The keyword identifier wrapper format.
- *
- * @var string
- */
- protected $wrapper = '`%s`';
-
/**
* The components that make up a select clause.
*
@@ -99,4 +92,39 @@ public function compileUpdate(Builder $query, $values)
return rtrim($sql);
}
-}
\ No newline at end of file
+ /**
+ * Compile a delete statement into SQL.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @return string
+ */
+ public function compileDelete(Builder $query)
+ {
+ $table = $this->wrapTable($query->from);
+
+ $where = is_array($query->wheres) ? $this->compileWheres($query) : '';
+
+ if (isset($query->joins))
+ {
+ $joins = ' '.$this->compileJoins($query, $query->joins);
+
+ return trim("delete $table from {$table}{$joins} $where");
+ }
+
+ return trim("delete from $table $where");
+ }
+
+ /**
+ * Wrap a single string in keyword identifiers.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function wrapValue($value)
+ {
+ if ($value === '*') return $value;
+
+ return '`'.str_replace('`', '``', $value).'`';
+ }
+
+}
diff --git a/Query/Grammars/PostgresGrammar.php b/Query/Grammars/PostgresGrammar.php
index 8c5aac0b1..7a8df9c4c 100755
--- a/Query/Grammars/PostgresGrammar.php
+++ b/Query/Grammars/PostgresGrammar.php
@@ -117,10 +117,8 @@ protected function compileUpdateWheres(Builder $query)
{
return 'where '.$this->removeLeadingBoolean($joinWhere);
}
- else
- {
- return $baseWhere.' '.$joinWhere;
- }
+
+ return $baseWhere.' '.$joinWhere;
}
/**
@@ -173,4 +171,4 @@ public function compileTruncate(Builder $query)
return array('truncate '.$this->wrapTable($query->from).' restart identity' => array());
}
-}
\ No newline at end of file
+}
diff --git a/Query/Grammars/SQLiteGrammar.php b/Query/Grammars/SQLiteGrammar.php
index aac1ad1ec..01558d35c 100755
--- a/Query/Grammars/SQLiteGrammar.php
+++ b/Query/Grammars/SQLiteGrammar.php
@@ -74,4 +74,57 @@ public function compileTruncate(Builder $query)
return $sql;
}
+ /**
+ * Compile a "where day" clause.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function whereDay(Builder $query, $where)
+ {
+ return $this->dateBasedWhere('%d', $query, $where);
+ }
+
+ /**
+ * Compile a "where month" clause.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function whereMonth(Builder $query, $where)
+ {
+ return $this->dateBasedWhere('%m', $query, $where);
+ }
+
+ /**
+ * Compile a "where year" clause.
+ *
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function whereYear(Builder $query, $where)
+ {
+ return $this->dateBasedWhere('%Y', $query, $where);
+ }
+
+ /**
+ * Compile a date based where clause.
+ *
+ * @param string $type
+ * @param \Illuminate\Database\Query\Builder $query
+ * @param array $where
+ * @return string
+ */
+ protected function dateBasedWhere($type, Builder $query, $where)
+ {
+ $value = str_pad($where['value'], 2, '0', STR_PAD_LEFT);
+
+ $value = $this->parameter($value);
+
+ return 'strftime(\''.$type.'\', '.$this->wrap($where['column']).') '.$where['operator'].' '.$value;
+ }
+
}
diff --git a/Query/Grammars/SqlServerGrammar.php b/Query/Grammars/SqlServerGrammar.php
index deda0b69c..24fd428ec 100755
--- a/Query/Grammars/SqlServerGrammar.php
+++ b/Query/Grammars/SqlServerGrammar.php
@@ -15,13 +15,6 @@ class SqlServerGrammar extends Grammar {
'&', '&=', '|', '|=', '^', '^=',
);
- /**
- * The keyword identifier wrapper format.
- *
- * @var string
- */
- protected $wrapper = '[%s]';
-
/**
* Compile a select query into SQL.
*
@@ -59,7 +52,7 @@ protected function compileColumns(Builder $query, $columns)
// If there is a limit on the query, but not an offset, we will add the top
// clause to the query, which serves as a "limit" type clause within the
// SQL Server system similar to the limit keywords available in MySQL.
- if ($query->limit > 0 and $query->offset <= 0)
+ if ($query->limit > 0 && $query->offset <= 0)
{
$select .= 'top '.$query->limit.' ';
}
@@ -84,10 +77,8 @@ protected function compileFrom(Builder $query, $table)
{
return $from.' with(rowlock,'.($query->lock ? 'updlock,' : '').'holdlock)';
}
- else
- {
- return $from;
- }
+
+ return $from;
}
/**
@@ -119,8 +110,6 @@ protected function compileAnsiOffset(Builder $query, $components)
// Next we need to calculate the constraints that should be placed on the query
// to get the right offset and limit from our query but if there is no limit
// set we will just handle the offset only since that is all that matters.
- $start = $query->offset + 1;
-
$constraint = $this->compileRowConstraint($query);
$sql = $this->concatenate($components);
@@ -219,4 +208,17 @@ public function getDateFormat()
return 'Y-m-d H:i:s.000';
}
-}
\ No newline at end of file
+ /**
+ * Wrap a single string in keyword identifiers.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function wrapValue($value)
+ {
+ if ($value === '*') return $value;
+
+ return '['.str_replace(']', ']]', $value).']';
+ }
+
+}
diff --git a/Query/JoinClause.php b/Query/JoinClause.php
index 662bea7bf..cea79ed4c 100755
--- a/Query/JoinClause.php
+++ b/Query/JoinClause.php
@@ -2,13 +2,6 @@
class JoinClause {
- /**
- * The query builder instance.
- *
- * @var \Illuminate\Database\Query\Builder
- */
- public $query;
-
/**
* The type of join being performed.
*
@@ -30,18 +23,23 @@ class JoinClause {
*/
public $clauses = array();
+ /**
+ * The "on" bindings for the join.
+ *
+ * @var array
+ */
+ public $bindings = array();
+
/**
* Create a new join clause instance.
*
- * @param \Illuminate\Database\Query\Builder $query
* @param string $type
* @param string $table
* @return void
*/
- public function __construct(Builder $query, $type, $table)
+ public function __construct($type, $table)
{
$this->type = $type;
- $this->query = $query;
$this->table = $table;
}
@@ -53,13 +51,13 @@ public function __construct(Builder $query, $type, $table)
* @param string $second
* @param string $boolean
* @param bool $where
- * @return \Illuminate\Database\Query\JoinClause
+ * @return $this
*/
public function on($first, $operator, $second, $boolean = 'and', $where = false)
{
$this->clauses[] = compact('first', 'operator', 'second', 'boolean', 'where');
- if ($where) $this->query->addBinding($second);
+ if ($where) $this->bindings[] = $second;
return $this;
}
@@ -97,7 +95,6 @@ public function where($first, $operator, $second, $boolean = 'and')
* @param string $first
* @param string $operator
* @param string $second
- * @param string $boolean
* @return \Illuminate\Database\Query\JoinClause
*/
public function orWhere($first, $operator, $second)
@@ -105,4 +102,16 @@ public function orWhere($first, $operator, $second)
return $this->on($first, $operator, $second, 'or', true);
}
-}
\ No newline at end of file
+ /**
+ * Add an "on where is null" clause to the join
+ *
+ * @param string $column
+ * @param string $boolean
+ * @return \Illuminate\Database\Query\JoinClause
+ */
+ public function whereNull($column, $boolean = 'and')
+ {
+ return $this->on($column, 'is', new Expression('null'), $boolean, false);
+ }
+
+}
diff --git a/Query/Processors/MySqlProcessor.php b/Query/Processors/MySqlProcessor.php
index 990810866..f77b41db2 100644
--- a/Query/Processors/MySqlProcessor.php
+++ b/Query/Processors/MySqlProcessor.php
@@ -1,7 +1,5 @@
column_name; }, $results);
+ return array_map(function($r) { $r = (object) $r; return $r->column_name; }, $results);
}
}
diff --git a/Query/Processors/PostgresProcessor.php b/Query/Processors/PostgresProcessor.php
index 10f39e7b4..665379df4 100755
--- a/Query/Processors/PostgresProcessor.php
+++ b/Query/Processors/PostgresProcessor.php
@@ -15,13 +15,15 @@ class PostgresProcessor extends Processor {
*/
public function processInsertGetId(Builder $query, $sql, $values, $sequence = null)
{
- $results = $query->getConnection()->select($sql, $values);
+ $results = $query->getConnection()->selectFromWriteConnection($sql, $values);
$sequence = $sequence ?: 'id';
$result = (array) $results[0];
- return (int) $result[$sequence];
+ $id = $result[$sequence];
+
+ return is_numeric($id) ? (int) $id : $id;
}
/**
@@ -32,7 +34,7 @@ public function processInsertGetId(Builder $query, $sql, $values, $sequence = nu
*/
public function processColumnListing($results)
{
- return array_values(array_map(function($r) { return $r->column_name; }, $results));
+ return array_values(array_map(function($r) { $r = (object) $r; return $r->column_name; }, $results));
}
-}
\ No newline at end of file
+}
diff --git a/Query/Processors/SQLiteProcessor.php b/Query/Processors/SQLiteProcessor.php
index dbd3e7e39..34493bf6e 100644
--- a/Query/Processors/SQLiteProcessor.php
+++ b/Query/Processors/SQLiteProcessor.php
@@ -10,7 +10,7 @@ class SQLiteProcessor extends Processor {
*/
public function processColumnListing($results)
{
- return array_values(array_map(function($r) { return $r->name; }, $results));
+ return array_values(array_map(function($r) { $r = (object) $r; return $r->name; }, $results));
}
}
diff --git a/Query/Processors/SqlServerProcessor.php b/Query/Processors/SqlServerProcessor.php
index b2d7de7f0..cfdb43263 100755
--- a/Query/Processors/SqlServerProcessor.php
+++ b/Query/Processors/SqlServerProcessor.php
@@ -33,4 +33,4 @@ public function processColumnListing($results)
return array_values(array_map(function($r) { return $r->name; }, $results));
}
-}
\ No newline at end of file
+}
diff --git a/QueryException.php b/QueryException.php
index 57822d19c..e3f9cf2fe 100644
--- a/QueryException.php
+++ b/QueryException.php
@@ -1,6 +1,8 @@
sql = $sql;
$this->bindings = $bindings;
$this->previous = $previous;
$this->code = $previous->getCode();
- $this->errorInfo = $previous->errorInfo;
$this->message = $this->formatMessage($sql, $bindings, $previous);
+
+ if ($previous instanceof PDOException)
+ {
+ $this->errorInfo = $previous->errorInfo;
+ }
}
/**
@@ -39,7 +47,7 @@ public function __construct($sql, array $bindings, $previous)
*
* @param string $sql
* @param array $bindings
- * @param \PDOException $previous
+ * @param \Exception $previous
* @return string
*/
protected function formatMessage($sql, $bindings, $previous)
@@ -67,4 +75,4 @@ public function getBindings()
return $this->bindings;
}
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index 5009fc098..ce8901ad1 100755
--- a/README.md
+++ b/README.md
@@ -27,9 +27,6 @@ use Illuminate\Events\Dispatcher;
use Illuminate\Container\Container;
$capsule->setEventDispatcher(new Dispatcher(new Container));
-// Set the cache manager instance used by connections... (optional)
-$capsule->setCacheManager(...);
-
// Make this Capsule instance available globally via static methods... (optional)
$capsule->setAsGlobal();
diff --git a/SQLiteConnection.php b/SQLiteConnection.php
index 5944bdc27..86603fc1e 100755
--- a/SQLiteConnection.php
+++ b/SQLiteConnection.php
@@ -46,4 +46,4 @@ protected function getDoctrineDriver()
return new DoctrineDriver;
}
-}
\ No newline at end of file
+}
diff --git a/Schema/Blueprint.php b/Schema/Blueprint.php
index ba0225ffe..0d91ac6ba 100755
--- a/Schema/Blueprint.php
+++ b/Schema/Blueprint.php
@@ -38,8 +38,8 @@ class Blueprint {
/**
* Create a new schema blueprint.
*
- * @param string $table
- * @param Closure $callback
+ * @param string $table
+ * @param \Closure|null $callback
* @return void
*/
public function __construct($table, Closure $callback = null)
@@ -103,11 +103,16 @@ public function toSql(Connection $connection, Grammar $grammar)
*/
protected function addImpliedCommands()
{
- if (count($this->columns) > 0 && ! $this->creating())
+ if (count($this->getAddedColumns()) > 0 && ! $this->creating())
{
array_unshift($this->commands, $this->createCommand('add'));
}
+ if (count($this->getChangedColumns()) > 0 && ! $this->creating())
+ {
+ array_unshift($this->commands, $this->createCommand('change'));
+ }
+
$this->addFluentIndexes();
}
@@ -360,6 +365,18 @@ public function bigIncrements($column)
return $this->unsignedBigInteger($column, true);
}
+ /**
+ * Create a new char column on the table.
+ *
+ * @param string $column
+ * @param int $length
+ * @return \Illuminate\Support\Fluent
+ */
+ public function char($column, $length = 255)
+ {
+ return $this->addColumn('char', $column, compact('length'));
+ }
+
/**
* Create a new string column on the table.
*
@@ -514,7 +531,6 @@ public function float($column, $total = 8, $places = 2)
* @param int|null $total
* @param int|null $places
* @return \Illuminate\Support\Fluent
- *
*/
public function double($column, $total = null, $places = null)
{
@@ -557,6 +573,17 @@ public function enum($column, array $allowed)
return $this->addColumn('enum', $column, compact('allowed'));
}
+ /**
+ * Create a new json column on the table.
+ *
+ * @param string $column
+ * @return \Illuminate\Support\Fluent
+ */
+ public function json($column)
+ {
+ return $this->addColumn('json', $column);
+ }
+
/**
* Create a new date column on the table.
*
@@ -628,11 +655,11 @@ public function timestamps()
/**
* Add a "deleted at" timestamp for the table.
*
- * @return void
+ * @return \Illuminate\Support\Fluent
*/
public function softDeletes()
{
- $this->timestamp('deleted_at')->nullable();
+ return $this->timestamp('deleted_at')->nullable();
}
/**
@@ -650,13 +677,26 @@ public function binary($column)
* Add the proper columns for a polymorphic table.
*
* @param string $name
+ * @param string|null $indexName
* @return void
*/
- public function morphs($name)
+ public function morphs($name, $indexName = null)
{
- $this->integer("{$name}_id");
+ $this->unsignedInteger("{$name}_id");
$this->string("{$name}_type");
+
+ $this->index(array("{$name}_id", "{$name}_type"), $indexName);
+ }
+
+ /**
+ * Adds the `remember_token` column to the table.
+ *
+ * @return \Illuminate\Support\Fluent
+ */
+ public function rememberToken()
+ {
+ return $this->string('remember_token', 100)->nullable();
}
/**
@@ -673,7 +713,7 @@ protected function dropIndexCommand($command, $type, $index)
// If the given "index" is actually an array of columns, the developer means
// to drop an index merely by specifying the columns involved without the
- // conventional name, so we will built the index name from the columns.
+ // conventional name, so we will build the index name from the columns.
if (is_array($index))
{
$columns = $index;
@@ -742,7 +782,7 @@ protected function addColumn($type, $name, array $parameters = array())
* Remove a column from the schema blueprint.
*
* @param string $name
- * @return \Illuminate\Database\Schema\Blueprint
+ * @return $this
*/
public function removeColumn($name)
{
@@ -791,7 +831,7 @@ public function getTable()
}
/**
- * Get the columns that should be added.
+ * Get the columns on the blueprint.
*
* @return array
*/
@@ -810,4 +850,30 @@ public function getCommands()
return $this->commands;
}
+ /**
+ * Get the columns on the blueprint that should be added.
+ *
+ * @return array
+ */
+ public function getAddedColumns()
+ {
+ return array_filter($this->columns, function($column)
+ {
+ return !$column->change;
+ });
+ }
+
+ /**
+ * Get the columns on the blueprint that should be changed.
+ *
+ * @return array
+ */
+ public function getChangedColumns()
+ {
+ return array_filter($this->columns, function($column)
+ {
+ return !!$column->change;
+ });
+ }
+
}
diff --git a/Schema/Builder.php b/Schema/Builder.php
index 5afc5d352..64a129c3b 100755
--- a/Schema/Builder.php
+++ b/Schema/Builder.php
@@ -19,6 +19,13 @@ class Builder {
*/
protected $grammar;
+ /**
+ * The Blueprint resolver callback.
+ *
+ * @var \Closure
+ */
+ protected $resolver;
+
/**
* Create a new database Schema manager.
*
@@ -66,7 +73,7 @@ public function hasColumn($table, $column)
* @param string $table
* @return array
*/
- protected function getColumnListing($table)
+ public function getColumnListing($table)
{
$table = $this->connection->getTablePrefix().$table;
@@ -78,8 +85,8 @@ protected function getColumnListing($table)
/**
* Modify a table on the schema.
*
- * @param string $table
- * @param Closure $callback
+ * @param string $table
+ * @param \Closure $callback
* @return \Illuminate\Database\Schema\Blueprint
*/
public function table($table, Closure $callback)
@@ -90,8 +97,8 @@ public function table($table, Closure $callback)
/**
* Create a new table on the schema.
*
- * @param string $table
- * @param Closure $callback
+ * @param string $table
+ * @param \Closure $callback
* @return \Illuminate\Database\Schema\Blueprint
*/
public function create($table, Closure $callback)
@@ -165,12 +172,17 @@ protected function build(Blueprint $blueprint)
/**
* Create a new command set with a Closure.
*
- * @param string $table
- * @param Closure $callback
+ * @param string $table
+ * @param \Closure|null $callback
* @return \Illuminate\Database\Schema\Blueprint
*/
protected function createBlueprint($table, Closure $callback = null)
{
+ if (isset($this->resolver))
+ {
+ return call_user_func($this->resolver, $table, $callback);
+ }
+
return new Blueprint($table, $callback);
}
@@ -188,7 +200,7 @@ public function getConnection()
* Set the database connection instance.
*
* @param \Illuminate\Database\Connection
- * @return \Illuminate\Database\Schema\Builder
+ * @return $this
*/
public function setConnection(Connection $connection)
{
@@ -197,4 +209,15 @@ public function setConnection(Connection $connection)
return $this;
}
-}
\ No newline at end of file
+ /**
+ * Set the Schema Blueprint resolver callback.
+ *
+ * @param \Closure $resolver
+ * @return void
+ */
+ public function blueprintResolver(Closure $resolver)
+ {
+ $this->resolver = $resolver;
+ }
+
+}
diff --git a/Schema/Grammars/Grammar.php b/Schema/Grammars/Grammar.php
index 7d7424674..f0c7688b4 100755
--- a/Schema/Grammars/Grammar.php
+++ b/Schema/Grammars/Grammar.php
@@ -1,9 +1,12 @@
getDoctrineSchemaManager();
- $column = $connection->getDoctrineColumn($blueprint->getTable(), $command->from);
+ $table = $this->getTablePrefix().$blueprint->getTable();
+
+ $column = $connection->getDoctrineColumn($table, $command->from);
$tableDiff = $this->getRenamedDiff($blueprint, $command, $column, $schema);
@@ -106,14 +111,14 @@ public function compileForeign(Blueprint $blueprint, Fluent $command)
/**
* Compile the blueprint's column definitions.
*
- * @param \Illuminate\Database\Schema\Blueprint $blueprint
+ * @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return array
*/
protected function getColumns(Blueprint $blueprint)
{
$columns = array();
- foreach ($blueprint->getColumns() as $column)
+ foreach ($blueprint->getAddedColumns() as $column)
{
// Each of the column types have their own compiler functions which are tasked
// with turning the column definition into its SQL format for this platform
@@ -151,6 +156,7 @@ protected function addModifiers($sql, Blueprint $blueprint, Fluent $column)
* Get the primary key command if it exists on the blueprint.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
+ * @param string $name
* @return \Illuminate\Support\Fluent|null
*/
protected function getCommandByName(Blueprint $blueprint, $name)
@@ -219,16 +225,13 @@ public function wrapTable($table)
}
/**
- * Wrap a value in keyword identifiers.
- *
- * @param string $value
- * @return string
+ * {@inheritdoc}
*/
- public function wrap($value)
+ public function wrap($value, $prefixAlias = false)
{
if ($value instanceof Fluent) $value = $value->name;
- return parent::wrap($value);
+ return parent::wrap($value, $prefixAlias);
}
/**
@@ -241,7 +244,7 @@ protected function getDefaultValue($value)
{
if ($value instanceof Expression) return $value;
- if (is_bool($value)) return "'".intval($value)."'";
+ if (is_bool($value)) return "'".(int) $value."'";
return "'".strval($value)."'";
}
@@ -255,11 +258,175 @@ protected function getDefaultValue($value)
*/
protected function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema)
{
- $tableDiff = new TableDiff($blueprint->getTable());
+ $table = $this->getTablePrefix().$blueprint->getTable();
+
+ $tableDiff = new TableDiff($table);
- $tableDiff->fromTable = $schema->listTableDetails($blueprint->getTable());
+ $tableDiff->fromTable = $schema->listTableDetails($table);
return $tableDiff;
}
-}
\ No newline at end of file
+ /**
+ * Compile a change column command into a series of SQL statements.
+ *
+ * @param \Illuminate\Database\Schema\Blueprint $blueprint
+ * @param \Illuminate\Support\Fluent $command
+ * @param \Illuminate\Database\Connection $connection
+ * @return array
+ */
+ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection)
+ {
+ $schema = $connection->getDoctrineSchemaManager();
+
+ $tableDiff = $this->getChangedDiff($blueprint, $schema);
+
+ if ($tableDiff !== false)
+ {
+ return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff);
+ }
+
+ return [];
+ }
+
+ /**
+ * Get the Doctrine table difference for the given changes.
+ *
+ * @param \Illuminate\Database\Schema\Blueprint $blueprint
+ * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema
+ * @return \Doctrine\DBAL\Schema\TableDiff|bool
+ */
+ protected function getChangedDiff(Blueprint $blueprint, SchemaManager $schema)
+ {
+ $table = $schema->listTableDetails($this->getTablePrefix().$blueprint->getTable());
+
+ return (new Comparator)->diffTable($table, $this->getTableWithColumnChanges($blueprint, $table));
+ }
+
+ /**
+ * Get a copy of the given Doctrine table after making the column changes.
+ *
+ * @param \Illuminate\Database\Schema\Blueprint $blueprint
+ * @param \Doctrine\DBAL\Schema\Table $table
+ * @return \Doctrine\DBAL\Schema\TableDiff
+ */
+ protected function getTableWithColumnChanges(Blueprint $blueprint, Table $table)
+ {
+ $table = clone $table;
+
+ foreach($blueprint->getChangedColumns() as $fluent)
+ {
+ $column = $this->getDoctrineColumnForChange($table, $fluent);
+
+ // Here we will spin through each fluent column definition and map it to the proper
+ // Doctrine column definitions, which is necessasry because Laravel and Doctrine
+ // use some different terminology for various column attributes on the tables.
+ foreach ($fluent->getAttributes() as $key => $value)
+ {
+ if ( ! is_null($option = $this->mapFluentOptionToDoctrine($key)))
+ {
+ if (method_exists($column, $method = 'set'.ucfirst($option)))
+ {
+ $column->{$method}($this->mapFluentValueToDoctrine($option, $value));
+ }
+ }
+ }
+ }
+
+ return $table;
+ }
+
+ /**
+ * Get the Doctrine column instance for a column change.
+ *
+ * @param \Doctrine\DBAL\Schema\Table $table
+ * @param \Illuminate\Support\Fluent $fluent
+ * @return \Doctrine\DBAL\Schema\Column
+ */
+ protected function getDoctrineColumnForChange(Table $table, Fluent $fluent)
+ {
+ return $table->changeColumn(
+ $fluent['name'], $this->getDoctrineColumnChangeOptions($fluent)
+ )->getColumn($fluent['name']);
+ }
+
+ /**
+ * Get the Doctrine column change options.
+ *
+ * @param \Illuminate\Support\Fluent $fluent
+ * @return array
+ */
+ protected function getDoctrineColumnChangeOptions(Fluent $fluent)
+ {
+ $options = ['type' => Type::getType($this->getType($fluent))];
+
+ if (in_array($fluent['type'], ['text', 'mediumText', 'longText']))
+ {
+ $options['length'] = $this->calculateDoctrineTextLength($fluent['type']);
+ }
+
+ return $options;
+ }
+
+ /**
+ * Calculate the proper column length to force the Doctrine text type.
+ *
+ * @param string $type
+ * @return int
+ */
+ protected function calculateDoctrineTextLength($type)
+ {
+ switch ($type)
+ {
+ case 'mediumText':
+ return 65535 + 1;
+
+ case 'longText':
+ return 16777215 + 1;
+
+ default:
+ return 255 + 1;
+ }
+ }
+
+ /**
+ * Get the matching Doctrine option for a given Fluent attribute name.
+ *
+ * @param string $attribute
+ * @return string
+ */
+ protected function mapFluentOptionToDoctrine($attribute)
+ {
+ switch($attribute)
+ {
+ case 'type':
+ case 'name':
+ return;
+
+ case 'nullable':
+ return 'notnull';
+
+ case 'total':
+ return 'precision';
+
+ case 'places':
+ return 'scale';
+
+ default:
+ return $attribute;
+ }
+ }
+
+ /**
+ * Get the matching Doctrine value for a given Fluent attribute.
+ *
+ * @param string $option
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function mapFluentValueToDoctrine($option, $value)
+ {
+ return $option == 'notnull' ? ! $value : $value;
+ }
+
+}
diff --git a/Schema/Grammars/MySqlGrammar.php b/Schema/Grammars/MySqlGrammar.php
index 7de0007d9..64cbb55ca 100755
--- a/Schema/Grammars/MySqlGrammar.php
+++ b/Schema/Grammars/MySqlGrammar.php
@@ -6,26 +6,19 @@
class MySqlGrammar extends Grammar {
- /**
- * The keyword identifier wrapper format.
- *
- * @var string
- */
- protected $wrapper = '`%s`';
-
/**
* The possible column modifiers.
*
* @var array
*/
- protected $modifiers = array('Unsigned', 'Nullable', 'Default', 'Increment', 'After');
+ protected $modifiers = array('Unsigned', 'Nullable', 'Default', 'Increment', 'Comment', 'After');
/**
* The possible column serials
*
* @var array
*/
- protected $serials = array('bigInteger', 'integer');
+ protected $serials = array('bigInteger', 'integer', 'mediumInteger', 'smallInteger', 'tinyInteger');
/**
* Compile the query to determine the list of tables.
@@ -40,7 +33,6 @@ public function compileTableExists()
/**
* Compile the query to determine the list of columns.
*
- * @param string $table
* @return string
*/
public function compileColumnExists()
@@ -98,7 +90,7 @@ protected function compileCreateEncoding($sql, Connection $connection)
}
/**
- * Compile a create table command.
+ * Compile an add column command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
@@ -276,6 +268,17 @@ public function compileRename(Blueprint $blueprint, Fluent $command)
return "rename table {$from} to ".$this->wrapTable($command->to);
}
+ /**
+ * Create the column definition for a char type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeChar(Fluent $column)
+ {
+ return "char({$column->length})";
+ }
+
/**
* Create the column definition for a string type.
*
@@ -383,7 +386,7 @@ protected function typeSmallInteger(Fluent $column)
*/
protected function typeFloat(Fluent $column)
{
- return "float({$column->total}, {$column->places})";
+ return $this->typeDouble($column);
}
/**
@@ -398,10 +401,8 @@ protected function typeDouble(Fluent $column)
{
return "double({$column->total}, {$column->places})";
}
- else
- {
- return 'double';
- }
+
+ return 'double';
}
/**
@@ -427,7 +428,7 @@ protected function typeBoolean(Fluent $column)
}
/**
- * Create the column definition for a enum type.
+ * Create the column definition for an enum type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
@@ -437,6 +438,17 @@ protected function typeEnum(Fluent $column)
return "enum('".implode("', '", $column->allowed)."')";
}
+ /**
+ * Create the column definition for a json type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeJson(Fluent $column)
+ {
+ return 'text';
+ }
+
/**
* Create the column definition for a date type.
*
@@ -563,4 +575,32 @@ protected function modifyAfter(Blueprint $blueprint, Fluent $column)
}
}
+ /**
+ * Get the SQL for an "comment" column modifier.
+ *
+ * @param \Illuminate\Database\Schema\Blueprint $blueprint
+ * @param \Illuminate\Support\Fluent $column
+ * @return string|null
+ */
+ protected function modifyComment(Blueprint $blueprint, Fluent $column)
+ {
+ if ( ! is_null($column->comment))
+ {
+ return ' comment "'.$column->comment.'"';
+ }
+ }
+
+ /**
+ * Wrap a single string in keyword identifiers.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function wrapValue($value)
+ {
+ if ($value === '*') return $value;
+
+ return '`'.str_replace('`', '``', $value).'`';
+ }
+
}
diff --git a/Schema/Grammars/PostgresGrammar.php b/Schema/Grammars/PostgresGrammar.php
index c17057944..de63407ec 100755
--- a/Schema/Grammars/PostgresGrammar.php
+++ b/Schema/Grammars/PostgresGrammar.php
@@ -5,13 +5,6 @@
class PostgresGrammar extends Grammar {
- /**
- * The keyword identifier wrapper format.
- *
- * @var string
- */
- protected $wrapper = '"%s"';
-
/**
* The possible column modifiers.
*
@@ -229,6 +222,17 @@ public function compileRename(Blueprint $blueprint, Fluent $command)
return "alter table {$from} rename to ".$this->wrapTable($command->to);
}
+ /**
+ * Create the column definition for a char type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeChar(Fluent $column)
+ {
+ return "char({$column->length})";
+ }
+
/**
* Create the column definition for a string type.
*
@@ -336,7 +340,7 @@ protected function typeSmallInteger(Fluent $column)
*/
protected function typeFloat(Fluent $column)
{
- return 'real';
+ return $this->typeDouble($column);
}
/**
@@ -382,7 +386,18 @@ protected function typeEnum(Fluent $column)
{
$allowed = array_map(function($a) { return "'".$a."'"; }, $column->allowed);
- return "varchar(255) check ({$column->name} in (".implode(', ', $allowed)."))";
+ return "varchar(255) check (\"{$column->name}\" in (".implode(', ', $allowed)."))";
+ }
+
+ /**
+ * Create the column definition for a json type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeJson(Fluent $column)
+ {
+ return "json";
}
/**
@@ -404,7 +419,7 @@ protected function typeDate(Fluent $column)
*/
protected function typeDateTime(Fluent $column)
{
- return 'timestamp';
+ return 'timestamp(0) without time zone';
}
/**
@@ -415,7 +430,7 @@ protected function typeDateTime(Fluent $column)
*/
protected function typeTime(Fluent $column)
{
- return 'time';
+ return 'time(0) without time zone';
}
/**
@@ -426,7 +441,7 @@ protected function typeTime(Fluent $column)
*/
protected function typeTimestamp(Fluent $column)
{
- return 'timestamp';
+ return 'timestamp(0) without time zone';
}
/**
@@ -476,7 +491,7 @@ protected function modifyDefault(Blueprint $blueprint, Fluent $column)
*/
protected function modifyIncrement(Blueprint $blueprint, Fluent $column)
{
- if (in_array($column->type, $this->serials) and $column->autoIncrement)
+ if (in_array($column->type, $this->serials) && $column->autoIncrement)
{
return ' primary key';
}
diff --git a/Schema/Grammars/SQLiteGrammar.php b/Schema/Grammars/SQLiteGrammar.php
index 32a26491c..b0c279eab 100755
--- a/Schema/Grammars/SQLiteGrammar.php
+++ b/Schema/Grammars/SQLiteGrammar.php
@@ -6,13 +6,6 @@
class SQLiteGrammar extends Grammar {
- /**
- * The keyword identifier wrapper format.
- *
- * @var string
- */
- protected $wrapper = '"%s"';
-
/**
* The possible column modifiers.
*
@@ -68,7 +61,7 @@ public function compileCreate(Blueprint $blueprint, Fluent $command)
$sql .= (string) $this->addPrimaryKeys($blueprint);
- return $sql .= ')';
+ return $sql.')';
}
/**
@@ -155,6 +148,8 @@ public function compileAdd(Blueprint $blueprint, Fluent $command)
$columns = $this->prefixArray('add column', $this->getColumns($blueprint));
+ $statements = array();
+
foreach ($columns as $column)
{
$statements[] = 'alter table '.$table.' '.$column;
@@ -293,6 +288,17 @@ public function compileRename(Blueprint $blueprint, Fluent $command)
return "alter table {$from} rename to ".$this->wrapTable($command->to);
}
+ /**
+ * Create the column definition for a char type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeChar(Fluent $column)
+ {
+ return 'varchar';
+ }
+
/**
* Create the column definition for a string type.
*
@@ -422,7 +428,7 @@ protected function typeDouble(Fluent $column)
*/
protected function typeDecimal(Fluent $column)
{
- return 'float';
+ return 'numeric';
}
/**
@@ -437,7 +443,7 @@ protected function typeBoolean(Fluent $column)
}
/**
- * Create the column definition for a enum type.
+ * Create the column definition for an enum type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
@@ -447,6 +453,17 @@ protected function typeEnum(Fluent $column)
return 'varchar';
}
+ /**
+ * Create the column definition for a json type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeJson(Fluent $column)
+ {
+ return 'text';
+ }
+
/**
* Create the column definition for a date type.
*
@@ -538,7 +555,7 @@ protected function modifyDefault(Blueprint $blueprint, Fluent $column)
*/
protected function modifyIncrement(Blueprint $blueprint, Fluent $column)
{
- if (in_array($column->type, $this->serials) and $column->autoIncrement)
+ if (in_array($column->type, $this->serials) && $column->autoIncrement)
{
return ' primary key autoincrement';
}
diff --git a/Schema/Grammars/SqlServerGrammar.php b/Schema/Grammars/SqlServerGrammar.php
index 68f4179e2..89dd14e50 100755
--- a/Schema/Grammars/SqlServerGrammar.php
+++ b/Schema/Grammars/SqlServerGrammar.php
@@ -5,13 +5,6 @@
class SqlServerGrammar extends Grammar {
- /**
- * The keyword identifier wrapper format.
- *
- * @var string
- */
- protected $wrapper = '"%s"';
-
/**
* The possible column modifiers.
*
@@ -44,8 +37,8 @@ public function compileTableExists()
*/
public function compileColumnExists($table)
{
- return "select col.name from sys.columns as col
- join sys.objects as obj on col.object_id = obj.object_id
+ return "select col.name from sys.columns as col
+ join sys.objects as obj on col.object_id = obj.object_id
where obj.type = 'U' and obj.name = '$table'";
}
@@ -164,8 +157,6 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command)
*/
public function compileDropPrimary(Blueprint $blueprint, Fluent $command)
{
- $table = $blueprint->getTable();
-
$table = $this->wrapTable($blueprint);
return "alter table {$table} drop constraint {$command->index}";
@@ -227,6 +218,17 @@ public function compileRename(Blueprint $blueprint, Fluent $command)
return "sp_rename {$from}, ".$this->wrapTable($command->to);
}
+ /**
+ * Create the column definition for a char type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeChar(Fluent $column)
+ {
+ return "nchar({$column->length})";
+ }
+
/**
* Create the column definition for a string type.
*
@@ -371,7 +373,7 @@ protected function typeBoolean(Fluent $column)
}
/**
- * Create the column definition for a enum type.
+ * Create the column definition for an enum type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
@@ -381,6 +383,17 @@ protected function typeEnum(Fluent $column)
return 'nvarchar(255)';
}
+ /**
+ * Create the column definition for a json type.
+ *
+ * @param \Illuminate\Support\Fluent $column
+ * @return string
+ */
+ protected function typeJson(Fluent $column)
+ {
+ return 'nvarchar(max)';
+ }
+
/**
* Create the column definition for a date type.
*
@@ -472,7 +485,7 @@ protected function modifyDefault(Blueprint $blueprint, Fluent $column)
*/
protected function modifyIncrement(Blueprint $blueprint, Fluent $column)
{
- if (in_array($column->type, $this->serials) and $column->autoIncrement)
+ if (in_array($column->type, $this->serials) && $column->autoIncrement)
{
return ' identity primary key';
}
diff --git a/Schema/MySqlBuilder.php b/Schema/MySqlBuilder.php
index a0a1d2e0f..747268154 100755
--- a/Schema/MySqlBuilder.php
+++ b/Schema/MySqlBuilder.php
@@ -25,7 +25,7 @@ public function hasTable($table)
* @param string $table
* @return array
*/
- protected function getColumnListing($table)
+ public function getColumnListing($table)
{
$sql = $this->grammar->compileColumnExists();
diff --git a/SeedServiceProvider.php b/SeedServiceProvider.php
index 859c9f1ef..3dd93c177 100755
--- a/SeedServiceProvider.php
+++ b/SeedServiceProvider.php
@@ -21,7 +21,7 @@ public function register()
{
$this->registerSeedCommand();
- $this->app->bindShared('seeder', function($app)
+ $this->app->singleton('seeder', function()
{
return new Seeder;
});
@@ -36,7 +36,7 @@ public function register()
*/
protected function registerSeedCommand()
{
- $this->app->bindShared('command.seed', function($app)
+ $this->app->singleton('command.seed', function($app)
{
return new SeedCommand($app['db']);
});
@@ -52,4 +52,4 @@ public function provides()
return array('seeder', 'command.seed');
}
-}
\ No newline at end of file
+}
diff --git a/Seeder.php b/Seeder.php
index 5a378856d..9074dce98 100755
--- a/Seeder.php
+++ b/Seeder.php
@@ -36,7 +36,10 @@ public function call($class)
{
$this->resolve($class)->run();
- $this->command->getOutput()->writeln("Seeded: $class");
+ if (isset($this->command))
+ {
+ $this->command->getOutput()->writeln("Seeded: $class");
+ }
}
/**
@@ -51,19 +54,26 @@ protected function resolve($class)
{
$instance = $this->container->make($class);
- return $instance->setContainer($this->container)->setCommand($this->command);
+ $instance->setContainer($this->container);
}
else
{
- return new $class;
+ $instance = new $class;
+ }
+
+ if (isset($this->command))
+ {
+ $instance->setCommand($this->command);
}
+
+ return $instance;
}
/**
* Set the IoC container instance.
*
* @param \Illuminate\Container\Container $container
- * @return void
+ * @return $this
*/
public function setContainer(Container $container)
{
@@ -76,7 +86,7 @@ public function setContainer(Container $container)
* Set the console command instance.
*
* @param \Illuminate\Console\Command $command
- * @return void
+ * @return $this
*/
public function setCommand(Command $command)
{
@@ -85,4 +95,4 @@ public function setCommand(Command $command)
return $this;
}
-}
\ No newline at end of file
+}
diff --git a/SqlServerConnection.php b/SqlServerConnection.php
index 47d3ac252..523f7598f 100755
--- a/SqlServerConnection.php
+++ b/SqlServerConnection.php
@@ -1,6 +1,7 @@
pdo->exec('ROLLBACK TRAN');
diff --git a/composer.json b/composer.json
index 4f05bc08c..a8ff505c2 100755
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,6 @@
{
"name": "illuminate/database",
+ "description": "The Illuminate Database package.",
"license": "MIT",
"keywords": ["laravel", "database", "sql", "orm"],
"authors": [
@@ -9,31 +10,26 @@
}
],
"require": {
- "php": ">=5.3.0",
- "illuminate/container": "4.1.x",
- "illuminate/events": "4.1.x",
- "illuminate/support": "4.1.x",
- "nesbot/carbon": "1.*"
- },
- "require-dev": {
- "illuminate/cache": "4.1.x",
- "illuminate/console": "4.1.x",
- "illuminate/filesystem": "4.1.x",
- "illuminate/pagination": "4.1.x",
- "illuminate/support": "4.1.x",
- "mockery/mockery": "0.7.2",
- "phpunit/phpunit": "3.7.*"
+ "php": ">=5.4.0",
+ "illuminate/container": "5.1.*",
+ "illuminate/contracts": "5.1.*",
+ "illuminate/support": "5.1.*",
+ "nesbot/carbon": "~1.0"
},
"autoload": {
- "psr-0": {
- "Illuminate\\Database": ""
+ "psr-4": {
+ "Illuminate\\Database\\": ""
}
},
- "target-dir": "Illuminate/Database",
"extra": {
"branch-alias": {
- "dev-master": "4.1-dev"
+ "dev-master": "5.1-dev"
}
},
+ "suggest": {
+ "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).",
+ "illuminate/console": "Required to use the database commands (5.1.*).",
+ "illuminate/filesystem": "Required to use the migrations (5.1.*)."
+ },
"minimum-stability": "dev"
}