From 68455378cc04b53bc0fb352d05a0d84c9feb23b6 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 12 Oct 2011 19:55:44 -0500 Subject: [PATCH] added support for database expressions. --- laravel/database/connection.php | 7 ++++ laravel/database/expression.php | 43 +++++++++++++++++++++ laravel/database/grammars/grammar.php | 55 ++++++++++++++++++--------- laravel/database/manager.php | 13 +++++++ laravel/routing/router.php | 26 ++++++++++++- 5 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 laravel/database/expression.php diff --git a/laravel/database/connection.php b/laravel/database/connection.php index ebd695b6..d8be69c3 100644 --- a/laravel/database/connection.php +++ b/laravel/database/connection.php @@ -109,6 +109,13 @@ public function first($sql, $bindings = array()) */ public function query($sql, $bindings = array()) { + // First we need to remove all expressions from the bindings since + // they will be placed into the query as raw strings. + foreach ($bindings as $key => $value) + { + if ($value instanceof Expression) unset($bindings[$key]); + } + $sql = $this->transform($sql, $bindings); $this->queries[] = compact('sql', 'bindings'); diff --git a/laravel/database/expression.php b/laravel/database/expression.php new file mode 100644 index 00000000..f92eed6a --- /dev/null +++ b/laravel/database/expression.php @@ -0,0 +1,43 @@ +value = $value; + } + + /** + * Get the string value of the database expression. + * + * @return string + */ + public function get() + { + return $this->value; + } + + /** + * Get the string value of the database expression. + * + * @return string + */ + public function __toString() + { + return $this->get(); + } + +} \ No newline at end of file diff --git a/laravel/database/grammars/grammar.php b/laravel/database/grammars/grammar.php index 12099fbe..75f5c495 100644 --- a/laravel/database/grammars/grammar.php +++ b/laravel/database/grammars/grammar.php @@ -1,4 +1,8 @@ -joins as $join) { - // To save some space, we'll go ahead and wrap all of the elements - // that should wrapped in keyword identifiers. Each join clause will - // be added to an array of clauses and will be imploded after all - // of the clauses have been processed and compiled. $table = $this->wrap($join['table']); $column1 = $this->wrap($join['column1']); @@ -147,12 +147,15 @@ final protected function wheres(Query $query) * This method handles the compilation of the structures created by the * "where" and "or_where" methods on the query builder. * + * This method also handles database expressions, so care must be taken + * to implement this functionality in any derived database grammars. + * * @param array $where * @return string */ protected function where($where) { - return $this->wrap($where['column']).' '.$where['operator'].' ?'; + return $this->wrap($where['column']).' '.$where['operator'].' '.$this->parameter($where['value']); } /** @@ -291,7 +294,10 @@ public function insert(Query $query, $values) */ public function update(Query $query, $values) { - $columns = $this->columnize(array_keys($values), ' = ?'); + foreach ($values as $column => $value) + { + $columns = $this->wrap($column).' = '.$this->parameter($value); + } return trim('UPDATE '.$this->wrap($query->from).' SET '.$columns.' '.$this->wheres($query)); } @@ -316,21 +322,12 @@ public function delete(Query $query) /** * Create a comma-delimited list of wrapped column names. * - * Optionally, an "append" value may be passed to the method. - * This value will be appended to every wrapped column name. - * * @param array $columns - * @param string $append * @return string */ - protected function columnize($columns, $append = '') + protected function columnize($columns) { - foreach ($columns as $column) - { - $sql[] = $this->wrap($column).$append; - } - - return implode(', ', $sql); + return implode(', ', array_map(array($this, 'wrap'), $columns)); } /** @@ -347,6 +344,11 @@ protected function wrap($value) { if (strpos(strtolower($value), ' as ') !== false) return $this->alias($value); + // Expressions should be injected into the query as raw strings, so we + // do not want to wrap them in any way. We will just return the string + // value from the expression. + if ($value instanceof Expression) return $value->get(); + foreach (explode('.', $value) as $segment) { $wrapped[] = ($segment !== '*') ? $this->wrapper.$segment.$this->wrapper : $segment; @@ -376,7 +378,22 @@ protected function alias($value) */ protected function parameterize($values) { - return implode(', ', array_fill(0, count($values), '?')); + return implode(', ', array_map(array($this, 'parameter'), $values)); + } + + /** + * Get the appropriate query parameter string for a value. + * + * If the value is an expression, the raw expression string should + * will be returned, otherwise, the parameter place-holder will be + * returned by the method. + * + * @param mixed $value + * @return string + */ + protected function parameter($value) + { + return ($value instanceof Expression) ? $value->get() : '?'; } } \ No newline at end of file diff --git a/laravel/database/manager.php b/laravel/database/manager.php index 92d49233..953426c8 100644 --- a/laravel/database/manager.php +++ b/laravel/database/manager.php @@ -91,6 +91,19 @@ public static function table($table, $connection = null) return static::connection($connection)->table($table); } + /** + * Create a new database expression instance. + * + * Database expressions are used to inject raw SQL into a fluent query. + * + * @param string $value + * @return Expression + */ + public static function raw($value) + { + return new Expression($value); + } + /** * Magic Method for calling methods on the default database connection. * diff --git a/laravel/routing/router.php b/laravel/routing/router.php index 78b035f1..3b0badeb 100644 --- a/laravel/routing/router.php +++ b/laravel/routing/router.php @@ -45,6 +45,26 @@ class Router { */ protected $controllers; + /** + * The wildcard patterns supported by the router. + * + * @var array + */ + protected $patterns = array( + '(:num)' => '[0-9]+', + '(:any)' => '[a-zA-Z0-9\.\-_]+', + ); + + /** + * The optional wildcard patterns supported by the router. + * + * @var array + */ + protected $optional = array( + '/(:num?)' => '(?:/([0-9]+)', + '/(:any?)' => '(?:/([a-zA-Z0-9\.\-_]+)', + ); + /** * Create a new router for a request method and URI. * @@ -215,11 +235,13 @@ protected function translate_wildcards($key) // For optional parameters, first translate the wildcards to their // regex equivalent, sans the ")?" ending. We will add the endings // back on after we know how many replacements we made. - $key = str_replace(array('/(:num?)', '/(:any?)'), array('(?:/([0-9]+)', '(?:/([a-zA-Z0-9\.\-_]+)'), $key, $replacements); + $key = str_replace(array_keys($this->optional), array_values($this->optional), $key, $replacements); $key .= ($replacements > 0) ? str_repeat(')?', $replacements) : ''; - return str_replace(array(':num', ':any'), array('[0-9]+', '[a-zA-Z0-9\.\-_]+'), $key); + // After replacing all of the optional wildcards, we can replace all + // of the "regular" wildcards and return the fully regexed string. + return str_replace(array_keys($this->patterns), array_values($this->patterns), $key); } /**