From e88d2213ab8191e67cff1cf81f88abe57627352a Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 8 Feb 2012 10:53:58 -0600 Subject: [PATCH] added support for complex joins on query builder. --- laravel/database/query.php | 23 ++++++- laravel/database/query/grammars/grammar.php | 52 ++++++++++------ laravel/database/query/join.php | 68 +++++++++++++++++++++ 3 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 laravel/database/query/join.php diff --git a/laravel/database/query.php b/laravel/database/query.php index e67b5de0..0f20d10b 100644 --- a/laravel/database/query.php +++ b/laravel/database/query.php @@ -147,9 +147,28 @@ public function select($columns = array('*')) * @param string $type * @return Query */ - public function join($table, $column1, $operator, $column2, $type = 'INNER') + public function join($table, $column1, $operator = null, $column2 = null, $type = 'INNER') { - $this->joins[] = compact('type', 'table', 'column1', 'operator', 'column2'); + // If the "column" is really an instance of a Closure, the developer is + // trying to create a join with a complex "ON" clause. So, we will add + // the join, and then call the Closure with the join. + if ($column1 instanceof Closure) + { + $this->joins[] = new Query\Join($type, $table); + + call_user_func($column1, end($this->joins)); + } + // If the column is just a string, we can assume that the join just + // has a simple on clause, and we'll create the join instance and + // add the clause automatically for the develoepr. + else + { + $join = new Query\Join($type, $table); + + $join->on($column1, $operator, $column2); + + $this->joins[] = $join; + } return $this; } diff --git a/laravel/database/query/grammars/grammar.php b/laravel/database/query/grammars/grammar.php index aefa5a48..57aad6f4 100644 --- a/laravel/database/query/grammars/grammar.php +++ b/laravel/database/query/grammars/grammar.php @@ -23,7 +23,7 @@ class Grammar extends \Laravel\Database\Grammar { */ public function select(Query $query) { - return $this->concatenate($this->components($query)); + die(var_dump($this->concatenate($this->components($query)))); } /** @@ -36,12 +36,11 @@ final protected function components($query) { // Each portion of the statement is compiled by a function corresponding // to an item in the components array. This lets us to keep the creation - // of the query very granular, and allows for the flexible customization - // of the query building process by each database system's grammar. + // of the query very granular and very flexible. // - // Note that each component also corresponds to a public property on the - // query instance, allowing us to pass the appropriate data into each of - // the compiler functions. + // Note that each component also connects to a public property on the + // query instance, allowing us to pass the correct data into each + // of the compiler functions. foreach ($this->components as $component) { if ( ! is_null($query->$component)) @@ -75,10 +74,6 @@ final protected function concatenate($components) */ protected function selects(Query $query) { - // Sometimes developers may set a "select" clause on the same query - // that is performing in aggregate look-up, like during pagination. - // So we will not generate the select clause if an aggregate is - // present so the aggregates work. if ( ! is_null($query->aggregate)) return; $select = ($query->distinct) ? 'SELECT DISTINCT ' : 'SELECT '; @@ -129,15 +124,40 @@ protected function joins(Query $query) // set of joins in valid SQL that can appended to the query. foreach ($query->joins as $join) { - $table = $this->wrap_table($join['table']); + $table = $this->wrap_table($join->table); - $column1 = $this->wrap($join['column1']); + $clauses = array(); - $column2 = $this->wrap($join['column2']); + // Each JOIN statement may have multiple clauses, so we will + // iterate through each clause creating the conditions then + // we will concatenate them all together. + foreach ($join->clauses as $clause) + { + extract($clause); - $sql[] = "{$join['type']} JOIN {$table} ON {$column1} {$join['operator']} {$column2}"; + $column1 = $this->wrap($column1); + + $column2 = $this->wrap($column2); + + $clauses[] = "{$connector} {$column1} {$operator} {$column2}"; + } + + // The first clause will have a connector on the front, + // but it is not needed on the first condition, so we + // will strip it off of the condition before adding + // it to the array of joins. + $search = array('AND ', 'OR '); + + $clauses[0] = str_replace($search, '', $clauses[0]); + + $clauses = implode(' ', $clauses); + + $sql[] = "{$join->type} JOIN {$table} ON {$clauses}"; } + // Finally, we should have an array of JOIN clauses + // that we can implode together and return as the + // complete SQL for the JOIN of the query. return implode(' ', $sql); } @@ -180,10 +200,6 @@ final protected function wheres(Query $query) */ protected function where_nested($where) { - // To generate a nested WHERE clause, we'll just feed the query - // back into the "wheres" method. Once we have the clause, we - // will strip off the first six characters to get rid of the - // leading WHERE keyword. return '('.substr($this->wheres($where['query']), 6).')'; } diff --git a/laravel/database/query/join.php b/laravel/database/query/join.php new file mode 100644 index 00000000..ff3c0141 --- /dev/null +++ b/laravel/database/query/join.php @@ -0,0 +1,68 @@ +type = $type; + $this->table = $table; + } + + /** + * Add an ON clause to the join. + * + * @param string $column1 + * @param string $operator + * @param string $column2 + * @param string $connector + * @return Join + */ + public function on($column1, $operator, $column2, $connector = 'AND') + { + $this->clauses[] = compact('column1', 'operator', 'column2', 'connector'); + + return $this; + } + + /** + * Add an OR ON clause to the join. + * + * @param string $column1 + * @param string $operator + * @param string $column2 + * @return Join + */ + public function or_on($column1, $operator, $column2) + { + return $this->on($column1, $operator, $column2, 'OR'); + } + +} \ No newline at end of file