Skip to content

Commit

Permalink
implement getWhere()->foo()->end() pattern for chaining
Browse files Browse the repository at this point in the history
  • Loading branch information
pounard committed Nov 30, 2023
1 parent 7c2a9b5 commit 0c4745e
Show file tree
Hide file tree
Showing 19 changed files with 653 additions and 163 deletions.
113 changes: 112 additions & 1 deletion docs/content/introduction/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ $result = $queryBuilder
->column('id')
->column('name')
->column('email')
->where(fn (Where $where) => $where->isLike('email', '%@?', 'gmail.com'))
->getWhere()
->isLike('email', '%@?', 'gmail.com')
->end()
->where('created_at', new \DateTime('2023-01-01 00:00:00'), '>')
->range(100)
->orderBy('created_at')
Expand Down Expand Up @@ -465,6 +467,115 @@ in any method.
The `Where` class acts as a specification pattern implementation, and give numerous
helper methods for adding various comparison expressions.

### Simple comparison

All of `Delete`, `Select` and `Update` allows a `WHERE` clause, which enable
the following syntax:

```php
$query->where('some_column', 'some_value');
```

Will give the following SQL:

```sql
WHERE "some_column" = 'some_value'
```

Additionnaly, you can use a third parameter `$operator` which allows you to
shortcut other expressions:

```php
$query->where('some_column', 'some_value', '<=');
```

Will give the following SQL:

```sql
WHERE "some_column" <= 'some_value'
```
:::warning
Using it this way, operator is considered as raw SQL and may lead to SQL injection.
:::

### Using fluent methods

`Where` class provide numerous comparison functions that will help you write faster
code without knowing the exact comparison or operator syntax:

```php
$query
->getWhere()
->exists('SELECT 1')
->isEqual('foo', 2)
->isBetween('bar', 3, 4)
->isLike('baz', 'foo%')
->isGreaterThan('fizz', 5)
->isNotIn('buzz', [7, 8, 9])
->end()
// Here you returned to the original query and can continue chaining.
```

Which will generate the following SQL:

```SQL
WHERE
EXISTS (
SELECT 1
)
AND "foo" = 2
AND "bar" BETWEEN 3 AND 4
AND "baz" IS LIKE 'foo%'
AND "fizz" > 5
AND "buzz" NOT IN (
7, 8, 9
)
```

You can enve nest `AND` or `OR` clauses:

```php
$query
->getWhere()
->exists('SELECT 1')
->and()
->isEqual('foo', 2)
->isBetween('bar', 3, 4)
->end()
->or()
->isGreaterThan('fizz', 5)
->isNotIn('buzz', [7, 8, 9])
->end()
->end()
// Here you returned to the original query and can continue chaining.
```

Which will generate the following SQL:

```SQL
WHERE
EXISTS (
SELECT 1
)
AND (
"foo" = 2
AND "bar" BETWEEN 3 AND 4
)
AND (
"fizz" > 5
OR "buzz" NOT IN (
7, 8, 9
)
)
```

:::warning
Your IDE will be tricked by the `end()` method for properly chaining, but for this
to work, we had to create as many classes as depth dimensions we support:
**chaining depeer that 2 levels will break IDE autocompletion** yet the code will
execute correctly.
:::

## Inserting arbitrary data

Inserting data is as easy as selecting data:
Expand Down
20 changes: 19 additions & 1 deletion src/Query/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use MakinaCorpus\QueryBuilder\Query\Partial\FromClauseTrait;
use MakinaCorpus\QueryBuilder\Query\Partial\ReturningQueryTrait;
use MakinaCorpus\QueryBuilder\Query\Partial\WhereClauseTrait;
use MakinaCorpus\QueryBuilder\Query\Where\WhereDelete;

/**
* Represents an DELETE query.
Expand All @@ -24,6 +25,7 @@ class Delete extends AbstractQuery
use WhereClauseTrait;

private TableName $table;
private WhereDelete $where;

/**
* Build a new query.
Expand All @@ -36,7 +38,7 @@ class Delete extends AbstractQuery
public function __construct(string|Expression $table, ?string $alias = null)
{
$this->table = $this->normalizeStrictTable($table, $alias);
$this->where = new Where();
$this->where = new WhereDelete($this);
}

/**
Expand All @@ -47,6 +49,22 @@ public function getTable(): TableName
return $this->table;
}

/**
* Get WHERE clause.
*/
public function getWhere(): WhereDelete
{
return $this->where;
}

/**
* {@inheritdoc}
*/
protected function getWhereInstance(): Where
{
return $this->where;
}

/**
* Deep clone support.
*/
Expand Down
47 changes: 4 additions & 43 deletions src/Query/Partial/HavingClauseTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,14 @@
use MakinaCorpus\QueryBuilder\Expression\Comparison;

/**
* Represents the HAVING part of any query.
*
* Don't forget to handle HAVING in __clone(), __construct().
* Additional methods for query with HAVING.
*/
trait HavingClauseTrait
{
private Where $having;

/**
* Get HAVING clause.
*/
public function getHaving(): Where
{
return $this->having;
}

/**
* Open OR expression with parenthesis.
*
* @param callable $callback
* First argument of callback is the nested Where instance.
*
* @deprecated
* Normalize with composite Where interface.
*/
public function havingOr(callable $callback): static
{
$this->having->or($callback);

return $this;
}

/**
* Open AND expression with parenthesis.
*
* @param callable $callback
* First argument of callback is the nested Where instance.
*
* @deprecated
* Normalize with composite Where interface.
*/
public function havingAnd(callable $callback): static
{
$this->having->and($callback);

return $this;
}
abstract protected function getHavingInstance(): Where;

/**
* Add a condition in the HAVING clause.
Expand All @@ -63,7 +24,7 @@ public function havingAnd(callable $callback): static
*/
public function having(mixed $column, mixed $value = null, string $operator = Comparison::EQUAL): static
{
$this->having->compare($column, $value, $operator);
$this->getHavingInstance()->compare($column, $value, $operator);

return $this;
}
Expand All @@ -75,7 +36,7 @@ public function having(mixed $column, mixed $value = null, string $operator = Co
*/
public function havingRaw(mixed $expression, mixed $arguments = null): static
{
$this->having->raw($expression, $arguments);
$this->getHavingInstance()->raw($expression, $arguments);

return $this;
}
Expand Down
47 changes: 4 additions & 43 deletions src/Query/Partial/WhereClauseTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,14 @@
use MakinaCorpus\QueryBuilder\Expression\Comparison;

/**
* Represents the WHERE part of any query.
*
* Don't forget to handle WHERE in __clone(), __construct().
* Additional methods for query with WHERE.
*/
trait WhereClauseTrait
{
private Where $where;

/**
* Get WHERE clause.
*/
public function getWhere(): Where
{
return $this->where;
}

/**
* Open OR statement with parenthesis.
*
* @param callable $callback
* First argument of callback is the nested Where instance.
*
* @deprecated
* Normalize with composite Where interface.
*/
public function whereOr(callable $callback): static
{
$this->where->or($callback);

return $this;
}

/**
* Open AND statement with parenthesis.
*
* @param callable $callback
* First argument of callback is the nested Where instance.
*
* @deprecated
* Normalize with composite Where interface.
*/
public function whereAnd(callable $callback): static
{
$this->where->and($callback);

return $this;
}
abstract protected function getWhereInstance(): Where;

/**
* Add a condition in the WHERE clause.
Expand All @@ -63,7 +24,7 @@ public function whereAnd(callable $callback): static
*/
public function where(mixed $column, mixed $value = null, string $operator = Comparison::EQUAL): static
{
$this->where->compare($column, $value, $operator);
$this->getWhereInstance()->compare($column, $value, $operator);

return $this;
}
Expand All @@ -75,7 +36,7 @@ public function where(mixed $column, mixed $value = null, string $operator = Com
*/
public function whereRaw(mixed $expression, mixed $arguments = null): static
{
$this->where->raw($expression, $arguments);
$this->getWhereInstance()->raw($expression, $arguments);

return $this;
}
Expand Down
41 changes: 38 additions & 3 deletions src/Query/Select.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use MakinaCorpus\QueryBuilder\Query\Partial\OrderByTrait;
use MakinaCorpus\QueryBuilder\Query\Partial\SelectColumn;
use MakinaCorpus\QueryBuilder\Query\Partial\WhereClauseTrait;
use MakinaCorpus\QueryBuilder\Query\Where\WhereSelect;

/**
* Represents a SELECT query.
Expand All @@ -26,8 +27,8 @@ class Select extends AbstractQuery implements TableExpression
{
use FromClauseTrait;
use HavingClauseTrait;
use WhereClauseTrait;
use OrderByTrait;
use WhereClauseTrait;

private bool $distinct = false;
/** @var SelectColumn[] */
Expand All @@ -41,6 +42,8 @@ class Select extends AbstractQuery implements TableExpression
private array $windows = [];
/** @var Expression[] */
private array $unions = [];
private WhereSelect $where;
private WhereSelect $having;

/**
* Build a new query.
Expand All @@ -56,8 +59,8 @@ public function __construct(mixed $table = null, ?string $alias = null)
if ($table) {
$this->from($table, $alias);
}
$this->having = new Where();
$this->where = new Where();
$this->having = new WhereSelect($this);
$this->where = new WhereSelect($this);
}

/**
Expand Down Expand Up @@ -352,6 +355,38 @@ public function createWindow(string $alias): Window
return $this->windows[] = new Window(null, null, $alias);
}

/**
* Get WHERE clause.
*/
public function getWhere(): WhereSelect
{
return $this->where;
}

/**
* {@inheritdoc}
*/
protected function getWhereInstance(): Where
{
return $this->where;
}

/**
* Get HAVING clause.
*/
public function getHaving(): WhereSelect
{
return $this->having;
}

/**
* {@inheritdoc}
*/
protected function getHavingInstance(): Where
{
return $this->having;
}

/**
* Get group by clauses array.
*/
Expand Down
Loading

0 comments on commit 0c4745e

Please sign in to comment.