pdo = $pdo; } final final public function setDebug(bool $debug): void { $this->_debug = $debug; } final final public function getLastQuery(): string { return $this->latestQuery; } final public function table(string $table): static { $this->table = $table; return $this; } protected function buildSelectSql(): string { $sql = "SELECT " . implode(', ', $this->select) . " FROM {$this->table}"; // ⬇️ Join 처리 추가 if (!empty($this->joins)) { $sql .= ' ' . implode(' ', $this->joins); } if (!empty($this->where)) { $sql .= " WHERE " . $this->buildWhereSql(); } if (!empty($this->order)) { $sql .= " ORDER BY " . implode(', ', $this->order); } if ($this->limit !== null) { $sql .= " LIMIT {$this->limit}"; } if ($this->offset !== null) { $sql .= " OFFSET {$this->offset}"; } return $sql; } protected function buildWhereSql(): string { $sql = ''; foreach ($this->where as $index => $clause) { $boolean = $index === 0 ? '' : $clause['boolean'] . ' '; $sql .= $boolean . $clause['condition'] . ' '; } return trim($sql); } //Select부분분 final public function select(array|string $columns): static { $this->select = is_array($columns) ? $columns : explode(',', $columns); return $this; } //Where절부분 final public function where(array|string $column, mixed $operator = null, mixed $value = null): static { if (is_array($column)) { foreach ($column as $col => $val) { $this->where($col, '=', $val); // 재귀 호출 } return $this; } // where("col = NOW()") 형태의 raw 처리 if ($operator === null && $value === null) { $this->where[] = ['condition' => $column, 'boolean' => 'AND']; return $this; } // where("col", "val") → operator 생략 시 = 처리 if ($value === null) { $value = $operator; $operator = '='; } $placeholder = ':w_' . count($this->bindings); $this->where[] = ['condition' => "$column $operator $placeholder", 'boolean' => 'AND']; $this->bindings[$placeholder] = $value; return $this; } final public function orWhere(string $column, mixed $operator = null, mixed $value = null): static { return $this->where($column, $operator, $value, "OR"); } final public function whereIn(string $column, array $values, string $boolean = 'AND', $conditon_boolean = "IN"): static { if (empty($values)) { throw new \InvalidArgumentException(__FUNCTION__ . ": values 배열이 비어있을 수 없습니다."); } $placeholders = []; foreach ($values as $index => $value) { $placeholder = ":in_" . count($this->bindings); $placeholders[] = $placeholder; $this->bindings[$placeholder] = $value; } $condition = "$column $conditon_boolean (" . implode(', ', $placeholders) . ")"; $this->where[] = ['condition' => $condition, 'boolean' => $boolean]; return $this; } final public function whereNotIn(string $column, array $values, $boolean = "AND", $conditon_boolean = "NOT IN"): static { if (empty($values)) { throw new \InvalidArgumentException(__FUNCTION__ . ": values 배열이 비어있을 수 없습니다."); } return $this->whereIn($column, $values, $boolean, $conditon_boolean); } final public function orWhereIn(string $column, array $values, $boolean = "OR", $conditon_boolean = "IN"): static { if (empty($values)) { throw new \InvalidArgumentException(__FUNCTION__ . ": values 배열이 비어있을 수 없습니다."); } return $this->whereIn($column, $values, $boolean, $conditon_boolean); } //Join final public function join(string $table, string $on, string $type = 'INNER'): static { $this->joins[] = strtoupper($type) . " JOIN $table ON $on"; return $this; } //Order final public function orderBy(mixed $column, string $direction = 'ASC'): static { if (is_array($column)) { // 배열 형식의 여러 컬럼 정렬을 처리 foreach ($column as $col => $dir) { // 배열 키가 컬럼 이름이고 값이 방향임 $this->order[] = "$col $dir"; } } else { // 단일 컬럼에 대한 정렬 $this->order[] = "$column $direction"; } return $this; } //Limit부분 final public function limit(int $limit): static { $this->limit = $limit; return $this; } final public function offset(int $offset): static { $this->offset = $offset; return $this; } //Customer SQL 실행부분 final 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); } //Result부분 //binding까지 완료한 SQL문을 return함 final public function toRawSql(): string { $sql = $this->buildSelectSql(); $raw = $sql; foreach ($this->bindings as $key => $value) { $escaped = is_numeric($value) ? $value : $this->pdo->quote($value); // 명명된 바인딩 if (str_starts_with($key, ':')) { $raw = str_replace($key, $escaped, $raw); } else { // 물음표 바인딩인 경우 (whereIn 등) $raw = preg_replace('/\?/', $escaped, $raw, 1); } } return $raw; } final public function get(): array { $sql = $this->buildSelectSql(); if ($this->_debug) { echo "\n
SQL DEBUG:" . $this->toRawSql() . "
"; } $this->latestQuery = $this->toRawSql(); $stmt = $this->pdo->prepare($this->buildSelectSql()); foreach ($this->bindings as $key => $value) { $stmt->bindValue($key, $value); } $stmt->execute(); $this->where = []; $this->bindings = []; return $stmt->fetchAll(PDO::FETCH_ASSOC); } final public function first(): ?array { $this->limit = 1; $results = $this->get(); return $results[0] ?? null; } final public function count(string $select = "COUNT(*) as cnt"): int { $this->select = [$select]; $results = $this->get(); return (int)($results[0]['cnt'] ?? 0); } //CUD부분 final public function insert(array $data): bool { $columns = array_keys($data); $placeholders = array_map(fn($c) => ':' . $c, $columns); $sql = "INSERT INTO {$this->table} (" . implode(',', $columns) . ") VALUES (" . implode(',', $placeholders) . ")"; $stmt = $this->pdo->prepare($sql); foreach ($data as $col => $val) { $stmt->bindValue(':' . $col, $val); } return $stmt->execute(); } final public function update(array $data): bool { if (empty($this->where)) throw new \Exception("Update without WHERE is not allowed."); $setParts = []; foreach ($data as $col => $val) { $placeholder = ':u_' . $col; $setParts[] = "$col = $placeholder"; $this->bindings[$placeholder] = $val; } $sql = "UPDATE {$this->table} SET " . implode(', ', $setParts) . " WHERE " . implode(' AND ', $this->where); $stmt = $this->pdo->prepare($sql); foreach ($this->bindings as $k => $v) { $stmt->bindValue($k, $v); } return $stmt->execute(); } final 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 $k => $v) { $stmt->bindValue($k, $v); } return $stmt->execute(); } //transaction관련련 final public function beginTransaction(): void { $this->pdo->beginTransaction(); } final public function commit(): void { $this->pdo->commit(); } final public function rollBack(): void { if ($this->pdo->inTransaction()) { $this->pdo->rollBack(); } } }