Created
March 4, 2026 17:03
-
-
Save ollieread/707824b932e5997ac6e50093c57eeb03 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Clauses; | |
| use Engine\Database\Query\Contracts\Expression; | |
| /** | |
| * | |
| */ | |
| final class JoinClause implements Expression | |
| { | |
| /** | |
| * @var list<array{conjunction: 'AND'|'OR', sql: string, bindings: array<int|string, mixed>}> | |
| */ | |
| private array $conditions = []; | |
| /** | |
| * Add an ON condition to the join. | |
| * | |
| * @param string $left | |
| * @param string $operator | |
| * @param string $right | |
| * | |
| * @return $this | |
| */ | |
| public function on(string $left, string $operator, string $right): self | |
| { | |
| $this->conditions[] = [ | |
| 'conjunction' => 'AND', | |
| 'sql' => "{$left} {$operator} {$right}", | |
| 'bindings' => [], | |
| ]; | |
| return $this; | |
| } | |
| /** | |
| * Add an OR ON condition to the join. | |
| * | |
| * @param string $left | |
| * @param string $operator | |
| * @param string $right | |
| * | |
| * @return $this | |
| */ | |
| public function orOn(string $left, string $operator, string $right): self | |
| { | |
| $this->conditions[] = [ | |
| 'conjunction' => 'OR', | |
| 'sql' => "{$left} {$operator} {$right}", | |
| 'bindings' => [], | |
| ]; | |
| return $this; | |
| } | |
| /** | |
| * Add a WHERE condition to the join (bound value, not column reference). | |
| * | |
| * @param string $column | |
| * @param mixed $operatorOrValue | |
| * @param mixed $value | |
| * | |
| * @return $this | |
| */ | |
| public function where(string $column, mixed $operatorOrValue = null, mixed $value = null): self | |
| { | |
| if (func_num_args() === 2) { | |
| $value = $operatorOrValue; | |
| $operator = '='; | |
| } else { | |
| /** @var string $operator */ | |
| $operator = $operatorOrValue; | |
| } | |
| $this->conditions[] = [ | |
| 'conjunction' => 'AND', | |
| 'sql' => "{$column} {$operator} ?", | |
| 'bindings' => [$value], | |
| ]; | |
| return $this; | |
| } | |
| /** | |
| * Add an OR WHERE condition to the join. | |
| * | |
| * @param string $column | |
| * @param mixed $operatorOrValue | |
| * @param mixed $value | |
| * | |
| * @return $this | |
| */ | |
| public function orWhere(string $column, mixed $operatorOrValue = null, mixed $value = null): self | |
| { | |
| if (func_num_args() === 2) { | |
| $value = $operatorOrValue; | |
| $operator = '='; | |
| } else { | |
| /** @var string $operator */ | |
| $operator = $operatorOrValue; | |
| } | |
| $this->conditions[] = [ | |
| 'conjunction' => 'OR', | |
| 'sql' => "{$column} {$operator} ?", | |
| 'bindings' => [$value], | |
| ]; | |
| return $this; | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $parts = []; | |
| foreach ($this->conditions as $i => $condition) { | |
| $prefix = $i === 0 ? '' : " {$condition['conjunction']} "; | |
| $parts[] = $prefix . $condition['sql']; | |
| } | |
| return implode('', $parts); | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| $bindings = []; | |
| foreach ($this->conditions as $condition) { | |
| $bindings[] = $condition['bindings']; | |
| } | |
| return array_merge(...$bindings ?: [[]]); | |
| } | |
| public function isEmpty(): bool | |
| { | |
| return empty($this->conditions); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Clauses; | |
| use Closure; | |
| use Engine\Database\Query\Contracts\Expression; | |
| use Engine\Database\Query\Exceptions\InvalidExpressionException; | |
| use Engine\Database\Query\Expressions; | |
| /** | |
| * | |
| */ | |
| final class WhereClause implements Expression | |
| { | |
| /** | |
| * @var list<array{conjunction: 'AND'|'OR', expression: \Engine\Database\Query\Contracts\Expression, grouped: bool}> | |
| */ | |
| private array $conditions = []; | |
| /** | |
| * Add a condition to the query. | |
| * | |
| * @param 'AND'|'OR' $conjunction | |
| * @param string|\Closure $column | |
| * @param string|null $operator | |
| * @param mixed $value | |
| * | |
| * @return void | |
| */ | |
| private function condition( | |
| string $conjunction, | |
| string|Closure $column, | |
| ?string $operator, | |
| mixed $value, | |
| ): void | |
| { | |
| if ($column instanceof Closure) { | |
| $clause = new self(); | |
| $column($clause); | |
| if ($clause->isEmpty()) { | |
| throw InvalidExpressionException::emptyGroupedCondition(); | |
| } | |
| $this->conditions[] = [ | |
| 'conjunction' => $conjunction, | |
| 'expression' => $clause, | |
| 'grouped' => true, | |
| ]; | |
| } else { | |
| /** @var string $operator */ | |
| $this->conditions[] = [ | |
| 'conjunction' => $conjunction, | |
| 'expression' => Expressions::whereColumn($operator, $column, $value), | |
| 'grouped' => false, | |
| ]; | |
| } | |
| } | |
| /** | |
| * Add a basic where clause to the query. | |
| * | |
| * @param string|\Closure $column | |
| * @param mixed|null $operatorOrValue | |
| * @param mixed|null $value | |
| * | |
| * @return $this | |
| */ | |
| public function where(string|Closure $column, mixed $operatorOrValue = null, mixed $value = null): self | |
| { | |
| if ($column instanceof Closure) { | |
| $this->condition('AND', $column, null, null); | |
| } else { | |
| if (func_num_args() === 2) { | |
| $value = $operatorOrValue; | |
| $operator = '='; | |
| } else { | |
| /** @var string $operator */ | |
| $operator = $operatorOrValue; | |
| } | |
| $this->condition('AND', $column, $operator, $value); | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Add an "or where" clause to the query. | |
| * | |
| * @param string|\Closure $column | |
| * @param mixed|null $operatorOrValue | |
| * @param mixed|null $value | |
| * | |
| * @return $this | |
| */ | |
| public function orWhere(string|Closure $column, mixed $operatorOrValue = null, mixed $value = null): self | |
| { | |
| if ($column instanceof Closure) { | |
| $this->condition('OR', $column, null, null); | |
| } else { | |
| if (func_num_args() === 2) { | |
| $value = $operatorOrValue; | |
| $operator = '='; | |
| } else { | |
| /** @var string $operator */ | |
| $operator = $operatorOrValue; | |
| } | |
| $this->condition('OR', $column, $operator, $value); | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Add a "where null" clause to the query. | |
| * | |
| * @param string $column | |
| * | |
| * @return $this | |
| */ | |
| public function whereNull(string $column): self | |
| { | |
| $this->condition('AND', $column, 'IS NULL', null); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where not null" clause to the query. | |
| * | |
| * @param string $column | |
| * | |
| * @return $this | |
| */ | |
| public function orWhereNull(string $column): self | |
| { | |
| $this->condition('OR', $column, 'IS NULL', null); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where not null" clause to the query. | |
| * | |
| * @param string $column | |
| * | |
| * @return $this | |
| */ | |
| public function whereNotNull(string $column): self | |
| { | |
| $this->condition('AND', $column, 'IS NOT NULL', null); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where in" clause to the query. | |
| * | |
| * @param string $column | |
| * @param array<mixed>|\Engine\Database\Query\Contracts\Expression $values | |
| * | |
| * @return $this | |
| */ | |
| public function whereIn(string $column, array|Expression $values): self | |
| { | |
| if (is_array($values) && empty($values)) { | |
| throw InvalidExpressionException::emptyInClause($column); | |
| } | |
| if (is_array($values)) { | |
| $this->condition('AND', $column, 'IN', $values); | |
| } else { | |
| $this->whereRaw("{$column} IN (" . $values->toSql() . ")", $values->getBindings()); | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Add a "where not in" clause to the query. | |
| * | |
| * @param string $column | |
| * @param array<mixed>|\Engine\Database\Query\Contracts\Expression $values | |
| * | |
| * @return $this | |
| */ | |
| public function whereNotIn(string $column, array|Expression $values): self | |
| { | |
| if (is_array($values) && empty($values)) { | |
| throw InvalidExpressionException::emptyInClause($column); | |
| } | |
| if (is_array($values)) { | |
| $this->condition('AND', $column, 'NOT IN', $values); | |
| } else { | |
| $this->whereRaw("{$column} NOT IN (" . $values->toSql() . ")", $values->getBindings()); | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Add a raw where clause to the query. | |
| * | |
| * @param string $sql | |
| * @param array<int|string, mixed> $bindings | |
| * | |
| * @return $this | |
| */ | |
| public function whereRaw(string $sql, array $bindings = []): self | |
| { | |
| $this->conditions[] = [ | |
| 'conjunction' => 'AND', | |
| 'expression' => Expressions::raw($sql, $bindings), | |
| 'grouped' => false, | |
| ]; | |
| return $this; | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $parts = []; | |
| foreach ($this->conditions as $i => $condition) { | |
| $prefix = $i === 0 ? '' : " {$condition['conjunction']} "; | |
| $sql = $condition['expression']->toSql(); | |
| $parts[] = $prefix . ($condition['grouped'] ? "({$sql})" : $sql); | |
| } | |
| return implode('', $parts); | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| $bindings = []; | |
| foreach ($this->conditions as $condition) { | |
| $bindings[] = $condition['expression']->getBindings(); | |
| } | |
| return array_merge(...$bindings); | |
| } | |
| public function isEmpty(): bool | |
| { | |
| return empty($this->conditions); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Concerns; | |
| use Closure; | |
| use Engine\Database\Query\Clauses\JoinClause; | |
| trait HasJoinClause | |
| { | |
| /** | |
| * @var list<array{type: string, table: string, clause: \Engine\Database\Query\Clauses\JoinClause}> | |
| */ | |
| private array $joins = []; | |
| /** | |
| * Add an inner join to the query. | |
| * | |
| * @param string $table | |
| * @param string|\Closure $first | |
| * @param string|null $operator | |
| * @param string|null $second | |
| * | |
| * @return $this | |
| */ | |
| public function join(string $table, string|Closure $first, ?string $operator = null, ?string $second = null): static | |
| { | |
| return $this->addJoin('INNER', $table, $first, $operator, $second); | |
| } | |
| /** | |
| * Add a left join to the query. | |
| * | |
| * @param string $table | |
| * @param string|\Closure $first | |
| * @param string|null $operator | |
| * @param string|null $second | |
| * | |
| * @return $this | |
| */ | |
| public function leftJoin(string $table, string|Closure $first, ?string $operator = null, ?string $second = null): static | |
| { | |
| return $this->addJoin('LEFT', $table, $first, $operator, $second); | |
| } | |
| /** | |
| * Add a right join to the query. | |
| * | |
| * @param string $table | |
| * @param string|\Closure $first | |
| * @param string|null $operator | |
| * @param string|null $second | |
| * | |
| * @return $this | |
| */ | |
| public function rightJoin(string $table, string|Closure $first, ?string $operator = null, ?string $second = null): static | |
| { | |
| return $this->addJoin('RIGHT', $table, $first, $operator, $second); | |
| } | |
| /** | |
| * Add a cross join to the query. | |
| * | |
| * @param string $table | |
| * | |
| * @return $this | |
| */ | |
| public function crossJoin(string $table): static | |
| { | |
| $this->joins[] = ['type' => 'CROSS', 'table' => $table, 'clause' => new JoinClause()]; | |
| return $this; | |
| } | |
| private function addJoin(string $type, string $table, string|Closure $first, ?string $operator, ?string $second): static | |
| { | |
| $clause = new JoinClause(); | |
| if ($first instanceof Closure) { | |
| $first($clause); | |
| } else { | |
| /** @var string $operator */ | |
| $clause->on($first, $operator, $second); | |
| } | |
| $this->joins[] = ['type' => $type, 'table' => $table, 'clause' => $clause]; | |
| return $this; | |
| } | |
| private function buildJoinClause(): string | |
| { | |
| if (empty($this->joins)) { | |
| return ''; | |
| } | |
| $parts = []; | |
| foreach ($this->joins as $join) { | |
| $sql = " {$join['type']} JOIN {$join['table']}"; | |
| if (! $join['clause']->isEmpty()) { | |
| $sql .= ' ON ' . $join['clause']->toSql(); | |
| } | |
| $parts[] = $sql; | |
| } | |
| return implode('', $parts); | |
| } | |
| /** | |
| * @return array<int, mixed> | |
| */ | |
| private function getJoinBindings(): array | |
| { | |
| $bindings = []; | |
| foreach ($this->joins as $join) { | |
| $bindings = array_merge($bindings, $join['clause']->getBindings()); | |
| } | |
| return $bindings; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Concerns; | |
| trait HasLimitClause | |
| { | |
| private ?int $limitValue = null; | |
| private ?int $offsetValue = null; | |
| /** | |
| * Set the limit for the query. | |
| * | |
| * @param int $limit | |
| * | |
| * @return $this | |
| */ | |
| public function limit(int $limit): static | |
| { | |
| $this->limitValue = $limit; | |
| return $this; | |
| } | |
| /** | |
| * Set the offset for the query. | |
| * | |
| * @param int $offset | |
| * | |
| * @return $this | |
| */ | |
| public function offset(int $offset): static | |
| { | |
| $this->offsetValue = $offset; | |
| return $this; | |
| } | |
| private function buildLimitClause(): string | |
| { | |
| $sql = ''; | |
| if ($this->limitValue !== null) { | |
| $sql .= " LIMIT {$this->limitValue}"; | |
| } | |
| if ($this->offsetValue !== null) { | |
| $sql .= " OFFSET {$this->offsetValue}"; | |
| } | |
| return $sql; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Concerns; | |
| use Engine\Database\Query\Contracts\Expression; | |
| trait HasOrderByClause | |
| { | |
| /** | |
| * @var list<array{column: string|\Engine\Database\Query\Contracts\Expression, direction: string}> | |
| */ | |
| private array $orders = []; | |
| /** | |
| * Add an order by clause to the query. | |
| * | |
| * @param string|\Engine\Database\Query\Contracts\Expression $column | |
| * @param string $direction | |
| * | |
| * @return $this | |
| */ | |
| public function orderBy(string|Expression $column, string $direction = 'asc'): static | |
| { | |
| $this->orders[] = [ | |
| 'column' => $column, | |
| 'direction' => strtolower($direction) === 'desc' ? 'DESC' : 'ASC', | |
| ]; | |
| return $this; | |
| } | |
| private function buildOrderByClause(): string | |
| { | |
| if (empty($this->orders)) { | |
| return ''; | |
| } | |
| $clauses = array_map(function (array $order): string { | |
| $col = $order['column'] instanceof Expression | |
| ? $order['column']->toSql() | |
| : $order['column']; | |
| return "{$col} {$order['direction']}"; | |
| }, $this->orders); | |
| return ' ORDER BY ' . implode(', ', $clauses); | |
| } | |
| /** | |
| * @return array<int, mixed> | |
| */ | |
| private function getOrderByBindings(): array | |
| { | |
| $bindings = []; | |
| foreach ($this->orders as $order) { | |
| if ($order['column'] instanceof Expression) { | |
| $bindings = array_merge($bindings, $order['column']->getBindings()); | |
| } | |
| } | |
| return $bindings; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Concerns; | |
| use Closure; | |
| use Engine\Database\Query\Clauses\WhereClause; | |
| use Engine\Database\Query\Contracts\Expression; | |
| trait HasWhereClause | |
| { | |
| private(set) protected WhereClause $whereClause { | |
| get => $this->whereClause ?? $this->whereClause = new WhereClause(); | |
| } | |
| /** | |
| * Add a basic where clause to the query. | |
| * | |
| * @param string|\Closure $column | |
| * @param mixed|null $operatorOrValue | |
| * @param mixed|null $value | |
| * | |
| * @return $this | |
| */ | |
| public function where(string|Closure $column, mixed $operatorOrValue = null, mixed $value = null): static | |
| { | |
| $this->whereClause->where(...func_get_args()); | |
| return $this; | |
| } | |
| /** | |
| * Add an "or where" clause to the query. | |
| * | |
| * @param string|\Closure $column | |
| * @param mixed|null $operatorOrValue | |
| * @param mixed|null $value | |
| * | |
| * @return $this | |
| */ | |
| public function orWhere(string|Closure $column, mixed $operatorOrValue = null, mixed $value = null): static | |
| { | |
| $this->whereClause->orWhere(...func_get_args()); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where null" clause to the query. | |
| * | |
| * @param string $column | |
| * | |
| * @return $this | |
| */ | |
| public function whereNull(string $column): static | |
| { | |
| $this->whereClause->whereNull($column); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where not null" clause to the query. | |
| * | |
| * @param string $column | |
| * | |
| * @return $this | |
| */ | |
| public function orWhereNull(string $column): static | |
| { | |
| $this->whereClause->orWhereNull($column); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where not null" clause to the query. | |
| * | |
| * @param string $column | |
| * | |
| * @return $this | |
| */ | |
| public function whereNotNull(string $column): static | |
| { | |
| $this->whereClause->whereNotNull($column); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where in" clause to the query. | |
| * | |
| * @param string $column | |
| * @param array<mixed>|\Engine\Database\Query\Contracts\Expression $values | |
| * | |
| * @return $this | |
| */ | |
| public function whereIn(string $column, array|Expression $values): static | |
| { | |
| $this->whereClause->whereIn($column, $values); | |
| return $this; | |
| } | |
| /** | |
| * Add a "where not in" clause to the query. | |
| * | |
| * @param string $column | |
| * @param array<mixed>|\Engine\Database\Query\Contracts\Expression $values | |
| * | |
| * @return $this | |
| */ | |
| public function whereNotIn(string $column, array|Expression $values): static | |
| { | |
| $this->whereClause->whereNotIn($column, $values); | |
| return $this; | |
| } | |
| /** | |
| * Add a raw where clause to the query. | |
| * | |
| * @param string $sql | |
| * @param array<int|string, mixed> $bindings | |
| * | |
| * @return $this | |
| */ | |
| public function whereRaw(string $sql, array $bindings = []): static | |
| { | |
| $this->whereClause->whereRaw($sql, $bindings); | |
| return $this; | |
| } | |
| protected function hasWhereClause(): bool | |
| { | |
| return $this->whereClause->isEmpty() === false; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Engine\Database\Query\Contracts; | |
| interface Expression | |
| { | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string; | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Engine\Database\Query\Contracts; | |
| interface Query extends Expression | |
| { | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query; | |
| use Engine\Database\Query\Concerns\HasWhereClause; | |
| use Engine\Database\Query\Contracts\Query; | |
| /** | |
| * | |
| */ | |
| final class Delete implements Query | |
| { | |
| use HasWhereClause; | |
| public static function from(string $table): self | |
| { | |
| return new self($table); | |
| } | |
| private function __construct( | |
| private string $table, | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $where = $this->hasWhereClause() ? ' WHERE ' . $this->whereClause->toSql() : ''; | |
| return "DELETE FROM {$this->table}" . $where; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return $this->whereClause->getBindings(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Exceptions; | |
| use InvalidArgumentException; | |
| final class InvalidExpressionException extends InvalidArgumentException | |
| { | |
| public static function emptyGroupedCondition(): self | |
| { | |
| return new self('Grouped condition closure must add at least one condition.'); | |
| } | |
| public static function emptyInClause(string $column): self | |
| { | |
| return new self( | |
| sprintf('Cannot use an empty array for an IN clause on column "%s".', $column) | |
| ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query; | |
| use Engine\Database\Query\Contracts\Expression; | |
| use Engine\Database\Query\Expressions\ColumnEqualTo; | |
| use Engine\Database\Query\Expressions\ColumnGreaterThen; | |
| use Engine\Database\Query\Expressions\ColumnGreaterThenOrEqualTo; | |
| use Engine\Database\Query\Expressions\ColumnIn; | |
| use Engine\Database\Query\Expressions\ColumnIs; | |
| use Engine\Database\Query\Expressions\ColumnIsNotNull; | |
| use Engine\Database\Query\Expressions\ColumnIsNull; | |
| use Engine\Database\Query\Expressions\ColumnLessThan; | |
| use Engine\Database\Query\Expressions\ColumnLessThanOrEqualTo; | |
| use Engine\Database\Query\Expressions\ColumnNotEqualTo; | |
| use Engine\Database\Query\Expressions\ColumnNotIn; | |
| use Engine\Database\Query\Expressions\RawExpression; | |
| final class Expressions | |
| { | |
| public static function whereColumn(string $operator, string $column, mixed $value): Expression | |
| { | |
| return match (strtolower($operator)) { | |
| '=' => ColumnEqualTo::make($column, $value), | |
| '<' => ColumnLessThan::make($column, $value), | |
| '>' => ColumnGreaterThen::make($column, $value), | |
| '<=' => ColumnLessThanOrEqualTo::make($column, $value), | |
| '>=' => ColumnGreaterThenOrEqualTo::make($column, $value), | |
| 'is' => ColumnIs::make($column, $value), | |
| 'is null' => ColumnIsNull::make($column), | |
| 'is not null' => ColumnIsNotNull::make($column), | |
| '!=' => ColumnNotEqualTo::make($column, $value), | |
| 'in' => ColumnIn::make($column, $value), // @phpstan-ignore-line | |
| 'not in' => ColumnNotIn::make($column, $value), // @phpstan-ignore-line | |
| }; | |
| } | |
| /** | |
| * @param string $sql | |
| * @param array<int|string, mixed> $bindings | |
| * | |
| * @return \Engine\Database\Query\Contracts\Expression | |
| */ | |
| public static function raw(string $sql, array $bindings): Expression | |
| { | |
| return RawExpression::make($sql, $bindings); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnEqualTo implements Expression | |
| { | |
| public static function make(string $column, mixed $value): self | |
| { | |
| return new self($column, $value); | |
| } | |
| private function __construct( | |
| private string $column, | |
| private mixed $value | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} = ?"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return [$this->value]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnGreaterThen implements Expression | |
| { | |
| public static function make(string $column, mixed $value): self | |
| { | |
| return new self($column, $value); | |
| } | |
| private function __construct( | |
| private string $column, | |
| private mixed $value | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} > ?"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return [$this->value]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnGreaterThenOrEqualTo implements Expression | |
| { | |
| public static function make(string $column, mixed $value): self | |
| { | |
| return new self($column, $value); | |
| } | |
| private function __construct( | |
| private string $column, | |
| private mixed $value | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} >= ?"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return [$this->value]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnIn implements Expression | |
| { | |
| /** | |
| * @param string $column | |
| * @param array<mixed> $values | |
| * | |
| * @return self | |
| */ | |
| public static function make(string $column, array $values): self | |
| { | |
| return new self($column, $values); | |
| } | |
| /** | |
| * @param string $column | |
| * @param array<mixed> $values | |
| */ | |
| private function __construct( | |
| private string $column, | |
| private array $values | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $params = array_fill(0, count($this->values), '?'); | |
| $params = implode(', ', $params); | |
| return "{$this->column} IN ({$params})"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return $this->values; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnIs implements Expression | |
| { | |
| public static function make(string $column, mixed $value): self | |
| { | |
| return new self($column, $value); | |
| } | |
| private function __construct( | |
| private string $column, | |
| private mixed $value | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} IS ?"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return [$this->value]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnIsNotNull implements Expression | |
| { | |
| public static function make(string $column): self | |
| { | |
| return new self($column); | |
| } | |
| private function __construct( | |
| private string $column | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} IS NOT NULL"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return []; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnIsNull implements Expression | |
| { | |
| public static function make(string $column): self | |
| { | |
| return new self($column); | |
| } | |
| private function __construct( | |
| private string $column | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} IS NULL"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return []; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnLessThan implements Expression | |
| { | |
| public static function make(string $column, mixed $value): self | |
| { | |
| return new self($column, $value); | |
| } | |
| private function __construct( | |
| private string $column, | |
| private mixed $value | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} < ?"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return [$this->value]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnLessThanOrEqualTo implements Expression | |
| { | |
| public static function make(string $column, mixed $value): self | |
| { | |
| return new self($column, $value); | |
| } | |
| private function __construct( | |
| private string $column, | |
| private mixed $value | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} <= ?"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return [$this->value]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnNotEqualTo implements Expression | |
| { | |
| public static function make(string $column, mixed $value): self | |
| { | |
| return new self($column, $value); | |
| } | |
| private function __construct( | |
| private string $column, | |
| private mixed $value | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return "{$this->column} != ?"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return [$this->value]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class ColumnNotIn implements Expression | |
| { | |
| /** | |
| * @param string $column | |
| * @param array<mixed> $values | |
| * | |
| * @return self | |
| */ | |
| public static function make(string $column, array $values): self | |
| { | |
| return new self($column, $values); | |
| } | |
| /** | |
| * @param string $column | |
| * @param array<mixed> $values | |
| */ | |
| private function __construct( | |
| private string $column, | |
| private array $values | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $params = array_fill(0, count($this->values), '?'); | |
| $params = implode(', ', $params); | |
| return "{$this->column} NOT IN ({$params})"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return $this->values; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query\Expressions; | |
| use Engine\Database\Query\Contracts\Expression; | |
| final readonly class RawExpression implements Expression | |
| { | |
| /** | |
| * @param string $sql | |
| * @param array<int|string, mixed> $bindings | |
| * | |
| * @return self | |
| */ | |
| public static function make(string $sql, array $bindings): self | |
| { | |
| return new self($sql, $bindings); | |
| } | |
| /** | |
| * @param string $sql | |
| * @param array<int|string, mixed> $bindings | |
| */ | |
| private function __construct( | |
| private string $sql, | |
| private array $bindings | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return $this->sql; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return $this->bindings; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query; | |
| use Engine\Database\Query\Contracts\Query; | |
| /** | |
| * | |
| */ | |
| final class Insert implements Query | |
| { | |
| public static function into(string $table): self | |
| { | |
| return new self($table); | |
| } | |
| /** | |
| * @var array<string, mixed> | |
| */ | |
| private array $values = []; | |
| private function __construct( | |
| private string $table, | |
| ) | |
| { | |
| } | |
| /** | |
| * Set the values to insert. | |
| * | |
| * @param array<string, mixed> $values | |
| * | |
| * @return $this | |
| */ | |
| public function values(array $values): self | |
| { | |
| $this->values = $values; | |
| return $this; | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $columns = implode(', ', array_keys($this->values)); | |
| $placeholders = implode(', ', array_fill(0, count($this->values), '?')); | |
| return "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})"; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return array_values($this->values); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query; | |
| use Engine\Database\Query\Contracts\Expression; | |
| /** | |
| * | |
| */ | |
| final readonly class Raw implements Expression | |
| { | |
| /** | |
| * @param string $sql | |
| * @param array<int|string, mixed> $bindings | |
| */ | |
| public function __construct( | |
| private string $sql, | |
| private array $bindings = [], | |
| ) | |
| { | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| return $this->sql; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return $this->bindings; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query; | |
| use Engine\Database\Query\Concerns\HasJoinClause; | |
| use Engine\Database\Query\Concerns\HasLimitClause; | |
| use Engine\Database\Query\Concerns\HasOrderByClause; | |
| use Engine\Database\Query\Concerns\HasWhereClause; | |
| use Engine\Database\Query\Contracts\Expression; | |
| use Engine\Database\Query\Contracts\Query; | |
| /** | |
| * | |
| */ | |
| final class Select implements Query | |
| { | |
| use HasWhereClause; | |
| use HasJoinClause; | |
| use HasOrderByClause; | |
| use HasLimitClause; | |
| public static function from(string|Expression $table): self | |
| { | |
| return new self($table); | |
| } | |
| private bool $distinct = false; | |
| /** | |
| * @var array<string|\Engine\Database\Query\Contracts\Expression> | |
| */ | |
| private array $columns = []; | |
| private function __construct( | |
| private string|Expression $table, | |
| ) | |
| { | |
| } | |
| /** | |
| * Set the columns to select. | |
| * | |
| * @param string|\Engine\Database\Query\Contracts\Expression ...$columns | |
| * | |
| * @return $this | |
| */ | |
| public function columns(string|Expression ...$columns): self | |
| { | |
| $this->columns = $columns; | |
| return $this; | |
| } | |
| /** | |
| * Add a column to select. | |
| * | |
| * @param string|\Engine\Database\Query\Contracts\Expression $column | |
| * | |
| * @return $this | |
| */ | |
| public function addColumn(string|Expression $column): self | |
| { | |
| $this->columns[] = $column; | |
| return $this; | |
| } | |
| /** | |
| * Set the query to select distinct rows. | |
| * | |
| * @return $this | |
| */ | |
| public function distinct(): self | |
| { | |
| $this->distinct = true; | |
| return $this; | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $columns = empty($this->columns) ? '*' : implode(', ', array_map( | |
| fn (string|Expression $col) => $col instanceof Expression ? $col->toSql() : $col, | |
| $this->columns, | |
| )); | |
| $distinct = $this->distinct ? 'DISTINCT ' : ''; | |
| $table = $this->table instanceof Expression ? '(' . $this->table->toSql() . ')' : $this->table; | |
| $where = $this->hasWhereClause() ? ' WHERE ' . $this->whereClause->toSql() : ''; | |
| return "SELECT {$distinct}{$columns} FROM {$table}" | |
| . $this->buildJoinClause() | |
| . $where | |
| . $this->buildOrderByClause() | |
| . $this->buildLimitClause(); | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| $bindings = []; | |
| // Table subquery bindings | |
| if ($this->table instanceof Expression) { | |
| $bindings = $this->table->getBindings(); | |
| } | |
| // Column expression bindings | |
| foreach ($this->columns as $column) { | |
| if ($column instanceof Expression) { | |
| $bindings = array_merge($bindings, $column->getBindings()); | |
| } | |
| } | |
| return array_merge( | |
| $bindings, | |
| $this->getJoinBindings(), | |
| $this->whereClause->getBindings(), | |
| $this->getOrderByBindings(), | |
| ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| declare(strict_types=1); | |
| namespace Engine\Database\Query; | |
| use Engine\Database\Query\Concerns\HasWhereClause; | |
| use Engine\Database\Query\Contracts\Query; | |
| /** | |
| * | |
| */ | |
| final class Update implements Query | |
| { | |
| use HasWhereClause; | |
| public static function table(string $table): self | |
| { | |
| return new self($table); | |
| } | |
| /** | |
| * @var array<string, mixed> | |
| */ | |
| private array $sets = []; | |
| private function __construct( | |
| private string $table, | |
| ) | |
| { | |
| } | |
| /** | |
| * Set the column values to update. | |
| * | |
| * @param array<string, mixed> $values | |
| * | |
| * @return $this | |
| */ | |
| public function set(array $values): self | |
| { | |
| $this->sets = array_merge($this->sets, $values); | |
| return $this; | |
| } | |
| /** | |
| * Get the SQL representation of the expression. | |
| * | |
| * @return string | |
| */ | |
| public function toSql(): string | |
| { | |
| $setClauses = []; | |
| foreach (array_keys($this->sets) as $column) { | |
| $setClauses[] = "{$column} = ?"; | |
| } | |
| $where = $this->hasWhereClause() ? ' WHERE ' . $this->whereClause->toSql() : ''; | |
| return "UPDATE {$this->table} SET " . implode(', ', $setClauses) . $where; | |
| } | |
| /** | |
| * Get the bindings for the expression. | |
| * | |
| * @return array<int|string, mixed> | |
| */ | |
| public function getBindings(): array | |
| { | |
| return array_merge( | |
| array_values($this->sets), | |
| $this->whereClause->getBindings(), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment