diff --git a/extdbms/lib/Configs/App.php b/extdbms/lib/Configs/App.php index f52ae68..b4a32f2 100644 --- a/extdbms/lib/Configs/App.php +++ b/extdbms/lib/Configs/App.php @@ -13,7 +13,6 @@ class App extends Core // .env 파일 로드 $dotenv = Dotenv::createImmutable(ROOT_PATH); $dotenv->load(); - DB::init(); // ✅ 여기서 초기화 parent::__construct(); } } diff --git a/extdbms/lib/Core/Model.php b/extdbms/lib/Core/Model.php index 2541529..00ef455 100644 --- a/extdbms/lib/Core/Model.php +++ b/extdbms/lib/Core/Model.php @@ -5,67 +5,17 @@ namespace lib\Core; use lib\Database\DB; use lib\Database\QueryBuilder; -abstract class Model +abstract class Model extends QueryBuilder { - - private bool $_debug = false; - protected static QueryBuilder $queryBuilder; - public function __construct() {} // + public function __construct() + { + DB::init(); // ✅ 여기서 초기화 + parent::__construct(DB::getPDO()); // QueryBuilder에 전달 + parent::table($this->getTable()); + parent::setDebug($_ENV['DATABASE_QUERY_DEBUG'] ?? $_SERVER['DATABASE_QUERY_DEBUG'] ?? false); + } abstract public function getTable(): string; abstract public function getPKField(): string; abstract public function getTitleField(): string; - final public function setDebug(bool $debug): void - { - $this->_debug = $debug; - } - final public function getDebug(): bool - { - return $this->_debug; - } - - public static function query(): QueryBuilder - { - if (!isset(self::$queryBuilder)) { - self::$queryBuilder = new QueryBuilder(pdo: DB::getPDO()); // Assuming DB::getPDO() gives PDO instance - } - return self::$queryBuilder->table((new static())->getTable()); - } - - public static function all(): array - { - return self::query()->get(); - } - public static function find($id): ?array - { - return self::query()->where((new static())->getPKField(), '=', $id)->first(); - } - public static function where(string $column, string $operator = "", mixed $value = ""): QueryBuilder - { - return self::query()->where($column, $operator, $value); - } - public static function whereNotIn(string $column, array $values): QueryBuilder - { - return self::query()->whereNotIn($column, $values); - } - public static function orWhereIn(string $column, array $values): QueryBuilder - { - return self::query()->orWhereIn($column, $values); - } - public static function count(): int - { - return self::query()->count(); - } - public static function insert(array $data): bool - { - return self::query()->insert($data); - } - public static function updateById($id, array $data): bool - { - return self::query()->where((new static())->getPKField(), '=', $id)->update($data); - } - public static function deleteById($id): bool - { - return self::query()->where((new static())->getPKField(), '=', $id)->delete(); - } } diff --git a/extdbms/lib/Database/DB.php b/extdbms/lib/Database/DB.php index ecd2108..e0be95c 100644 --- a/extdbms/lib/Database/DB.php +++ b/extdbms/lib/Database/DB.php @@ -7,25 +7,22 @@ use PDO; class DB { - private static PDO $pdo; + private static ?PDO $pdo = null; - public static function init(): void - { - $driver = $_ENV['DATABASE_DRIVER'] ?? $_SERVER['DATABASE_DRIVER'] ?? 'mysql'; - $host = $_ENV['DATABASE_HOST'] ?? $_SERVER['DATABASE_HOST'] ?? 'localhost'; - $dbname = $_ENV['DATABASE_DB'] ?? $_SERVER['DATABASE_DB'] ?? 'test'; - $charset = $_ENV['DATABASE_CHARSET'] ?? $_SERVER['DATABASE_CHARSET'] ?? 'utf8'; - $user = $_ENV['DATABASE_ID'] ?? $_SERVER['DATABASE_ID'] ?? 'root'; - $pass = $_ENV['DATABASE_PASSWORD'] ?? $_SERVER['DATABASE_PASSWORD'] ?? ''; - $dsn = sprintf("%s:host=%s;dbname=%s;charset=%s", $driver, $host, $dbname, $charset); - self::$pdo = new \PDO($dsn, $user, $pass); - self::$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - } + public static function init(): void {} public static function getPDO(): PDO { - if (!isset(self::$pdo)) { - throw new \Exception("PDO not initialized. Call DB::init() first."); + if (self::$pdo === null) { + $driver = $_ENV['DATABASE_DRIVER'] ?? $_SERVER['DATABASE_DRIVER'] ?? 'mysql'; + $host = $_ENV['DATABASE_HOST'] ?? $_SERVER['DATABASE_HOST'] ?? 'localhost'; + $dbname = $_ENV['DATABASE_DB'] ?? $_SERVER['DATABASE_DB'] ?? 'test'; + $charset = $_ENV['DATABASE_CHARSET'] ?? $_SERVER['DATABASE_CHARSET'] ?? 'utf8'; + $user = $_ENV['DATABASE_ID'] ?? $_SERVER['DATABASE_ID'] ?? 'root'; + $pass = $_ENV['DATABASE_PASSWORD'] ?? $_SERVER['DATABASE_PASSWORD'] ?? ''; + $dsn = sprintf("%s:host=%s;dbname=%s;charset=%s", $driver, $host, $dbname, $charset); + self::$pdo = new \PDO($dsn, $user, $pass); + self::$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); } return self::$pdo; } diff --git a/extdbms/lib/Database/QueryBuilder.php b/extdbms/lib/Database/QueryBuilder.php index c92967e..f1bf102 100644 --- a/extdbms/lib/Database/QueryBuilder.php +++ b/extdbms/lib/Database/QueryBuilder.php @@ -6,236 +6,168 @@ 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 = []; + private bool $_debug = false; + protected PDO $pdo; + protected string $latestQuery = ""; + protected string $table = ''; + protected array $select = ['*']; + protected array $where = []; + protected array $bindings = []; + protected array $order = []; + protected array $joins = []; + protected ?int $limit = null; + protected ?int $offset = null; - public function __construct(PDO $pdo) + protected function __construct(PDO $pdo) { $this->pdo = $pdo; } - + final public function setDebug(bool $debug): void + { + $this->_debug = $debug; + } + final public function getLastQuery(): string + { + return $this->latestQuery; + } 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부분분 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 + //Where절부분 + 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[] = "$column $operator $placeholder"; + $this->where[] = ['condition' => "$column $operator $placeholder", 'boolean' => 'AND']; $this->bindings[$placeholder] = $value; return $this; } - - public function orderBy(string $column, string $direction = 'ASC'): static + public function orWhere(string $column, mixed $operator = null, mixed $value = null): static { - $this->order[] = "$column $direction"; - return $this; + return $this->where($column, $operator, $value, "OR"); } - 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 + public function whereIn(string $column, array $values, string $boolean = 'AND', $conditon_boolean = "IN"): static { if (empty($values)) { - throw new \InvalidArgumentException("whereIn: values 배열이 비어있을 수 없습니다."); + throw new \InvalidArgumentException(__FUNCTION__ . ": values 배열이 비어있을 수 없습니다."); } - - $placeholders = implode(', ', array_fill(0, count($values), '?')); - $this->where[] = "$column IN ($placeholders)"; - $this->bindings = array_merge($this->bindings, $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; } - - public function whereNotIn(string $column, array $values, string $boolean = 'AND'): static + public function whereNotIn(string $column, array $values, $boolean = "AND", $conditon_boolean = "NOT IN"): static { if (empty($values)) { - throw new \InvalidArgumentException("whereNotIn: values 배열이 비어있을 수 없습니다."); + throw new \InvalidArgumentException(__FUNCTION__ . ": values 배열이 비어있을 수 없습니다."); } - - $placeholders = implode(', ', array_fill(0, count($values), '?')); - $this->where[] = "$column NOT IN ($placeholders)"; - $this->bindings = array_merge($this->bindings, $values); - - return $this; + return $this->whereIn($column, $values, $boolean, $conditon_boolean); } - - public function orWhereIn(string $column, array $values): static + public function orWhereIn(string $column, array $values, $boolean = "OR", $conditon_boolean = "IN"): 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)"; + if (empty($values)) { + throw new \InvalidArgumentException(__FUNCTION__ . ": values 배열이 비어있을 수 없습니다."); } - - $this->bindings[$placeholder] = $value; - return $this; + return $this->whereIn($column, $values, $boolean, $conditon_boolean); } + //Join public function join(string $table, string $on, string $type = 'INNER'): static { $this->joins[] = strtoupper($type) . " JOIN $table ON $on"; return $this; } + //Order + public function order(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부분 + public function limit(int $limit): static + { + $this->limit = $limit; + return $this; + } + public function offset(int $offset): static + { + $this->offset = $offset; + return $this; + } + + //Customer SQL 실행부분 public function raw(string $sql, array $bindings = []): array { $stmt = $this->pdo->prepare($sql); @@ -246,6 +178,98 @@ class QueryBuilder return $stmt->fetchAll(PDO::FETCH_ASSOC); } + //Result부분 + 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; + } + public function get(): array + { + $sql = $this->buildSelectSql(); + if ($this->_debug) { + echo "\nSQL 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); + } + + public function first(): ?array + { + $this->limit = 1; + $results = $this->get(); + return $results[0] ?? null; + } + + public function count(): int + { + $this->select = ['COUNT(*) as cnt']; + $results = $this->get(); + return (int)($results[0]['cnt'] ?? 0); + } + + //CUD부분 + 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(); + } + + 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(); + } + + 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관련련 public function beginTransaction(): void { $this->pdo->beginTransaction(); diff --git a/extdbms/lib/Services/ServiceService.php b/extdbms/lib/Services/ServiceService.php index cd8b80e..f2abf22 100644 --- a/extdbms/lib/Services/ServiceService.php +++ b/extdbms/lib/Services/ServiceService.php @@ -4,7 +4,6 @@ namespace lib\Services; use lib\Entities\ServiceEntity as Entity; use lib\Models\ServiceModel as Model; -use PDO; class ServiceService extends CommonService { @@ -68,7 +67,7 @@ class ServiceService extends CommonService $this->getModel()->where($where); $this->getModel()->where(["service_line" => $type, "service_status" => 'o']); $this->getModel()->where("service_sw BETWEEN '{$switchcode_begin}' AND '$switchcode_end'"); - $count = $this->getModel()->countAllResults(); + $count = $this->getModel()->count(); // echo "
" . $this->getModel()->getLastQuery(); return $count; }