_authContext === null) { $this->_authContext = new AuthContext(); } return $this->_authContext; } final protected function addClassPaths(string $path): void { $this->_classPaths[] = $path; } final public function getPKField(): string { return $this->model->getPKField(); } final public function getTitleField(): string { return $this->model->getTitleField(); } final public function getClassPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string { return $isArray ? $this->_classPaths : implode($delimeter, $this->_classPaths); } final public function getNextPK(): int { $pkField = $this->getPKField(); $row = $this->model->selectMax($pkField)->get()->getRow(); return isset($row->{$pkField}) ? ((int)$row->{$pkField} + 1) : 1; } /** * 단일 엔티티를 조회합니다. */ abstract protected function getEntity_process(CommonEntity $entity): CommonEntity; final public function getEntity(string|int|array $where, ?string $message = null): ?object { try { $entity = is_array($where) ? $this->model->where($where)->first() : $this->model->find($where); if (!$entity) { return null; } // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 $entityClass = $this->getEntityClass(); if (!$entity instanceof $entityClass) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능:" . get_class($entity)); } return $this->getEntity_process($entity); } catch (DatabaseException $e) { $errorMessage = sprintf( "\n------DB Query 오류 (%s)-----\nQuery: %s\nError: %s\n------------------------------\n", static::class . '->' . __FUNCTION__, $this->model->getLastQuery() ?? "No Query Available", $e->getMessage() ); log_message('error', $errorMessage); throw new RuntimeException($errorMessage, $e->getCode(), $e); } catch (\Exception $e) { $errorMessage = sprintf( "\n------일반 오류 (%s)-----\nError: %s\n------------------------------\n", static::class . '->' . __FUNCTION__, $e->getMessage() ); throw new \Exception($errorMessage, $e->getCode(), $e); } } /** * 엔티티 배열 조회합니다. * @return CommonEntity|null CommonEntity 인스턴스 또는 찾지 못했을 경우 null */ protected function getEntities_process(mixed $where = null, array $columns = ['*'], array $entities = []): array { if ($where) { $this->model->where($where); } /** @var array<\App\Entities\CommonEntity> $results */ $results = $this->model->select(implode(',', $columns))->findAll(); log_message('debug', $this->model->getLastQuery()); foreach ($results as $entity) { $entities[] = $entity; } return $entities; } public function getEntities(?array $where = null, array $columns = ['*'], array $entities = []): array { try { /** @var array<\App\Entities\CommonEntity> $entities */ $entities = $this->getEntities_process($where, $columns, $entities); return $entities; } catch (DatabaseException $e) { $errorMessage = sprintf( "\n------DB Query 오류 (%s)-----\nQuery: %s\nError: %s\n------------------------------\n", static::class . '->' . __FUNCTION__, $this->model->getLastQuery() ?? "No Query Available", $e->getMessage() ); log_message('error', $errorMessage); throw new RuntimeException($errorMessage, $e->getCode(), $e); } catch (\Exception $e) { $errorMessage = sprintf( "\n------일반 오류 (%s)-----\nError: %s\n------------------------------\n", static::class . '->' . __FUNCTION__, $e->getMessage() ); throw new \Exception($errorMessage, $e->getCode(), $e); } } //CURD 결과처리용 protected function handle_save_result(mixed $result, int|string $uid): int|string { if ($result === false) { $errors = $this->model->errors(); $errorMsg = is_array($errors) ? implode(", ", $errors) : "DB 저장 작업이 실패했습니다."; throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . $errorMsg); } $pk = $uid; // 기본적으로 기존 $uid (업데이트의 경우) // AUTO_INCREMENT 필드를 사용하는 경우, INSERT 작업이라면 새로 생성된 ID를 가져옵니다. // INSERT 작업은 보통 $uid가 0 또는 null/빈 문자열일 때 실행됩니다. if ($this->model->useAutoIncrement() && (empty($uid) || $uid === 0)) { // CodeIgniter 모델의 getInsertID()를 사용하여 새로 생성된 PK를 확실히 가져옵니다. $insertID = $this->model->getInsertID(); if ($insertID > 0) { $pk = $insertID; } } elseif ($this->model->useAutoIncrement() && is_numeric($result) && (int)$result > 0) { // save()가 성공적인 INSERT 후 PK를 반환하는 경우를 대비 (CI4의 동작) $pk = (int)$result; } // 최종적으로 PK가 유효한지 확인합니다. if (empty($pk)) { $errors = $this->model->errors(); $errorMsg = is_array($errors) && !empty($errors) ? implode(", ", $errors) : "DB 작업 성공 후 PK를 확인할 수 없거나 모델 오류 발생:{$pk}"; throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . $errorMsg); } return $pk; } protected function save_process(CommonEntity $entity): object { // INSERT 시 Entity의 PK는 0 또는 NULL이어야 함 (DB가 ID를 생성하도록) $initialPK = $entity->getPK(); $result = $this->model->save($entity); log_message('debug', $this->model->getLastQuery()); // 최종적으로 DB에 반영된 PK를 반환받습니다. (UPDATE이면 기존 PK, INSERT이면 새 PK) $entity->{$this->getPKField()} = $this->handle_save_result($result, $initialPK); // handle_save_result에서 확인된 최종 PK를 사용하여 DB에서 최신 엔티티를 가져옴 return $entity; } //생성용 protected function create_process(array $formDatas): object { // 데이터 검증 if (!$this->getFormService()->validate($formDatas)) { throw new ValidationException(implode("\n", service('validation')->getErrors())); } // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 $entityClass = $this->getEntityClass(); $entity = new $entityClass($formDatas); if (!$entity instanceof $entityClass) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능"); } $entity = $this->save_process($entity); //입력/출력데이터 확인용 log_message('debug', var_export($formDatas, true)); log_message('debug', var_export($entity->toArray(), true)); return $entity; } final public function create(array $formDatas): object { $db = \Config\Database::connect(); try { //트랜잭션 도중 DB 오류가 발생하면 DatabaseException을 던지도록 설정 $db->transException(true)->transStart(); //관리자 정보추가용 $formDatas['user_uid'] = $this->getAuthContext()->getUID(); $entity = $this->create_process($formDatas); $db->transComplete(); return $entity; } catch (DatabaseException $e) { // DatabaseException을 포착하면 자동으로 롤백 처리됨 throw new RuntimeException(sprintf( "\n----[%s]에서 트랜잭션 실패: DB 오류----\n%s\n%s\n------------------------------\n", static::class . '->' . __FUNCTION__, $this->model->getLastQuery(), $e->getMessage() ), $e->getCode(), $e); } catch (\Throwable $e) { $db->transRollback(); // 예외 발생 시 수동으로 롤백 throw new RuntimeException($e->getMessage()); } } //수정용 protected function modify_process($entity, array $formDatas): object { $fields = array_keys($formDatas); $this->getFormService()->setFormFields($fields); $this->getFormService()->setFormRules('modify', $fields); // if (static::class === ServerService::class) { // var_dump($this->getFormService()->getFormRules()); // echo "
"; // var_dump($entity->toArray()); // echo "
"; // dd($formDatas); // } // 데이터 검증 if (!$this->getFormService()->validate($formDatas)) { throw new ValidationException( implode("\n", service('validation')->getErrors()) ); } //PK 추가 $pkField = $this->getPKField(); if (!isset($formDatas[$pkField]) && !empty($entity->getPK())) { // original에 있는 PK 값을 attributes에 명시적으로 복사합니다. $formDatas[$pkField] = $entity->getPK(); } foreach ($formDatas as $key => $value) { if ($value !== null) { $entity->$key = $value; } } $entity = $this->save_process($entity); //입력/출력데이터 확인용 log_message('debug', var_export($formDatas, true)); log_message('debug', var_export($entity->toArray(), true)); return $entity; } final public function modify(string|int $uid, array $formDatas): object { $db = \Config\Database::connect(); try { $db->transException(true)->transStart(); $entity = $this->getEntity($uid); if (!$entity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 $entityClass = $this->getEntityClass(); if (!$entity instanceof $entityClass) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능"); } //관리자 정보추가용 $formDatas['user_uid'] = $this->getAuthContext()->getUID(); $entity = $this->modify_process($entity, $formDatas); // 트랜잭션 완료 및 커밋 $db->transComplete(); return $entity; } catch (DatabaseException $e) { // DatabaseException을 포착하면 자동으로 롤백 처리됨 throw new RuntimeException(sprintf( "\n----[%s]에서 트랜잭션 실패: DB 오류----\n%s\n%s\n------------------------------\n", static::class . '->' . __FUNCTION__, $this->model->getLastQuery(), $e->getMessage() ), $e->getCode(), $e); } catch (\Throwable $e) { $db->transRollback(); // 예외 발생 시 수동으로 롤백 throw new RuntimeException($e->getMessage()); } } //배치 작업용 수정 protected function batchjob_process($entity, array $formDatas): object { $entity = $this->modify_process($entity, $formDatas); return $entity; } final public function batchjob(array $uids, array $formDatas): array { $db = \Config\Database::connect(); try { //트랜잭션 도중 DB 오류가 발생하면 DatabaseException을 던지도록 설정 $db->transException(true)->transStart(); $entities = []; foreach ($uids as $uid) { $entity = $this->getEntity($uid); if (!$entity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 $entityClass = $this->getEntityClass(); if (!$entity instanceof $entityClass) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능"); } //관리자 정보추가용 $formDatas['user_uid'] = $this->getAuthContext()->getUID(); $entities[] = $this->modify_process($entity, $formDatas); } return $entities; } catch (\Throwable $e) { $db->transRollback(); // 예외 발생 시 수동으로 롤백 throw new RuntimeException($e->getMessage()); } } //삭제용 (일반) protected function delete_process($entity): object { $result = $this->model->delete($entity->getPK()); log_message('debug', $this->model->getLastQuery()); if ($result === false) { $errors = $this->model->errors(); $errorMsg = is_array($errors) ? implode(", ", $errors) : "삭제 작업이 실패했습니다."; throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . $errorMsg); } return $entity; } final public function delete(string|int $uid): object { $db = \Config\Database::connect(); try { if (!$uid) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 삭제에 필요한 PrimaryKey 가 정의 되지 않았습니다."); } $entity = $this->getEntity($uid); if (!$entity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 $entityClass = $this->getEntityClass(); if (!$entity instanceof $entityClass) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능"); } $db->transException(true)->transStart(); $entity = $this->delete_process($entity); // 트랜잭션 완료 및 커밋 $db->transComplete(); return $entity; } catch (DatabaseException $e) { // DatabaseException을 포착하면 자동으로 롤백 처리됨 throw new RuntimeException(sprintf( "\n----[%s]에서 트랜잭션 실패: DB 오류----\n%s\n%s\n------------------------------\n", __FUNCTION__, $this->model->getLastQuery(), $e->getMessage() ), $e->getCode(), $e); } catch (\Throwable $e) { $db->transRollback(); // 예외 발생 시 수동으로 롤백 throw new RuntimeException($e->getMessage()); } } //삭제용 (배치 작업) protected function batchjob_delete_process($entity): object { $entity = $this->delete_process($entity); return $entity; } final public function batchjob_delete(array $uids): array { $db = \Config\Database::connect(); try { $db->transException(true)->transStart(); //일괄작업처리 $entities = []; foreach ($uids as $uid) { $entity = $this->getEntity($uid); if (!$entity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 $entityClass = $this->getEntityClass(); if (!$entity instanceof $entityClass) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능"); } $entities[] = $this->batchjob_delete_process($entity); } // 트랜잭션 완료 및 커밋 $db->transComplete(); return $entities; } catch (DatabaseException $e) { // DatabaseException을 포착하면 자동으로 롤백 처리됨 throw new RuntimeException(sprintf( "\n----[%s]에서 트랜잭션 실패: DB 오류----\n%s\n%s\n------------------------------\n", __FUNCTION__, $this->model->getLastQuery(), $e->getMessage() ), $e->getCode(), $e); } catch (\Throwable $e) { $db->transRollback(); // 예외 발생 시 수동으로 롤백 throw new RuntimeException($e->getMessage()); } } //Index용 final public function getTotalCount(): int { return $this->model->countAllResults(); } //Limit처리 final public function setLimit(int $perpage): void { $this->model->limit($perpage); } //Offset처리 final public function setOffset(int $offset): void { $this->model->offset($offset); } public function setFilter(string $field, mixed $filter_value): void { switch ($field) { default: $this->model->where("{$this->model->getTable()}.{$field}", $filter_value); break; } } //검색어조건절처리 public function setSearchWord(string $word): void { $this->model->orLike($this->model->getTable() . "." . $this->getTitleField(), $word, 'both'); } //날자검색 public function setDateFilter(string $start, string $end): void { $this->model->where(sprintf("%s.created_at >= '%s 00:00:00'", $this->model->getTable(), $start)); $this->model->where(sprintf("%s.created_at <= '%s 23:59:59'", $this->model->getTable(), $end)); } //OrderBy 처리 public function setOrderBy(mixed $field = null, mixed $value = null): void { if ($field !== null && $value !== null) { $this->model->orderBy(sprintf("%s.%s %s", $this->model->getTable(), $field, $value)); } else { $this->model->orderBy(sprintf("%s.%s %s", $this->model->getTable(), $this->getPKField(), "DESC")); } } }