Skip to content

Commit

Permalink
query builder: simplify join method interfaces (BC break!)
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Apr 10, 2020
1 parent c0215b3 commit 91ff937
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 88 deletions.
11 changes: 3 additions & 8 deletions doc/query-builder.texy
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,14 @@ $builder->limitBy(20, 10); // sets offset to 1O
INNER, LEFT and RIGHT JOIN
==========================

Choose from `innerJoin()`, `leftJoin()`, and `rightJoin()` methods. Each of them has the same signature. Arguments:
- from source name - from which table should be join created (you should reference to alias if used earlier),
- to expression - target expression, do not forget to escape the target table name,
- to alias - alias of the target, you can leave it NULL,
Choose from `joinInner()`, `joinLeft()`, and `joinRight()` methods. Each of them has the same signature. Arguments:
- to expression - target expression, do not forget to escape the target table name, you may define also an alias,
- on expression - ON clause expression,
- arguments for expressions.

The "from source name" is present for better validation. It disallows you to build join on table which has not been defined yet in the QueryBuilder.

/--php
$builder->from('[authors]', 'a');
$builder->leftJoin('a', '[books]', 'b', '[a.id] = [b.authorId] AND [b.title] = %s',
$title);
$builder->joinLeft('[books] AS [b]', '[a.id] = [b.authorId] AND [b.title] = %s', $title);

// will produce
// FROM [authors] AS [a]
Expand Down
91 changes: 53 additions & 38 deletions src/QueryBuilder/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class QueryBuilder

/**
* @var array|null
* @phpstan-var array<array{type: string, from: string, alias: string, table: string, on: string}>
* @phpstan-var array<array{type: string, from: string, alias: string, table: string, on: string}>|null
*/
private $join;

Expand Down Expand Up @@ -139,17 +139,10 @@ private function getSqlForSelect(): string

private function getFromClauses(): string
{
$knownAliases = array_flip($this->getKnownAliases());

$query = $this->from[0] . ($this->from[1] ? " AS [{$this->from[1]}]" : '');
foreach ((array) $this->join as $join) {
if (!isset($knownAliases[$join['from']])) {
throw new InvalidStateException("Unknown alias '{$join['from']}'.");
}

$query .= ' '
. $join['type'] . " JOIN {$join['table']} " . ($join['alias'] ? "AS [{$join['alias']}] " : '')
. 'ON (' . $join['on'] . ')';
foreach ((array) $this->join as $join) {
$query .= " $join[type] JOIN $join[table] ON ($join[on])";
}

return $query;
Expand Down Expand Up @@ -194,51 +187,90 @@ public function getFromAlias(): ?string

/**
* @phpstan-param array<int, mixed> $args
* @deprecated QueryBuilder::innerJoin() is deprecated. Use QueryBuilder::joinInner() without $fromAlias and with $toAlias included in $toExpression.
*/
public function innerJoin(string $fromAlias, string $toExpression, string $toAlias, string $onExpression, ...$args): self
{
return $this->join('INNER', $fromAlias, $toExpression, $toAlias, $onExpression, $args);
\trigger_error(
'QueryBuilder::innerJoin() is deprecated. Use QueryBuilder::joinInner() without $fromAlias and with $toAlias included in $toExpression.',
E_USER_DEPRECATED
);
return $this->joinInner("$toExpression AS [$toAlias]", $onExpression, $args);
}


/**
* @phpstan-param array<int, mixed> $args
* @deprecated QueryBuilder::leftJoin() is deprecated. Use QueryBuilder::joinLeft() without $fromAlias and with $toAlias included in $toExpression.
*/
public function leftJoin(string $fromAlias, string $toExpression, string $toAlias, string $onExpression, ...$args): self
{
return $this->join('LEFT', $fromAlias, $toExpression, $toAlias, $onExpression, $args);
\trigger_error(
'QueryBuilder::leftJoin() is deprecated. Use QueryBuilder::joinLeft() without $fromAlias and with $toAlias included in $toExpression.',
E_USER_DEPRECATED
);
return $this->joinLeft("$toExpression AS [$toAlias]", $onExpression, $args);
}


/**
* @phpstan-param array<int, mixed> $args
* @deprecated QueryBuilder::rightJoin() is deprecated. Use QueryBuilder::joinRight() without $fromAlias and with $toAlias included in $toExpression.
*/
public function rightJoin(string $fromAlias, string $toExpression, string $toAlias, string $onExpression, ...$args): self
{
return $this->join('RIGHT', $fromAlias, $toExpression, $toAlias, $onExpression, $args);
\trigger_error(
'QueryBuilder::rightJoin() is deprecated. Use QueryBuilder::joinRight() without $fromAlias and with $toAlias included in $toExpression.',
E_USER_DEPRECATED
);
return $this->joinRight("$toExpression AS [$toAlias]", $onExpression, $args);
}


/**
* @phpstan-return array<mixed>|null
* @phpstan-param array<int, mixed> $args
*/
public function getJoin(string $toAlias): ?array
public function joinInner(string $toExpression, string $onExpression, ...$args): self
{
return isset($this->join[$toAlias]) ? $this->join[$toAlias] : null;
return $this->join('INNER', $toExpression, $onExpression, $args);
}


/**
* @phpstan-param array<mixed> $args
* @phpstan-param array<int, mixed> $args
*/
private function join(string $type, string $fromAlias, string $toExpression, string $toAlias, string $onExpression, array $args): self
public function joinLeft(string $toExpression, string $onExpression, ...$args): self
{
return $this->join('LEFT', $toExpression, $onExpression, $args);
}


/**
* @phpstan-param array<int, mixed> $args
*/
public function joinRight(string $toExpression, string $onExpression, ...$args): self
{
return $this->join('RIGHT', $toExpression, $onExpression, $args);
}


public function removeJoins(): self
{
$this->join = null;
$this->args['join'] = null;
return $this;
}


/**
* @phpstan-param array<mixed> $args
*/
private function join(string $type, string $toExpression, string $onExpression, array $args): self
{
$this->dirty();
$this->join[$toAlias] = [
$this->join[$toExpression] = [
'type' => $type,
'from' => $fromAlias,
'table' => $toExpression,
'alias' => $toAlias,
'on' => $onExpression,
];
$this->pushArgs('join', $args);
Expand Down Expand Up @@ -448,21 +480,4 @@ private function pushArgs(string $type, array $args): void
{
$this->args[$type] = array_merge((array) $this->args[$type], $args);
}


/**
* @return string[]
*/
private function getKnownAliases(): array
{
$knownAliases = [];
if (isset($this->from)) {
$knownAliases[] = isset($this->from[1]) ? $this->from[1] : $this->from[0];
}
foreach ((array) $this->join as $join) {
$knownAliases[] = $join['alias'];
}

return $knownAliases;
}
}
45 changes: 3 additions & 42 deletions tests/cases/unit/QueryBuilderTest.joins.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,11 @@ class QueryBuilderJoinsTest extends QueryBuilderTestCase
$this->builder()
->from('one', 'o')
->select('*')
->leftJoin('o', 'two', 't', 'o.userId = t.userId')
->innerJoin('t', 'three', 'th', 't.userId = th.userId')
->rightJoin('th', 'four', 'f', 'th.userId = f.userId')
->joinLeft('two AS [t]', 'o.userId = t.userId')
->joinInner('three AS [th]', 't.userId = th.userId')
->joinRight('four AS [f]', 'th.userId = f.userId')
);
}


public function testValidateMissingAlias()
{
Assert::throws(function () {

$this->builder()
->from('one', 'o')
->innerJoin('t', 'three', 'th', 't.userId = th.userId')
->rightJoin('th', 'four', 'f', 'th.userId = f.userId')
->getQuerySql();

}, InvalidStateException::class, "Unknown alias 't'.");
}


public function testOverride()
{
$builder = $this->builder()
->from('one', 'o')
->leftJoin('o', 'two', 't', 'o.userId = t.userId')
->innerJoin('t', 'three', 't', 't.userId = th.userId');

$this->assertBuilder(
[
'SELECT * FROM one AS [o] ' .
'INNER JOIN three AS [t] ON (t.userId = th.userId)'
],
$builder
);

Assert::same([
'type' => 'INNER',
'from' => 't',
'table' => 'three',
'alias' => 't',
'on' => 't.userId = th.userId',
], $builder->getJoin('t'));
}
}


Expand Down

0 comments on commit 91ff937

Please sign in to comment.