From 209b86180c3adec8bffc59ef0472562dbe9dc40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A4=80=ED=9D=A0?= Date: Mon, 2 Mar 2026 12:07:14 +0900 Subject: [PATCH] daemon-idc init --- app/Controllers/AbstractWebController.php | 186 +++++----------------- app/Entities/UserEntity.php | 105 ++---------- app/Models/CommonModel.php | 127 ++------------- app/Services/CommonService.php | 110 +++++-------- app/Services/UserService.php | 44 ++--- 5 files changed, 118 insertions(+), 454 deletions(-) diff --git a/app/Controllers/AbstractWebController.php b/app/Controllers/AbstractWebController.php index 9aa8fd1..4da710a 100644 --- a/app/Controllers/AbstractWebController.php +++ b/app/Controllers/AbstractWebController.php @@ -1,13 +1,4 @@ 400 -// * critical/alert/emergency => 500 -// * info/notice/debug/default => 200 -// - RedirectResponse|ResponseInterface로 엄격 정리 -// ========================================================= namespace App\Controllers; @@ -18,26 +9,29 @@ use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Psr\Log\LoggerInterface; -use App\Exceptions\FormValidationException; +/** + * CommonController + * + * 이 클래스를 상속받는 모든 자식 클래스(UserController 등)는 + * 반드시 'PATH' 상수를 가지고 있음을 IDE에 알려줍니다. + * * @property-read string PATH // ⭐ 이 부분이 핵심입니다. + */ abstract class AbstractWebController extends Controller { use LogTrait; + protected $service = null; private array $_action_paths = []; private array $_viewDatas = []; private ?string $_title = null; - protected $layouts = []; - protected $service = null; - // --- 초기화 및 DI --- + public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) { parent::initController($request, $response, $logger); - helper('util'); } - final protected function getAuthContext(): AuthContext { return service('myauth')->getAuthContext(); @@ -46,13 +40,15 @@ abstract class AbstractWebController extends Controller protected function getTitle(): string { if ($this->_title === null) { + // 이 로직은 하위 클래스에서 service가 초기화되었다고 가정합니다. $this->_title = lang("{$this->service->getClassPaths(false)}.title"); } return $this->_title; } // --- 경로 및 뷰 데이터 관리 --- - final protected function addActionPaths(string $path): void + + final protected function addActionPaths(string $path) { $this->_action_paths[] = $path; } @@ -62,19 +58,24 @@ abstract class AbstractWebController extends Controller return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths); } - final protected function addViewDatas(string $key, mixed $value): void + final protected function addViewDatas(string $key, mixed $value) { $this->_viewDatas[$key] = $value; } final protected function getViewDatas(?string $key = null): mixed { - if ($key === null) + if ($key === null) { return $this->_viewDatas; + } return $this->_viewDatas[$key] ?? null; } // --- 공통 처리 로직 (Override 가능) --- + + /** + * 모든 액션 실행 전 공통 초기화 작업 + */ protected function action_init_process(string $action, array $formDatas = []): void { $this->addViewDatas('action', $action); @@ -84,49 +85,10 @@ abstract class AbstractWebController extends Controller } /** - * action_redirect_process - * ✅ AJAX 요청이면 RedirectResponse 대신 JSON으로 변환(방어) - * - * 상태코드 정책(고정): - * - warning/error => 400 - * - critical/alert/emergency => 500 - * - info/notice/debug/default => 200 + * 액션 성공 후 모달을 닫고 부모 창을 리로드하는 스크립트를 반환합니다. */ - protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse|ResponseInterface + protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse { - $resolvedRedirect = $redirect_url - ?? $this->getAuthContext()->popPreviousUrl() - ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths()); - - if ($this->request->isAJAX()) { - $error400 = ['warning', 'error']; - $error500 = ['critical', 'alert', 'emergency']; - - if (in_array($type, $error400, true)) { - log_message($type, $message); - return $this->response->setStatusCode(400)->setJSON([ - 'ok' => false, - 'message' => $message, - 'redirect' => $resolvedRedirect, - ]); - } - - if (in_array($type, $error500, true)) { - log_message($type, $message); - return $this->response->setStatusCode(500)->setJSON([ - 'ok' => false, - 'message' => $message, - 'redirect' => $resolvedRedirect, - ]); - } - - return $this->response->setStatusCode(200)->setJSON([ - 'ok' => true, - 'message' => $message, - 'redirect' => $resolvedRedirect, - ]); - } - switch ($type) { case 'warning': case 'error': @@ -134,104 +96,42 @@ abstract class AbstractWebController extends Controller case 'alert': case 'emergency': log_message($type, $message); - return redirect()->back()->withInput()->with('message', $message); - + $result = redirect()->back()->withInput()->with('message', $message); + break; case 'debug': case 'info': case 'notice': default: - return redirect()->to($resolvedRedirect)->with('message', $message); + $redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths()); + $result = redirect()->to($redirect_url)->with('message', $message); + break; } + return $result; } /** - * 뷰 렌더링 + * 뷰 경로와 데이터를 이용하여 최종 HTML을 렌더링합니다. */ protected function action_render_process(string $view_file, array $viewDatas, ?string $template_path = null): string { - helper(['form', 'utility']); + helper(['form', 'IconHelper', 'utility']); + $config = config('Layout'); + $layoutConfig = $config->layouts[$viewDatas['layout']['path']] ?? []; - $baseViewPath = trim($viewDatas['layout']['path'], '/'); - if ($template_path) - $baseViewPath .= '/' . trim($template_path, '/'); - - $viewName = $baseViewPath . '/' . ltrim($view_file, '/'); - - return view($viewName, [ + $viewDatas['layout'] = array_merge($layoutConfig, $viewDatas['layout']); + $view_path = $viewDatas['layout']['path']; + if ($template_path) { + $view_path .= '/' . $template_path; + } + // dd($view_path); + //최종 ViewPath + $viewDatas['view_path'] = $view_path; + helper([__FUNCTION__]); + return view($view_path . '/' . $view_file, [ 'viewDatas' => [ ...$viewDatas, - 'forms' => [ - 'attributes' => ['method' => 'post'], - 'hiddens' => [], - ], - ], + 'forms' => ['attributes' => ['method' => "post",], 'hiddens' => []], + ] ]); } - - // ========================================================= - // 공통화: runAction / okResponse / failResponse - // ========================================================= - protected function stringifyError(mixed $x): string - { - if (is_string($x)) - return $x; - - if ($x instanceof \Throwable) { - return $x->getMessage(); - } - - if (is_array($x) || is_object($x)) { - $json = json_encode($x, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - return $json !== false ? $json : print_r($x, true); - } - - return (string) $x; - } - - protected function runAction(string $action, callable $core): mixed - { - try { - return $core(); - } catch (FormValidationException $e) { - return $this->failResponse($action, $e); - } catch (\Throwable $e) { - return $this->failResponse($action, $e); - } - } - - protected function okResponse(string $message, array $payload = [], ?string $redirectUrl = null): RedirectResponse|ResponseInterface - { - if ($this->request->isAJAX()) { - return $this->response->setStatusCode(200)->setJSON(array_merge( - ['ok' => true, 'message' => $message], - $payload - )); - } - return $this->action_redirect_process('info', $message, $redirectUrl); - } - - protected function failResponse(string $action, \Throwable $e, ?string $humanPrefix = null): RedirectResponse|ResponseInterface - { - if ($e instanceof FormValidationException) { - if ($this->request->isAJAX()) { - return $this->response->setStatusCode(422)->setJSON([ - 'ok' => false, - 'errors' => $e->errors, - ]); - } - // ✅ redirect에는 string만 넣는다 - return $this->action_redirect_process('error', $e->getMessage()); - } - - if ($this->request->isAJAX()) { - return $this->response->setStatusCode(500)->setJSON([ - 'ok' => false, - 'message' => static::class . '->' . $action . "에서 오류:" . $e->getMessage(), - ]); - } - - $msg = $humanPrefix ? ($humanPrefix . $e->getMessage()) : $e->getMessage(); - // ✅ redirect에는 string만 넣는다 - return $this->action_redirect_process('error', $msg); - } } diff --git a/app/Entities/UserEntity.php b/app/Entities/UserEntity.php index d87467b..93dee44 100644 --- a/app/Entities/UserEntity.php +++ b/app/Entities/UserEntity.php @@ -10,113 +10,34 @@ class UserEntity extends CommonEntity const PK = Model::PK; const TITLE = Model::TITLE; - // ✅ role은 반드시 "문자열" 기본값 (DB 저장형) - protected $attributes = [ - 'id' => '', - 'passwd' => '', - 'name' => '', - 'email' => '', - 'mobile' => null, - 'role' => '', // ✅ array 금지 - 'status' => '', - ]; - public function __construct(array|null $data = null) { parent::__construct($data); + $this->nullableFields = [ + ...$this->nullableFields, + 'mobile', + ]; } public function getID(): string { - return (string) ($this->attributes['id'] ?? ''); + return $this->id; } public function getPassword(): string { - return (string) ($this->attributes['passwd'] ?? ''); + return $this->passwd; } - - /** - * role을 "배열"로 반환 (DB에는 CSV/JSON/배열 무엇이든 복구) - */ - public function getRole(): array + public function getRole(): string { - $role = $this->attributes['role'] ?? null; - - if (is_array($role)) { - return array_values(array_filter($role, fn($v) => (string) $v !== '')); - } - - if (is_string($role) && $role !== '') { - // JSON 시도 - $decoded = json_decode($role, true); - if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { - $clean = array_map( - fn($item) => trim((string) ($item ?? ''), " \t\n\r\0\x0B\""), - $decoded - ); - return array_values(array_filter($clean, fn($v) => $v !== '')); - } - - // CSV fallback - $parts = explode(DEFAULTS["DELIMITER_COMMA"], $role); - $clean = array_map( - fn($item) => trim((string) ($item ?? ''), " \t\n\r\0\x0B\""), - $parts - ); - return array_values(array_filter($clean, fn($v) => $v !== '')); - } - - return []; + return $this->role; } - - /** - * ✅ CI4 뮤테이터: "return 값"이 attributes에 저장됨 - * - 빈값이면 기존값 유지 (create에서 required면 validate에서 걸러짐) - */ - public function setPasswd($password): string + public function getEmail(): string { - // null/'' 이면 기존값 유지 - if (!is_string($password) || $password === '') { - return (string) ($this->attributes['passwd'] ?? ''); - } - - return password_hash($password, PASSWORD_BCRYPT); + return $this->email; } - - /** - * ✅ role은 최종적으로 "CSV 문자열"로 저장 (DB 안전) - */ - public function setRole($role): string + public function getMobile(): ?string { - $roleArray = []; - - if (is_string($role)) { - $clean = trim($role, " \t\n\r\0\x0B\""); - if ($clean !== '') { - // JSON 문자열 가능성도 있어서 먼저 JSON 시도 - $decoded = json_decode($clean, true); - if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { - $roleArray = $decoded; - } else { - $roleArray = explode(DEFAULTS["DELIMITER_COMMA"], $clean); - } - } - } elseif (is_array($role)) { - $roleArray = $role; - } else { - // 그 외 타입은 안전하게 빈값 처리 - $roleArray = []; - } - - $cleaned = array_map( - fn($item) => trim((string) ($item ?? ''), " \t\n\r\0\x0B\""), - $roleArray - ); - - $roleArray = array_values(array_filter($cleaned, fn($v) => $v !== '')); - - // ✅ 무조건 문자열 반환 (빈 배열이면 '') - return implode(DEFAULTS["DELIMITER_COMMA"], $roleArray); + return $this->mobile; } -} +} \ No newline at end of file diff --git a/app/Models/CommonModel.php b/app/Models/CommonModel.php index 81e16c9..8052fd3 100644 --- a/app/Models/CommonModel.php +++ b/app/Models/CommonModel.php @@ -9,15 +9,19 @@ abstract class CommonModel extends Model protected $table = ''; protected $primaryKey = ''; protected $useAutoIncrement = true; - + // protected $returnType = 'array'; + //true이면 모든 delete * 메소드 호출은 실제로 행을 삭제하는 것이 아니라 플래그를 데이터베이스로 설정 protected $useSoftDeletes = false; protected $protectFields = true; protected $allowedFields = []; - + // $allowEmptyInserts = false (기본값): 삽입할 데이터가 전혀 없는 경우, CI4는 오류를 발생시키며 쿼리 실행을 막습니다. (보안 및 데이터 무결성 목적) + // $allowEmptyInserts = true: 삽입할 데이터가 없어도 INSERT INTO table_name () VALUES () 같은 빈 쿼리 실행을 허용합니다 (극히 드문 경우에 사용). protected bool $allowEmptyInserts = false; protected bool $updateOnlyChanged = true; - - protected $useEmptyStringIfNull = false; // NULL도 DB로 보내기 + // protected $useEmptyStringIfNull = true; (기본값) + // 이 기본 설정 때문에 PHP의 null 값이 데이터베이스로 전달될 때 실제 SQL의 NULL 키워드가 아닌 **빈 문자열 ('')**로 변환되고 있습니다. + // 그리고 데이터베이스(MySQL 등)의 설정에 따라 빈 문자열이 업데이트 쿼리에서 무시되거나, 해당 컬럼의 기존 값이 유지되는 현상이 발생합니다. + protected $useEmptyStringIfNull = false; //NULL값도 넣을려면 false protected array $casts = []; protected array $castHandlers = []; @@ -37,148 +41,37 @@ abstract class CommonModel extends Model // Callbacks protected $allowCallbacks = true; - - /** - * ✅ 변경: - * - beforeInsert: emptyStringToNull + applyDbDefaultsOnInsert - * - beforeUpdate: emptyStringToNull만 유지 (UPDATE에서는 DB default를 쓰려고 컬럼을 빼면 위험/의도와 다름) - */ - protected $beforeInsert = ['emptyStringToNull', 'applyDbDefaultsOnInsert']; + protected $beforeInsert = []; //Field 값이 NULL일 경우 DB Default값 적용용 protected $afterInsert = []; - protected $beforeUpdate = ['emptyStringToNull']; + protected $beforeUpdate = []; //Field 값이 NULL일 경우 DB Default값 적용용 protected $afterUpdate = []; - protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; - /** - * 빈 문자열을 NULL로 바꾸고 싶은 필드들 (모델별 override) - * - 예: FK, 숫자필드 등 - */ - protected array $nullableFields = []; - - /** - * ✅ 추가: DB DEFAULT를 “사용하고 싶은” 필드들 (모델별 override) - * - INSERT 시 값이 null/''/공백이면 payload에서 제거(unset)해서 DB default가 동작하게 함 - * - UPDATE에서는 적용하지 않음 (비우기 가능) - */ - protected array $allowedDbDefaultFields = []; - - /** - * 공백문자열도 빈값으로 취급할지 정책 - */ - protected bool $dbDefaultTreatWhitespaceAsEmpty = true; - protected function __construct() { parent::__construct(); } - final public function getTable(): string { return constant("static::TABLE"); } - final public function getPKField(): string { return constant("static::PK"); } - final public function getTitleField(): string { return constant("static::TITLE"); } - final public function useAutoIncrement(): bool { return $this->useAutoIncrement; } - final public function getAllowedFields(): array { return $this->allowedFields; } - - /** - * 기존 로직 유지: - * - nullableFields에 지정된 필드만 '' => null 로 변환 - */ - protected function emptyStringToNull(array $data): array - { - if (!isset($data['data']) || !is_array($data['data'])) { - return $data; - } - - if (empty($this->nullableFields)) { - return $data; - } - - foreach ($this->nullableFields as $field) { - if (!array_key_exists($field, $data['data'])) { - continue; - } - - $v = $data['data'][$field]; - - if (is_string($v)) { - $v = trim($v); - $data['data'][$field] = ($v === '') ? null : $v; - } else { - if ($v === '') { - $data['data'][$field] = null; - } - } - } - - return $data; - } - - /** - * ✅ 추가 로직: - * INSERT 때만 DB DEFAULT를 쓰고 싶은 필드를 payload에서 제거(unset) - * - * - allowedDbDefaultFields에 있는 필드만 처리 - * - 값이 null / '' / (옵션)공백문자열 이면 unset - * - 이렇게 하면 INSERT 쿼리에서 컬럼 자체가 빠져서 DB default가 적용됨 - */ - protected function applyDbDefaultsOnInsert(array $data): array - { - if (!isset($data['data']) || !is_array($data['data'])) { - return $data; - } - - if (empty($this->allowedDbDefaultFields)) { - return $data; - } - - foreach ($this->allowedDbDefaultFields as $field) { - if (!array_key_exists($field, $data['data'])) { - continue; - } - - $v = $data['data'][$field]; - - // null이면 제거 - if ($v === null) { - unset($data['data'][$field]); - continue; - } - - // 문자열이면 '' 또는 (옵션)trim 후 '' 이면 제거 - if (is_string($v)) { - if ($v === '') { - unset($data['data'][$field]); - continue; - } - if ($this->dbDefaultTreatWhitespaceAsEmpty && trim($v) === '') { - unset($data['data'][$field]); - continue; - } - } - } - - return $data; - } } diff --git a/app/Services/CommonService.php b/app/Services/CommonService.php index 2d921ee..c1de3e8 100644 --- a/app/Services/CommonService.php +++ b/app/Services/CommonService.php @@ -2,13 +2,13 @@ namespace App\Services; -use App\Forms\CommonForm; -use App\Entities\CommonEntity; -use App\Models\CommonModel; -use App\Libraries\AuthContext; -use CodeIgniter\Database\Exceptions\DatabaseException; -use App\Exceptions\FormValidationException; use RuntimeException; +use App\Forms\CommonForm; +use App\Models\CommonModel; +use App\Entities\CommonEntity; +use App\Libraries\AuthContext; +use App\Exceptions\FormValidationException; +use CodeIgniter\Database\Exceptions\DatabaseException; abstract class CommonService { @@ -25,6 +25,7 @@ abstract class CommonService protected function __construct(protected CommonModel $model) { } + abstract public function getEntityClass(): string; /** @@ -38,9 +39,6 @@ abstract class CommonService $result = $callback($db); $db->transComplete(); return $result; - } catch (FormValidationException $e) { - $db->transRollback(); - throw $e; // ✅ 이거 필수 } catch (DatabaseException $e) { $errorMessage = sprintf( "\n----[%s]에서 트랜잭션 실패: DB 오류----\n%s\n%s\n------------------------------\n", @@ -52,7 +50,7 @@ abstract class CommonService throw new RuntimeException($errorMessage, $e->getCode(), $e); } catch (\Throwable $e) { $db->transRollback(); - throw $e; // ✅ 여기서도 RuntimeException으로 감싸지 말 것 (권장) + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); } } @@ -201,46 +199,19 @@ abstract class CommonService } } //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): CommonEntity { try { - // INSERT 시 Entity의 PK는 0 또는 NULL이어야 함 (DB가 ID를 생성하도록) - $initialPK = $entity->getPK(); - $result = $this->model->save($entity); - log_message('debug', __FUNCTION__ . ":" . var_export($entity, true)); - log_message('debug', __FUNCTION__ . ":" . $this->model->getLastQuery()); - // 최종적으로 DB에 반영된 PK를 반환받습니다. (UPDATE이면 기존 PK, INSERT이면 새 PK) - $entity->{$this->getPKField()} = $this->handle_save_result($result, $initialPK); - // handle_save_result에서 확인된 최종 PK를 사용하여 DB에서 최신 엔티티를 가져옴 + if (!$this->model->save($entity)) { + $errors = $this->model->errors(); + $errorMsg = is_array($errors) ? implode(", ", $errors) : "DB 저장 작업이 실패했습니다."; + throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . $errorMsg); + } + // CodeIgniter 모델의 getInsertID()를 사용하여 새로 생성된 PK를 확실히 가져옵니다. + if ($this->model->useAutoIncrement()) { + $entity->{$this->getPKField()} = $this->model->getInsertID(); + } return $entity; } catch (\Throwable $e) { log_message('debug', __FUNCTION__ . ":" . var_export($entity, true)); @@ -250,7 +221,7 @@ abstract class CommonService } //Action 작업시 field에따른 Hook처리(각 Service에서 override); - protected function actionForm_fieldhook_process(string $field, $value, array $formDatas): array + protected function fieldhook_process(string $field, $value, array $formDatas): array { return $formDatas; } @@ -260,21 +231,21 @@ abstract class CommonService { try { $actionForm = $this->getActionForm(); - if ($actionForm instanceof CommonForm) { - $actionForm->action_init_process('create', $formDatas); - // log_message('debug', 'BEFORE hook CREATE FORMDATA:' . print_r($formDatas ?? null, true)); - foreach ($formDatas as $field => $value) { - $formDatas = $this->actionForm_fieldhook_process($field, $value, $formDatas); - } - // log_message('debug', 'AFTER hook CREATE FORMDATA:' . print_r($formDatas ?? null, true)); - $actionForm->validate($formDatas); // ✅ 여기서 검증 + if (!$actionForm instanceof CommonForm) { + throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: actionForm이 정의되지 않았습니다."); + } + $actionForm->action_init_process('create', $formDatas); + $actionForm->validate($formDatas); // ✅ 여기서 검증 + // 검증 통과 후 엔티티 반영용 + foreach ($formDatas as $field => $value) { + $formDatas = $this->fieldhook_process($field, $value, $formDatas); } - $entityClass = $this->getEntityClass(); $entity = new $entityClass($formDatas); if (!$entity instanceof $entityClass) { throw new RuntimeException("Return Type은 {$entityClass}만 가능"); } + $entity->fill($formDatas); return $this->save_process($entity); } catch (FormValidationException $e) { throw $e; // ✅ 감싸지 말고 그대로 @@ -286,34 +257,26 @@ abstract class CommonService final public function create(array $formDatas): CommonEntity { return $this->dbTransaction(function () use ($formDatas) { - $formDatas['user_uid'] = $this->getAuthContext()->getUID(); + $formDatas['user_uid'] = (int) $this->getAuthContext()->getUID(); return $this->create_process($formDatas); }, __FUNCTION__); } //수정용 - protected function save_before_fill(array $formDatas): array - { - return $formDatas; - } protected function modify_process($entity, array $formDatas): CommonEntity { try { $actionForm = $this->getActionForm(); - if ($actionForm instanceof CommonForm) { - $actionForm->action_init_process('modify', $formDatas); - // log_message('debug', 'BEFORE hook MODIFY FORMDATA:' . print_r($formDatas ?? null, true)); - foreach ($formDatas as $field => $value) { - $formDatas = $this->actionForm_fieldhook_process($field, $value, $formDatas); - } - // log_message('debug', 'AFTER hook MODIFY FORMDATA:' . print_r($formDatas ?? null, true)); - $actionForm->validate($formDatas); // ✅ 여기서 검증 + if (!$actionForm instanceof CommonForm) { + throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: actionForm이 정의되지 않았습니다."); } + $actionForm->action_init_process('modify', $formDatas); + $actionForm->validate($formDatas); // ✅ 여기서 검증 // 검증 통과 후 엔티티 반영 - $formDatas = $this->save_before_fill($formDatas); - // log_message('debug', 'BEFORE MODIFY fill Entity:' . print_r($formDatas ?? null, true)); + foreach ($formDatas as $field => $value) { + $formDatas = $this->fieldhook_process($field, $value, $formDatas); + } $entity->fill($formDatas); - // log_message('debug', 'AFTER MODIFY fill Entity:' . print_r($entity ?? null, true)); if (!$entity->hasChanged()) { return $entity; } @@ -325,6 +288,7 @@ abstract class CommonService } } + final public function modify(string|int $uid, array $formDatas): CommonEntity { return $this->dbTransaction(function () use ($uid, $formDatas) { @@ -332,7 +296,7 @@ abstract class CommonService if (!$entity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } - $formDatas['user_uid'] = $this->getAuthContext()->getUID(); + $formDatas['user_uid'] = (int) $this->getAuthContext()->getUID(); return $this->modify_process($entity, $formDatas); }, __FUNCTION__); } diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 6b81ee9..aa2ce52 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -7,7 +7,6 @@ use App\Helpers\UserHelper; use App\Forms\UserForm; use App\Entities\UserEntity; - class UserService extends CommonService { protected string $formClass = UserForm::class; @@ -27,44 +26,31 @@ class UserService extends CommonService { return $entity; } - protected function actionForm_fieldhook_process(string $field, $value, array $formDatas): array + protected function fieldhook_process(string $field, $value, array $formDatas): array { switch ($field) { case 'role': - if (is_string($value)) { - $formDatas['role'] = explode(',', $value) ?? []; + $arr = is_array($value) ? $value : explode(',', (string) $value); + $arr = array_values(array_filter(array_map('trim', $arr))); + sort($arr); + $formDatas[$field] = implode(',', $arr); + break; + case 'passwd': + if ($formDatas[$field] !== '') { + $formDatas[$field] = password_hash($value, PASSWORD_BCRYPT); + } else { + unset($formDatas[$field]); } break; + case 'confirmpassword': + unset($formDatas['confirmpassword']); + break; default: - $formDatas = parent::actionForm_fieldhook_process($field, $value, $formDatas); + $formDatas = parent::fieldhook_process($field, $value, $formDatas); break; } return $formDatas; } - - protected function save_before_fill(array $formDatas): array - { - // 1) DB 컬럼 아닌 값 제거 - unset($formDatas['confirmpassword']); - - // 2) role은 무조건 문자열로 - if (array_key_exists('role', $formDatas)) { - $arr = is_array($formDatas['role']) - ? $formDatas['role'] - : explode(',', (string) $formDatas['role']); - - $arr = array_values(array_filter(array_map('trim', $arr))); - sort($arr); - $formDatas['role'] = implode(',', $arr); - } - - // 3) passwd는 빈 값이면 업데이트 제외 (원하면) - if (array_key_exists('passwd', $formDatas) && $formDatas['passwd'] === '') { - unset($formDatas['passwd']); - } - - return $formDatas; - } //List 검색용 //FormFilter 조건절 처리 public function setFilter(string $field, mixed $filter_value): void