266 lines
6.4 KiB
PHP
266 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace lib\Database;
|
|
|
|
use PDO;
|
|
|
|
class QueryBuilder
|
|
{
|
|
private PDO $pdo;
|
|
private string $table = '';
|
|
private array $select = ['*'];
|
|
private array $where = [];
|
|
private array $bindings = [];
|
|
private array $order = [];
|
|
private ?int $limit = null;
|
|
private ?int $offset = null;
|
|
private array $joins = [];
|
|
|
|
public function __construct(PDO $pdo)
|
|
{
|
|
$this->pdo = $pdo;
|
|
}
|
|
|
|
public function table(string $table): static
|
|
{
|
|
$this->table = $table;
|
|
return $this;
|
|
}
|
|
|
|
public function select(array|string $columns): static
|
|
{
|
|
$this->select = is_array($columns) ? $columns : explode(',', $columns);
|
|
return $this;
|
|
}
|
|
|
|
public function where(string $column, string $operator = "", mixed $value = ""): static
|
|
{
|
|
$placeholder = ':w_' . count($this->bindings);
|
|
$this->where[] = "$column $operator $placeholder";
|
|
$this->bindings[$placeholder] = $value;
|
|
return $this;
|
|
}
|
|
|
|
public function orderBy(string $column, string $direction = 'ASC'): static
|
|
{
|
|
$this->order[] = "$column $direction";
|
|
return $this;
|
|
}
|
|
|
|
public function limit(int $limit): static
|
|
{
|
|
$this->limit = $limit;
|
|
return $this;
|
|
}
|
|
|
|
public function offset(int $offset): static
|
|
{
|
|
$this->offset = $offset;
|
|
return $this;
|
|
}
|
|
|
|
public function when(bool $condition, callable $callback): static
|
|
{
|
|
if ($condition) {
|
|
$callback($this);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
public function rawWhere(string $expression, array $bindings = []): static
|
|
{
|
|
$this->where[] = "($expression)";
|
|
foreach ($bindings as $key => $value) {
|
|
$this->bindings[":raw_" . $key] = $value;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
public function get(): array
|
|
{
|
|
$sql = "SELECT " . implode(', ', $this->select) . " FROM {$this->table}";
|
|
|
|
if (!empty($this->where)) {
|
|
$sql .= " WHERE " . implode(' AND ', $this->where);
|
|
}
|
|
|
|
if (!empty($this->order)) {
|
|
$sql .= " ORDER BY " . implode(', ', $this->order);
|
|
}
|
|
|
|
if (!is_null($this->limit)) {
|
|
$sql .= " LIMIT :__limit";
|
|
$this->bindings[':__limit'] = $this->limit;
|
|
}
|
|
|
|
if (!is_null($this->offset)) {
|
|
$sql .= " OFFSET :__offset";
|
|
$this->bindings[':__offset'] = $this->offset;
|
|
}
|
|
|
|
$stmt = $this->pdo->prepare($sql);
|
|
foreach ($this->bindings as $placeholder => $value) {
|
|
$stmt->bindValue($placeholder, $value);
|
|
}
|
|
|
|
$stmt->execute();
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
public function first(): ?array
|
|
{
|
|
$this->limit(1);
|
|
$rows = $this->get();
|
|
return $rows[0] ?? null;
|
|
}
|
|
|
|
public function count(): int
|
|
{
|
|
$this->select = ['COUNT(*) AS cnt'];
|
|
$results = $this->get();
|
|
return (int)($results[0]['cnt'] ?? 0);
|
|
}
|
|
|
|
public function exists(): bool
|
|
{
|
|
return $this->count() > 0;
|
|
}
|
|
|
|
public function insert(array $data): bool
|
|
{
|
|
$columns = array_keys($data);
|
|
$placeholders = array_map(fn($col) => ':' . $col, $columns);
|
|
|
|
$sql = "INSERT INTO {$this->table} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
|
$stmt = $this->pdo->prepare($sql);
|
|
|
|
foreach ($data as $col => $value) {
|
|
$stmt->bindValue(':' . $col, $value);
|
|
}
|
|
|
|
return $stmt->execute();
|
|
}
|
|
|
|
public function update(array $data): bool
|
|
{
|
|
if (empty($this->where)) {
|
|
throw new \Exception("Update without WHERE is not allowed.");
|
|
}
|
|
|
|
$setClauses = [];
|
|
foreach ($data as $col => $val) {
|
|
$placeholder = ':u_' . $col;
|
|
$setClauses[] = "$col = $placeholder";
|
|
$this->bindings[$placeholder] = $val;
|
|
}
|
|
|
|
$sql = "UPDATE {$this->table} SET " . implode(', ', $setClauses);
|
|
|
|
if (!empty($this->where)) {
|
|
$sql .= " WHERE " . implode(' AND ', $this->where);
|
|
}
|
|
|
|
$stmt = $this->pdo->prepare($sql);
|
|
foreach ($this->bindings as $placeholder => $value) {
|
|
$stmt->bindValue($placeholder, $value);
|
|
}
|
|
|
|
return $stmt->execute();
|
|
}
|
|
|
|
public function delete(): bool
|
|
{
|
|
if (empty($this->where)) {
|
|
throw new \Exception("Delete without WHERE is not allowed.");
|
|
}
|
|
|
|
$sql = "DELETE FROM {$this->table} WHERE " . implode(' AND ', $this->where);
|
|
$stmt = $this->pdo->prepare($sql);
|
|
foreach ($this->bindings as $placeholder => $value) {
|
|
$stmt->bindValue($placeholder, $value);
|
|
}
|
|
|
|
return $stmt->execute();
|
|
}
|
|
|
|
public function whereIn(string $column, array $values, string $boolean = 'AND'): static
|
|
{
|
|
if (empty($values)) {
|
|
throw new \InvalidArgumentException("whereIn: values 배열이 비어있을 수 없습니다.");
|
|
}
|
|
|
|
$placeholders = implode(', ', array_fill(0, count($values), '?'));
|
|
$this->where[] = "$column IN ($placeholders)";
|
|
$this->bindings = array_merge($this->bindings, $values);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function whereNotIn(string $column, array $values, string $boolean = 'AND'): static
|
|
{
|
|
if (empty($values)) {
|
|
throw new \InvalidArgumentException("whereNotIn: values 배열이 비어있을 수 없습니다.");
|
|
}
|
|
|
|
$placeholders = implode(', ', array_fill(0, count($values), '?'));
|
|
$this->where[] = "$column NOT IN ($placeholders)";
|
|
$this->bindings = array_merge($this->bindings, $values);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function orWhereIn(string $column, array $values): static
|
|
{
|
|
return $this->whereIn($column, $values, 'OR');
|
|
}
|
|
|
|
public function orWhere(string $column, string $operator, mixed $value): static
|
|
{
|
|
$placeholder = ':w_' . count($this->bindings);
|
|
$condition = "$column $operator $placeholder";
|
|
|
|
if (empty($this->where)) {
|
|
$this->where[] = $condition;
|
|
} else {
|
|
$last = array_pop($this->where);
|
|
$this->where[] = "($last OR $condition)";
|
|
}
|
|
|
|
$this->bindings[$placeholder] = $value;
|
|
return $this;
|
|
}
|
|
|
|
public function join(string $table, string $on, string $type = 'INNER'): static
|
|
{
|
|
$this->joins[] = strtoupper($type) . " JOIN $table ON $on";
|
|
return $this;
|
|
}
|
|
|
|
public function raw(string $sql, array $bindings = []): array
|
|
{
|
|
$stmt = $this->pdo->prepare($sql);
|
|
foreach ($bindings as $key => $value) {
|
|
$stmt->bindValue(is_int($key) ? $key + 1 : $key, $value);
|
|
}
|
|
$stmt->execute();
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
public function beginTransaction(): void
|
|
{
|
|
$this->pdo->beginTransaction();
|
|
}
|
|
|
|
public function commit(): void
|
|
{
|
|
$this->pdo->commit();
|
|
}
|
|
|
|
public function rollBack(): void
|
|
{
|
|
if ($this->pdo->inTransaction()) {
|
|
$this->pdo->rollBack();
|
|
}
|
|
}
|
|
}
|