Files
Woles-Framework/app/Core/Database/QueryBuilder.php

444 lines
10 KiB
PHP
Raw Normal View History

<?php
namespace App\Core\Database;
/**
* NovaCore Query Builder
* Simple query builder for database operations
*/
class QueryBuilder
{
private Connection $connection;
private string $table;
private array $select = ['*'];
private array $where = [];
private array $orderBy = [];
private array $groupBy = [];
private array $having = [];
private ?int $limit = null;
private ?int $offset = null;
private array $joins = [];
public function __construct(Connection $connection, string $table)
{
$this->connection = $connection;
$this->table = $table;
}
/**
* Select columns
*/
public function select(array $columns): self
{
$this->select = $columns;
return $this;
}
/**
* Add where condition
*/
public function where(string $column, $operator, $value = null): self
{
if ($value === null) {
$value = $operator;
$operator = '=';
}
$this->where[] = [
'column' => $column,
'operator' => $operator,
'value' => $value,
'boolean' => 'AND'
];
return $this;
}
/**
* Add OR where condition
*/
public function orWhere(string $column, $operator, $value = null): self
{
if ($value === null) {
$value = $operator;
$operator = '=';
}
$this->where[] = [
'column' => $column,
'operator' => $operator,
'value' => $value,
'boolean' => 'OR'
];
return $this;
}
/**
* Add where in condition
*/
public function whereIn(string $column, array $values): self
{
$this->where[] = [
'column' => $column,
'operator' => 'IN',
'value' => $values,
'boolean' => 'AND'
];
return $this;
}
/**
* Add where not in condition
*/
public function whereNotIn(string $column, array $values): self
{
$this->where[] = [
'column' => $column,
'operator' => 'NOT IN',
'value' => $values,
'boolean' => 'AND'
];
return $this;
}
/**
* Add where null condition
*/
public function whereNull(string $column): self
{
$this->where[] = [
'column' => $column,
'operator' => 'IS NULL',
'value' => null,
'boolean' => 'AND'
];
return $this;
}
/**
* Add where not null condition
*/
public function whereNotNull(string $column): self
{
$this->where[] = [
'column' => $column,
'operator' => 'IS NOT NULL',
'value' => null,
'boolean' => 'AND'
];
return $this;
}
/**
* Add order by clause
*/
public function orderBy(string $column, string $direction = 'ASC'): self
{
$this->orderBy[] = [
'column' => $column,
'direction' => strtoupper($direction)
];
return $this;
}
/**
* Add group by clause
*/
public function groupBy(string $column): self
{
$this->groupBy[] = $column;
return $this;
}
/**
* Add having clause
*/
public function having(string $column, $operator, $value): self
{
$this->having[] = [
'column' => $column,
'operator' => $operator,
'value' => $value,
'boolean' => 'AND'
];
return $this;
}
/**
* Add limit clause
*/
public function limit(int $limit): self
{
$this->limit = $limit;
return $this;
}
/**
* Add offset clause
*/
public function offset(int $offset): self
{
$this->offset = $offset;
return $this;
}
/**
* Add join clause
*/
public function join(string $table, string $first, string $operator, string $second, string $type = 'INNER'): self
{
$this->joins[] = [
'table' => $table,
'first' => $first,
'operator' => $operator,
'second' => $second,
'type' => $type
];
return $this;
}
/**
* Add left join clause
*/
public function leftJoin(string $table, string $first, string $operator, string $second): self
{
return $this->join($table, $first, $operator, $second, 'LEFT');
}
/**
* Add right join clause
*/
public function rightJoin(string $table, string $first, string $operator, string $second): self
{
return $this->join($table, $first, $operator, $second, 'RIGHT');
}
/**
* Get all results
*/
public function get(): array
{
$sql = $this->toSql();
$params = $this->getBindings();
return $this->connection->fetchAll($sql, $params);
}
/**
* Get first result
*/
public function first(): ?array
{
$this->limit(1);
$sql = $this->toSql();
$params = $this->getBindings();
return $this->connection->fetch($sql, $params);
}
/**
* Get count
*/
public function count(): int
{
$this->select = ['COUNT(*) as count'];
$result = $this->first();
return (int) $result['count'];
}
/**
* Insert data
*/
public function insert(array $data): int
{
$columns = array_keys($data);
$values = array_values($data);
$placeholders = array_fill(0, count($values), '?');
$sql = "INSERT INTO {$this->table} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
return $this->connection->execute($sql, $values);
}
/**
* Update data
*/
public function update(array $data): int
{
$columns = array_keys($data);
$values = array_values($data);
$set = [];
foreach ($columns as $column) {
$set[] = "{$column} = ?";
}
$sql = "UPDATE {$this->table} SET " . implode(', ', $set);
$params = $values;
if (!empty($this->where)) {
$sql .= " WHERE " . $this->buildWhereClause();
$params = array_merge($params, $this->getWhereBindings());
}
return $this->connection->execute($sql, $params);
}
/**
* Delete records
*/
public function delete(): int
{
$sql = "DELETE FROM {$this->table}";
$params = [];
if (!empty($this->where)) {
$sql .= " WHERE " . $this->buildWhereClause();
$params = $this->getWhereBindings();
}
return $this->connection->execute($sql, $params);
}
/**
* Build SQL query
*/
private function toSql(): string
{
$sql = "SELECT " . implode(', ', $this->select) . " FROM {$this->table}";
// Add joins
foreach ($this->joins as $join) {
$sql .= " {$join['type']} JOIN {$join['table']} ON {$join['first']} {$join['operator']} {$join['second']}";
}
// Add where clause
if (!empty($this->where)) {
$sql .= " WHERE " . $this->buildWhereClause();
}
// Add group by clause
if (!empty($this->groupBy)) {
$sql .= " GROUP BY " . implode(', ', $this->groupBy);
}
// Add having clause
if (!empty($this->having)) {
$sql .= " HAVING " . $this->buildHavingClause();
}
// Add order by clause
if (!empty($this->orderBy)) {
$orderBy = [];
foreach ($this->orderBy as $order) {
$orderBy[] = "{$order['column']} {$order['direction']}";
}
$sql .= " ORDER BY " . implode(', ', $orderBy);
}
// Add limit clause
if ($this->limit !== null) {
$sql .= " LIMIT {$this->limit}";
}
// Add offset clause
if ($this->offset !== null) {
$sql .= " OFFSET {$this->offset}";
}
return $sql;
}
/**
* Build where clause
*/
private function buildWhereClause(): string
{
$clauses = [];
foreach ($this->where as $index => $condition) {
$clause = '';
if ($index > 0) {
$clause .= " {$condition['boolean']} ";
}
if ($condition['operator'] === 'IN' || $condition['operator'] === 'NOT IN') {
$placeholders = array_fill(0, count($condition['value']), '?');
$clause .= "{$condition['column']} {$condition['operator']} (" . implode(', ', $placeholders) . ")";
} else {
$clause .= "{$condition['column']} {$condition['operator']} ?";
}
$clauses[] = $clause;
}
return implode('', $clauses);
}
/**
* Build having clause
*/
private function buildHavingClause(): string
{
$clauses = [];
foreach ($this->having as $index => $condition) {
$clause = '';
if ($index > 0) {
$clause .= " {$condition['boolean']} ";
}
$clause .= "{$condition['column']} {$condition['operator']} ?";
$clauses[] = $clause;
}
return implode('', $clauses);
}
/**
* Get all bindings
*/
private function getBindings(): array
{
$bindings = [];
// Add where bindings
$bindings = array_merge($bindings, $this->getWhereBindings());
// Add having bindings
foreach ($this->having as $condition) {
$bindings[] = $condition['value'];
}
return $bindings;
}
/**
* Get where bindings
*/
private function getWhereBindings(): array
{
$bindings = [];
foreach ($this->where as $condition) {
if ($condition['operator'] === 'IN' || $condition['operator'] === 'NOT IN') {
$bindings = array_merge($bindings, $condition['value']);
} else {
$bindings[] = $condition['value'];
}
}
return $bindings;
}
}