- Add comprehensive error handling system with custom error pages - Implement professional enterprise-style design with Tailwind CSS - Create modular HMVC architecture with clean separation of concerns - Add security features: CSRF protection, XSS filtering, Argon2ID hashing - Include CLI tools for development workflow - Add error reporting dashboard with system monitoring - Implement responsive design with consistent slate color scheme - Replace all emoji icons with professional SVG icons - Add comprehensive test suite with PHPUnit - Include database migrations and seeders - Add proper exception handling with fallback pages - Implement template engine with custom syntax support - Add helper functions and facades for clean code - Include proper logging and debugging capabilities
444 lines
10 KiB
PHP
444 lines
10 KiB
PHP
<?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;
|
|
}
|
|
}
|