diff --git a/application/config/aliases.php b/application/config/aliases.php index a246bab1..18f0ba6f 100644 --- a/application/config/aliases.php +++ b/application/config/aliases.php @@ -26,8 +26,8 @@ 'Cookie' => 'System\\Cookie', 'Crypt' => 'System\\Crypt', 'Date' => 'System\\Date', - 'DB' => 'System\\DB', - 'Eloquent' => 'System\\DB\\Eloquent', + 'DB' => 'System\\DB\\Manager', + 'Eloquent' => 'System\\DB\\Eloquent\\Model', 'File' => 'System\\File', 'Form' => 'System\\Form', 'Hash' => 'System\\Hash', diff --git a/public/index.php b/public/index.php index 11de2ee4..053eb6fa 100644 --- a/public/index.php +++ b/public/index.php @@ -8,6 +8,8 @@ * @link http://laravel.com */ +$t = microtime(true); + // -------------------------------------------------------------- // The path to the application directory. // -------------------------------------------------------------- @@ -135,6 +137,16 @@ System\Session::load(System\Cookie::get('laravel_session')); } +// -------------------------------------------------------------- +// Load all of the core routing classes. +// -------------------------------------------------------------- +require SYS_PATH.'request'.EXT; +require SYS_PATH.'response'.EXT; +require SYS_PATH.'routing/route'.EXT; +require SYS_PATH.'routing/router'.EXT; +require SYS_PATH.'routing/loader'.EXT; +require SYS_PATH.'routing/filter'.EXT; + // -------------------------------------------------------------- // Register the route filters. // -------------------------------------------------------------- @@ -150,7 +162,7 @@ // ---------------------------------------------------------- if (is_null($response)) { - $route = System\Routing\Router::make(Request::method(), Request::uri(), new System\Routing\Loader)->route(); + $route = System\Routing\Router::make(System\Request::method(), System\Request::uri(), new System\Routing\Loader(APP_PATH))->route(); $response = (is_null($route)) ? System\Response::error('404') : $route->call(); } diff --git a/system/db.php b/system/db.php deleted file mode 100644 index e2765f61..00000000 --- a/system/db.php +++ /dev/null @@ -1,127 +0,0 @@ - 0) ? $results[0] : null; - } - - /** - * Execute a SQL query against the connection. - * - * The method returns the following based on query type: - * - * SELECT -> Array of stdClasses - * UPDATE -> Number of rows affected. - * DELETE -> Number of Rows affected. - * ELSE -> Boolean true / false depending on success. - * - * @param string $sql - * @param array $bindings - * @param string $connection - * @return array - */ - public static function query($sql, $bindings = array(), $connection = null) - { - $query = static::connection($connection)->prepare($sql); - - $result = $query->execute($bindings); - - if (strpos(strtoupper($sql), 'SELECT') === 0) - { - return $query->fetchAll(\PDO::FETCH_CLASS, 'stdClass'); - } - elseif (strpos(strtoupper($sql), 'UPDATE') === 0 or strpos(strtoupper($sql), 'DELETE') === 0) - { - return $query->rowCount(); - } - else - { - return $result; - } - } - - /** - * Begin a fluent query against a table. - * - * This method is simply a convenient shortcut into Query::table. - * - * @param string $table - * @param string $connection - * @return Query - */ - public static function table($table, $connection = null) - { - return new DB\Query($table, $connection); - } - - /** - * Get the driver name for a database connection. - * - * @param string $connection - * @return string - */ - public static function driver($connection = null) - { - return static::connection($connection)->getAttribute(\PDO::ATTR_DRIVER_NAME); - } - - /** - * Get the table prefix for a database connection. - * - * @param string $connection - * @return string - */ - public static function prefix($connection = null) - { - $connections = Config::get('db.connections'); - - if (is_null($connection)) - { - $connection = Config::get('db.default'); - } - - return (array_key_exists('prefix', $connections[$connection])) ? $connections[$connection]['prefix'] : ''; - } - -} \ No newline at end of file diff --git a/system/db/connection.php b/system/db/connection.php new file mode 100644 index 00000000..0c3ab037 --- /dev/null +++ b/system/db/connection.php @@ -0,0 +1,138 @@ +name = $name; + $this->config = $config; + $this->connector = $connector; + $this->pdo = $this->connector->connect($this->config); + } + + /** + * Execute a SQL query against the connection and return the first result. + * + * @param string $sql + * @param array $bindings + * @return object + */ + public function first($sql, $bindings = array()) + { + return (count($results = $this->query($sql, $bindings)) > 0) ? $results[0] : null; + } + + /** + * Execute a SQL query against the connection. + * + * The method returns the following based on query type: + * + * SELECT -> Array of stdClasses + * UPDATE -> Number of rows affected. + * DELETE -> Number of Rows affected. + * ELSE -> Boolean true / false depending on success. + * + * @param string $sql + * @param array $bindings + * @return array + */ + public function query($sql, $bindings = array()) + { + $query = $this->pdo->prepare($sql); + + $result = $query->execute($bindings); + + if (strpos(strtoupper($sql), 'SELECT') === 0) + { + return $query->fetchAll(\PDO::FETCH_CLASS, 'stdClass'); + } + elseif (strpos(strtoupper($sql), 'UPDATE') === 0 or strpos(strtoupper($sql), 'DELETE') === 0) + { + return $query->rowCount(); + } + else + { + return $result; + } + } + + /** + * Begin a fluent query against a table. + * + * @param string $table + * @return Query + */ + public function table($table) + { + return new Query($table, $this); + } + + /** + * Get the keyword identifier wrapper for the connection. + * + * @return string + */ + public function wrapper() + { + if (array_key_exists('wrap', $this->config) and $this->config['wrap'] === false) return ''; + + return ($this->driver() == 'mysql') ? '`' : '"'; + } + + /** + * Get the driver name for the database connection. + * + * @return string + */ + public function driver() + { + return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Get the table prefix for the database connection. + * + * @return string + */ + public function prefix() + { + return (array_key_exists('prefix', $this->config)) ? $this->config['prefix'] : ''; + } + +} \ No newline at end of file diff --git a/system/db/connector.php b/system/db/connector.php index 769f4f27..95e2a3b4 100644 --- a/system/db/connector.php +++ b/system/db/connector.php @@ -9,7 +9,7 @@ class Connector { * * @var array */ - public static $options = array( + public $options = array( \PDO::ATTR_CASE => \PDO::CASE_LOWER, \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_ORACLE_NULLS => \PDO::NULL_NATURAL, @@ -20,24 +20,22 @@ class Connector { /** * Establish a PDO database connection. * - * @param string $connection + * @param object $connection * @return PDO */ - public static function connect($connection) + public function connect($config) { - $config = static::configuration($connection); - switch ($config->driver) { case 'sqlite': - return static::connect_to_sqlite($config); + return $this->connect_to_sqlite($config); case 'mysql': case 'pgsql': - return static::connect_to_server($config); + return $this->connect_to_server($config); default: - return static::connect_to_generic($config); + return $this->connect_to_generic($config); } throw new \Exception('Database driver '.$config->driver.' is not supported.'); @@ -47,20 +45,25 @@ public static function connect($connection) * Establish a PDO connection to a SQLite database. * * SQLite database paths can be specified either relative to the application/db - * directory, or as an absolute path to any location on the file system. + * directory, or as an absolute path to any location on the file system. In-memory + * databases are also supported. * * @param object $config * @return PDO */ - private static function connect_to_sqlite($config) + private function connect_to_sqlite($config) { - if (file_exists($path = DATABASE_PATH.$config->database.'.sqlite')) + if ($config->database == ':memory:') { - return new \PDO('sqlite:'.$path, null, null, static::$options); + return new \PDO('sqlite::memory:', null, null, $this->options); + } + elseif (file_exists($path = DATABASE_PATH.$config->database.'.sqlite')) + { + return new \PDO('sqlite:'.$path, null, null, $this->options); } elseif (file_exists($config->database)) { - return new \PDO('sqlite:'.$config->database, null, null, static::$options); + return new \PDO('sqlite:'.$config->database, null, null, $this->options); } else { @@ -74,7 +77,7 @@ private static function connect_to_sqlite($config) * @param object $config * @return PDO */ - private static function connect_to_server($config) + private function connect_to_server($config) { $dsn = $config->driver.':host='.$config->host.';dbname='.$config->database; @@ -83,7 +86,7 @@ private static function connect_to_server($config) $dsn .= ';port='.$config->port; } - $connection = new \PDO($dsn, $config->username, $config->password, static::$options); + $connection = new \PDO($dsn, $config->username, $config->password, $this->options); if (isset($config->charset)) { @@ -99,27 +102,9 @@ private static function connect_to_server($config) * @param object $config * @return PDO */ - private static function connect_to_generic($config) + private function connect_to_generic($config) { - return new \PDO($config->driver.':'.$config->dsn, $config->username, $config->password, static::$options); - } - - /** - * Get the configuration options for a database connection. - * - * @param string $connection - * @return object - */ - private static function configuration($connection) - { - $config = Config::get('db.connections'); - - if ( ! array_key_exists($connection, $config)) - { - throw new \Exception("Database connection [$connection] is not defined."); - } - - return (object) $config[$connection]; + return new \PDO($config->driver.':'.$config->dsn, $config->username, $config->password, $this->options); } } \ No newline at end of file diff --git a/system/db/eloquent/hydrator.php b/system/db/eloquent/hydrator.php index 276aa1bf..0d836115 100644 --- a/system/db/eloquent/hydrator.php +++ b/system/db/eloquent/hydrator.php @@ -1,13 +1,11 @@ attributes[$spoof = $include.'_id'] = 0; @@ -79,9 +75,7 @@ private static function eagerly($eloquent, &$parents, $include) unset($eloquent->attributes[$spoof]); // Reset the WHERE clause and bindings on the query. We'll add our own WHERE clause soon. - $relationship->query->where = 'WHERE 1 = 1'; - - $relationship->query->bindings = array(); + $relationship->query->reset_where(); // Initialize the relationship attribute on the parents. As expected, "many" relationships // are initialized to an array and "one" relationships are initialized to null. @@ -90,21 +84,13 @@ private static function eagerly($eloquent, &$parents, $include) $parent->ignore[$include] = (in_array($eloquent->relating, array('has_many', 'has_and_belongs_to_many'))) ? array() : null; } - if ($eloquent->relating == 'has_one') + if (in_array($relating = $eloquent->relating, array('has_one', 'has_many', 'belongs_to'))) { - static::eagerly_load_one($relationship, $parents, $eloquent->relating_key, $include); - } - elseif ($eloquent->relating == 'has_many') - { - static::eagerly_load_many($relationship, $parents, $eloquent->relating_key, $include); - } - elseif ($eloquent->relating == 'belongs_to') - { - static::eagerly_load_belonging($relationship, $parents, $eloquent->relating_key, $include); + return static::$relating($relationship, $parents, $eloquent->relating_key, $include); } else { - static::eagerly_load_many_to_many($relationship, $parents, $eloquent->relating_key, $eloquent->relating_table, $include); + static::has_and_belongs_to_many($relationship, $parents, $eloquent->relating_key, $eloquent->relating_table, $include); } } @@ -118,7 +104,7 @@ private static function eagerly($eloquent, &$parents, $include) * @param string $include * @return void */ - private static function eagerly_load_one($relationship, &$parents, $relating_key, $include) + private static function has_one($relationship, &$parents, $relating_key, $include) { foreach ($relationship->where_in($relating_key, array_keys($parents))->get() as $key => $child) { @@ -136,7 +122,7 @@ private static function eagerly_load_one($relationship, &$parents, $relating_key * @param string $include * @return void */ - private static function eagerly_load_many($relationship, &$parents, $relating_key, $include) + private static function has_many($relationship, &$parents, $relating_key, $include) { foreach ($relationship->where_in($relating_key, array_keys($parents))->get() as $key => $child) { @@ -153,7 +139,7 @@ private static function eagerly_load_many($relationship, &$parents, $relating_ke * @param string $include * @return void */ - private static function eagerly_load_belonging($relationship, &$parents, $relating_key, $include) + private static function belongs_to($relationship, &$parents, $relating_key, $include) { $keys = array(); @@ -184,14 +170,17 @@ private static function eagerly_load_belonging($relationship, &$parents, $relati * * @return void */ - private static function eagerly_load_many_to_many($relationship, &$parents, $relating_key, $relating_table, $include) + private static function has_and_belongs_to_many($relationship, &$parents, $relating_key, $relating_table, $include) { + // The model "has and belongs to many" method sets the SELECT clause; however, we need + // to clear it here since we will be adding the foreign key to the select. $relationship->query->select = null; $relationship->query->where_in($relating_table.'.'.$relating_key, array_keys($parents)); // The foreign key is added to the select to allow us to easily match the models back to their parents. - $children = $relationship->query->get(array(Eloquent::table(get_class($relationship)).'.*', $relating_table.'.'.$relating_key)); + // Otherwise, there would be no apparent connection between the models to allow us to match them. + $children = $relationship->query->get(array(Model::table(get_class($relationship)).'.*', $relating_table.'.'.$relating_key)); $class = get_class($relationship); diff --git a/system/db/eloquent.php b/system/db/eloquent/model.php similarity index 72% rename from system/db/eloquent.php rename to system/db/eloquent/model.php index f0eb9d9e..9b095f0b 100644 --- a/system/db/eloquent.php +++ b/system/db/eloquent/model.php @@ -1,10 +1,25 @@ -includes = func_get_args(); + return $this; + } + + /** + * Factory for creating queryable Eloquent model instances. + * + * @param string $class + * @return object + */ + public static function query($class) + { + $model = new $class; + + // Since this method is only used for instantiating models for querying + // purposes, we will go ahead and set the Query instance on the model. + $model->query = Manager::connection(static::$connection)->table(static::table($class)); + + return $model; + } + /** * Get the table name for a model. * @@ -118,35 +154,13 @@ public static function table($class) } /** - * Factory for creating new Eloquent model instances. + * Get all of the models from the database. * - * @param string $class - * @return object + * @return array */ - public static function make($class) + public static function all() { - $model = new $class; - - // Since this method is only used for instantiating models for querying - // purposes, we will go ahead and set the Query instance on the model. - $model->query = Query::table(static::table($class)); - - return $model; - } - - /** - * Create a new model instance and set the relationships - * that should be eagerly loaded. - * - * @return mixed - */ - public static function with() - { - $model = static::make(get_called_class()); - - $model->includes = func_get_args(); - - return $model; + return Hydrator::hydrate(static::query(get_called_class())); } /** @@ -157,7 +171,7 @@ public static function with() */ public static function find($id) { - return static::make(get_called_class())->where('id', '=', $id)->first(); + return static::query(get_called_class())->where('id', '=', $id)->first(); } /** @@ -167,7 +181,7 @@ public static function find($id) */ private function _get() { - return Eloquent\Hydrator::hydrate($this); + return Hydrator::hydrate($this); } /** @@ -177,7 +191,7 @@ private function _get() */ private function _first() { - return (count($results = Eloquent\Hydrator::hydrate($this->take(1))) > 0) ? reset($results) : null; + return (count($results = Hydrator::hydrate($this->take(1))) > 0) ? reset($results) : null; } /** @@ -192,12 +206,7 @@ private function _paginate($per_page = null) if (is_null($per_page)) { - if ( ! property_exists(get_class($this), 'per_page')) - { - throw new \Exception("The number of models to display per page for model [".get_class($this)."] has not been specified."); - } - - $per_page = static::$per_page; + $per_page = (property_exists(get_class($this), 'per_page')) ? static::$per_page : 15; } $current_page = \System\Paginator::page($total, $per_page); @@ -248,7 +257,7 @@ private function has_one_or_many($model, $foreign_key) { $this->relating_key = (is_null($foreign_key)) ? strtolower(get_class($this)).'_id' : $foreign_key; - return static::make($model)->where($this->relating_key, '=', $this->id); + return static::query($model)->where($this->relating_key, '=', $this->id); } /** @@ -277,7 +286,7 @@ public function belongs_to($model, $foreign_key = null) $this->relating_key = $caller['function'].'_id'; } - return static::make($model)->where('id', '=', $this->attributes[$this->relating_key]); + return static::query($model)->where('id', '=', $this->attributes[$this->relating_key]); } /** @@ -318,7 +327,7 @@ public function has_and_belongs_to_many($model, $table = null, $foreign_key = nu $associated_key = (is_null($associated_key)) ? strtolower($model).'_id' : $associated_key; - return static::make($model) + return static::query($model) ->select(array(static::table($model).'.*')) ->join($this->relating_table, static::table($model).'.id', '=', $this->relating_table.'.'.$associated_key) ->where($this->relating_table.'.'.$this->relating_key, '=', $this->id); @@ -331,25 +340,19 @@ public function has_and_belongs_to_many($model, $table = null, $foreign_key = nu */ public function save() { - if ($this->exists and count($this->dirty) == 0) - { - return true; - } + // If the model does not have any dirty attributes, there is no reason + // to save it to the database. + if ($this->exists and count($this->dirty) == 0) return true; $model = get_class($this); // Since the model was instantiated using "new", a query instance has not been set. // Only models being used for querying have their query instances set by default. - $this->query = Query::table(static::table($model)); + $this->query = Manager::connection(static::$connection)->table(static::table($model)); if (property_exists($model, 'timestamps') and $model::$timestamps) { - $this->updated_at = date('Y-m-d H:i:s'); - - if ( ! $this->exists) - { - $this->created_at = $this->updated_at; - } + $this->timestamp(); } // If the model already exists in the database, we will just update it. @@ -368,6 +371,21 @@ public function save() $this->dirty = array(); } + /** + * Set the creation and update timestamps on the model. + * + * @return void + */ + private function timestamp() + { + $this->updated_at = date('Y-m-d H:i:s'); + + if ( ! $this->exists) + { + $this->created_at = $this->updated_at; + } + } + /** * Delete a model from the database. * @@ -376,9 +394,13 @@ public function save() */ public function delete($id = null) { + // If the delete method is being called on an existing model, we only want to delete + // that model. If it is being called from an Eloquent query model, it is probably + // the developer's intention to delete more than one model, so we will pass the + // delete statement to the query instance. if ($this->exists) { - return Query::table(static::table(get_class($this)))->delete($this->id); + return Manager::connection(static::$connection)->table(static::table(get_class($this)))->delete($this->id); } return $this->query->delete(); @@ -389,21 +411,24 @@ public function delete($id = null) */ public function __get($key) { - // The ignored attributes hold all of the loaded relationships for the model. + // Is the requested item a model relationship that has already been loaded? + // All of the loaded relationships are stored in the "ignore" array. if (array_key_exists($key, $this->ignore)) { return $this->ignore[$key]; } - - // If the attribute is a relationship method, return the related models. - if (method_exists($this, $key)) + // Is the requested item a model relationship? If it is, we will dynamically + // load it and return the results of the relationship query. + elseif (method_exists($this, $key)) { $model = $this->$key(); return $this->ignore[$key] = (in_array($this->relating, array('has_one', 'belongs_to'))) ? $model->first() : $model->get(); } - - return (array_key_exists($key, $this->attributes)) ? $this->attributes[$key] : null; + elseif (array_key_exists($key, $this->attributes)) + { + return $this->attributes[$key]; + } } /** @@ -412,6 +437,7 @@ public function __get($key) public function __set($key, $value) { // If the key is a relationship, add it to the ignored attributes. + // Ignored attributes are not stored in the database. if (method_exists($this, $key)) { $this->ignore[$key] = $value; @@ -444,20 +470,24 @@ public function __unset($key) */ public function __call($method, $parameters) { - if (in_array($method, array('get', 'first', 'paginate'))) + // To allow the "with", "get", "first", and "paginate" methods to be called both + // staticly and on an instance, we need to have private, underscored versions + // of the methods and handle them dynamically. + if (in_array($method, array('with', 'get', 'first', 'paginate'))) { - $method = '_'.$method; - - return $this->$method(); + return call_user_func_array(array($this, '_'.$method), $parameters); } + // All of the aggregate functions can be passed directly to the query instance. + // For these functions, we can simply return the response of the query. if (in_array($method, array('count', 'sum', 'min', 'max', 'avg'))) { return call_user_func_array(array($this->query, $method), $parameters); } // Pass the method to the query instance. This allows the chaining of methods - // from the query builder, providing a nice, convenient API. + // from the query builder, providing the same convenient query API as the + // query builder itself. call_user_func_array(array($this->query, $method), $parameters); return $this; @@ -468,30 +498,8 @@ public function __call($method, $parameters) */ public static function __callStatic($method, $parameters) { - $model = static::make(get_called_class()); - - if ($method == 'all') - { - return $model->_get(); - } - - if (in_array($method, array('get', 'first', 'paginate'))) - { - $method = '_'.$method; - - return $model->$method(); - } - - if (in_array($method, array('count', 'sum', 'min', 'max', 'avg'))) - { - return call_user_func_array(array($model->query, $method), $parameters); - } - - // Pass the method to the query instance. This allows the chaining of methods - // from the query builder, providing a nice, convenient API. - call_user_func_array(array($model->query, $method), $parameters); - - return $model; + // Just pass the method to a model instance and let the __call method take care of it. + return call_user_func_array(array(static::query(get_called_class()), $method), $parameters); } } \ No newline at end of file diff --git a/system/db/manager.php b/system/db/manager.php new file mode 100644 index 00000000..9f83bb68 --- /dev/null +++ b/system/db/manager.php @@ -0,0 +1,65 @@ +table($table); + } + + /** + * Magic Method for calling methods on the default database connection. + */ + 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/system/db/query.php b/system/db/query.php index 5260f9a8..19e21d0d 100644 --- a/system/db/query.php +++ b/system/db/query.php @@ -1,24 +1,16 @@ connection = (is_null($connection)) ? Config::get('db.default') : $connection; - $this->from = 'FROM '.$this->wrap($this->table = $table); + $this->connection = $connection; + $this->table = $table; + $this->from = 'FROM '.$this->wrap($table); } /** * Create a new query instance. * - * @param string $table - * @param string $connection + * @param string $table + * @param Connection $connection * @return Query */ - public static function table($table, $connection = null) + public static function table($table, $connection) { return new static($table, $connection); } @@ -129,22 +122,9 @@ public function select($columns = array('*')) { $this->select = ($this->distinct) ? 'SELECT DISTINCT ' : 'SELECT '; - $wrapped = array(); - foreach ($columns as $column) { - // If the column name is being aliased, we will need to wrap the column - // name and its alias in keyword identifiers. - if (strpos(strtolower($column), ' as ') !== false) - { - $segments = explode(' ', $column); - - $wrapped[] = $this->wrap($segments[0]).' AS '.$this->wrap($segments[2]); - } - else - { - $wrapped[] = $this->wrap($column); - } + $wrapped[] = $this->wrap($column); } $this->select .= implode(', ', $wrapped); @@ -194,6 +174,17 @@ public function left_join($table, $column1, $operator, $column2) return $this->join($table, $column1, $operator, $column2, 'LEFT'); } + /** + * Reset the where clause to its initial state. + * + * @return void + */ + public function reset_where() + { + $this->where = 'WHERE 1 = 1'; + $this->bindings = array(); + } + /** * Add a raw where condition to the query. * @@ -356,6 +347,47 @@ public function or_where_not_null($column) return $this->where_not_null($column, 'OR'); } + /** + * Add dynamic where conditions to the query. + * + * @param string $method + * @param array $parameters + * @return Query + */ + private function dynamic_where($method, $parameters) + { + // Strip the "where_" off of the method. + $finder = substr($method, 6); + + // Split the column names from the connectors. + $segments = preg_split('/(_and_|_or_)/i', $finder, -1, PREG_SPLIT_DELIM_CAPTURE); + + // The connector variable will determine which connector will be used for the condition. + // We'll change it as we come across new connectors in the dynamic method string. + // + // The index variable helps us get the correct parameter value for the where condition. + // We increment it each time we add a condition. + $connector = 'AND'; + + $index = 0; + + foreach ($segments as $segment) + { + if ($segment != '_and_' and $segment != '_or_') + { + $this->where($segment, '=', $parameters[$index], $connector); + + $index++; + } + else + { + $connector = trim(strtoupper($segment), '_'); + } + } + + return $this; + } + /** * Add an ordering to the query. * @@ -393,6 +425,18 @@ public function take($value) return $this; } + /** + * Set the limit and offset values for a given page. + * + * @param int $page + * @param int $per_page + * @return Query + */ + public function for_page($page, $per_page) + { + return $this->skip(($page - 1) * $per_page)->take($per_page); + } + /** * Find a record by the primary key. * @@ -405,6 +449,46 @@ public function find($id, $columns = array('*')) return $this->where('id', '=', $id)->first($columns); } + /** + * Get an aggregate value. + * + * @param string $aggregate + * @param string $column + * @return mixed + */ + private function aggregate($aggregator, $column) + { + $this->select = 'SELECT '.$aggregator.'('.$this->wrap($column).') AS '.$this->wrap('aggregate'); + return $this->first()->aggregate; + } + + /** + * Get paginated query results. + * + * @param int $per_page + * @param array $columns + * @return Paginator + */ + public function paginate($per_page, $columns = array('*')) + { + list($select, $total) = array($this->select, $this->count()); + + // Every query clears the SELECT clause, so we store the contents of the clause + // before executing the count query and then put the contents back in afterwards. + if ( ! is_null($select)) + { + $this->select = $select; + } + else + { + $this->select($columns); + } + + $current_page = \System\Paginator::page($total, $per_page); + + return \System\Paginator::make($this->for_page($current_page, $per_page)->get(), $total, $per_page); + } + /** * Execute the query as a SELECT statement and return the first result. * @@ -429,71 +513,32 @@ public function get($columns = array('*')) $this->select($columns); } - $results = DB::query(Query\Compiler::select($this), $this->bindings, $this->connection); + $sql = $this->select.' '.$this->from.' '.$this->where; + + if (count($this->orderings) > 0) + { + $sql .= ' ORDER BY '.implode(', ', $this->orderings); + } + + if ( ! is_null($this->limit)) + { + $sql .= ' LIMIT '.$this->limit; + } + + if ( ! is_null($this->offset)) + { + $sql .= ' OFFSET '.$this->offset; + } + + $results = $this->connection->query($sql, $this->bindings); // Reset the SELECT clause so more queries can be performed using the same instance. - // This is helpful for performing counts and then getting actual results, such as - // when paginating results. + // This is helpful for getting aggregates and then getting actual results. $this->select = null; return $results; } - /** - * Get an aggregate value. - * - * @param string $aggregate - * @param string $column - * @return mixed - */ - private function aggregate($aggregator, $column) - { - $this->select = 'SELECT '.$aggregator.'('.$this->wrap($column).') AS '.$this->wrap('aggregate'); - - return $this->first()->aggregate; - } - - /** - * Get paginated query results. - * - * @param int $per_page - * @param array $columns - * @return Paginator - */ - public function paginate($per_page, $columns = array('*')) - { - $select = $this->select; - - $total = $this->count(); - - // Every query clears the SELECT clause, so we store the contents of the clause - // before executing the count query and then put the contents back in afterwards. - if ( ! is_null($select)) - { - $this->select = $select; - } - else - { - $this->select($columns); - } - - $current_page = \System\Paginator::page($total, $per_page); - - return \System\Paginator::make($this->for_page($current_page, $per_page)->get(), $total, $per_page); - } - - /** - * Set the LIMIT and OFFSET values for a given page. - * - * @param int $page - * @param int $per_page - * @return Query - */ - public function for_page($page, $per_page) - { - return $this->skip(($page - 1) * $per_page)->take($per_page); - } - /** * Execute an INSERT statement. * @@ -502,7 +547,7 @@ public function for_page($page, $per_page) */ public function insert($values) { - return DB::query(Query\Compiler::insert($this, $values), array_values($values), $this->connection); + return $this->connection->query($this->compile_insert($values), array_values($values)); } /** @@ -513,22 +558,40 @@ public function insert($values) */ public function insert_get_id($values) { - $sql = Query\Compiler::insert($this, $values); + $sql = $this->compile_insert($values); // Use the RETURNING clause on Postgres instead of the PDO lastInsertID method. // The PDO method is a little cumbersome using Postgres. - if (DB::driver($this->connection) == 'pgsql') + if ($this->connection->driver() == 'pgsql') { - $query = DB::connection($this->connection)->prepare($sql.' RETURNING '.$this->wrap('id')); + $query = $this->connection->pdo->prepare($sql.' RETURNING '.$this->wrap('id')); $query->execute(array_values($values)); return $query->fetch(\PDO::FETCH_CLASS, 'stdClass')->id; } - DB::query($sql, array_values($values), $this->connection); + $this->connection->query($sql, array_values($values)); - return DB::connection($this->connection)->lastInsertId(); + return $this->connection->pdo->lastInsertId(); + } + + /** + * Compile an SQL INSERT statement. + * + * @param array $values + * @return string + */ + private function compile_insert($values) + { + $sql = 'INSERT INTO '.$this->wrap($this->table); + + foreach (array_keys($values) as $column) + { + $columns[] = $this->wrap($column); + } + + return $sql .= ' ('.implode(', ', $columns).') VALUES ('.$this->parameterize($values).')'; } /** @@ -539,7 +602,16 @@ public function insert_get_id($values) */ public function update($values) { - return DB::query(Query\Compiler::update($this, $values), array_merge(array_values($values), $this->bindings), $this->connection); + $sql = 'UPDATE '.$this->wrap($this->table).' SET '; + + foreach (array_keys($values) as $column) + { + $sets[] = $this->wrap($column).' = ?'; + } + + $sql .= implode(', ', $sets).' '.$this->where; + + return $this->connection->query($sql, array_merge(array_values($values), $this->bindings)); } /** @@ -555,32 +627,38 @@ public function delete($id = null) $this->where('id', '=', $id); } - return DB::query(Query\Compiler::delete($this), $this->bindings, $this->connection); + return $this->connection->query('DELETE FROM '.$this->wrap($this->table).' '.$this->where, $this->bindings); } /** * Wrap a value in keyword identifiers. * - * @param string $value + * @param string $value * @return string */ - public function wrap($value) + private function wrap($value) { - if (is_null($this->config)) + if (strpos(strtolower($value), ' as ') !== false) { - $connections = Config::get('db.connections'); - - $this->config = $connections[$this->connection]; + return $this->wrap_alias($value, $connection); } - if (array_key_exists('wrap', $this->config) and $this->config['wrap'] === false) - { - return $value; - } + $wrap = $this->connection->wrapper(); - $wrap = (DB::driver($this->connection) == 'mysql') ? '`' : '"'; + return implode('.', array_map(function($segment) use ($wrap) { return ($segment != '*') ? $wrap.$segment.$wrap : $segment; }, explode('.', $value))); + } - return implode('.', array_map(function($segment) use ($wrap) {return ($segment != '*') ? $wrap.$segment.$wrap : $segment;}, explode('.', $value))); + /** + * Wrap an alias in keyword identifiers. + * + * @param string $value + * @return string + */ + private function wrap_alias($value) + { + $segments = explode(' ', $value); + + return $this->wrap($segments[0]).' AS '.$this->wrap($segments[2]); } /** @@ -589,7 +667,7 @@ public function wrap($value) * @param array $values * @return string */ - public function parameterize($values) + private function parameterize($values) { return implode(', ', array_fill(0, count($values), '?')); } @@ -601,7 +679,7 @@ public function __call($method, $parameters) { if (strpos($method, 'where_') === 0) { - return Query\Dynamic::build($method, $parameters, $this); + return $this->dynamic_where($method, $parameters, $this); } if (in_array($method, array('count', 'min', 'max', 'avg', 'sum'))) diff --git a/system/db/query/compiler.php b/system/db/query/compiler.php deleted file mode 100644 index c9945a85..00000000 --- a/system/db/query/compiler.php +++ /dev/null @@ -1,86 +0,0 @@ -select.' '.$query->from.' '.$query->where; - - if (count($query->orderings) > 0) - { - $sql .= ' ORDER BY '.implode(', ', $query->orderings); - } - - if ( ! is_null($query->limit)) - { - $sql .= ' LIMIT '.$query->limit; - } - - if ( ! is_null($query->offset)) - { - $sql .= ' OFFSET '.$query->offset; - } - - return $sql; - } - - /** - * Build a SQL INSERT statement. - * - * @param Query $query - * @param array $values - * @return string - */ - public static function insert($query, $values) - { - $sql = 'INSERT INTO '.$query->wrap($query->table); - - $columns = array(); - - foreach (array_keys($values) as $column) - { - $columns[] = $query->wrap($column); - } - - return $sql .= ' ('.implode(', ', $columns).') VALUES ('.$query->parameterize($values).')'; - } - - /** - * Build a SQL UPDATE statement. - * - * @param Query $query - * @param array $values - * @return string - */ - public static function update($query, $values) - { - $sql = 'UPDATE '.$query->wrap($query->table).' SET '; - - $sets = array(); - - foreach (array_keys($values) as $column) - { - $sets[] = $query->wrap($column).' = ?'; - } - - return $sql .= implode(', ', $sets).' '.$query->where; - } - - /** - * Build a SQL DELETE statement. - * - * @param Query $query - * @return string - */ - public static function delete($query) - { - return 'DELETE FROM '.$query->wrap($query->table).' '.$query->where; - } - -} \ No newline at end of file diff --git a/system/db/query/dynamic.php b/system/db/query/dynamic.php deleted file mode 100644 index 8993de6b..00000000 --- a/system/db/query/dynamic.php +++ /dev/null @@ -1,54 +0,0 @@ -where($segment, '=', $parameters[$index], $connector); - - $index++; - } - else - { - $connector = trim(strtoupper($segment), '_'); - } - } - - return $query; - } - -} \ No newline at end of file