From a0fa225f3e3ea4e41bba0e3067334c534e9e5921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A4=80=ED=9D=A0?= Date: Fri, 13 Feb 2026 17:39:29 +0900 Subject: [PATCH] daemon-idc init --- app/Controllers/AbstractWebController.php | 22 +++- app/Entities/BoardEntity.php | 4 - app/Entities/CommonEntity.php | 25 +--- app/Entities/UserEntity.php | 5 - app/Forms/CommonForm.php | 134 ---------------------- app/Forms/UserForm.php | 1 + app/Helpers/UserHelper.php | 33 ++++-- app/Services/CommonService.php | 18 ++- app/Services/UserService.php | 41 ++++++- 9 files changed, 105 insertions(+), 178 deletions(-) diff --git a/app/Controllers/AbstractWebController.php b/app/Controllers/AbstractWebController.php index 7b8953e..9aa8fd1 100644 --- a/app/Controllers/AbstractWebController.php +++ b/app/Controllers/AbstractWebController.php @@ -171,6 +171,22 @@ abstract class AbstractWebController extends Controller // ========================================================= // 공통화: 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 { @@ -203,7 +219,8 @@ abstract class AbstractWebController extends Controller 'errors' => $e->errors, ]); } - return $this->action_redirect_process('error', dev_exception($e->getMessage())); + // ✅ redirect에는 string만 넣는다 + return $this->action_redirect_process('error', $e->getMessage()); } if ($this->request->isAJAX()) { @@ -214,6 +231,7 @@ abstract class AbstractWebController extends Controller } $msg = $humanPrefix ? ($humanPrefix . $e->getMessage()) : $e->getMessage(); - return $this->action_redirect_process('error', dev_exception($msg)); + // ✅ redirect에는 string만 넣는다 + return $this->action_redirect_process('error', $msg); } } diff --git a/app/Entities/BoardEntity.php b/app/Entities/BoardEntity.php index 252f2a4..1938874 100644 --- a/app/Entities/BoardEntity.php +++ b/app/Entities/BoardEntity.php @@ -9,10 +9,6 @@ class BoardEntity extends CommonEntity { const PK = Model::PK; const TITLE = Model::TITLE; - protected array $nullableFields = [ - 'user_uid', - 'worker_uid', - ]; protected $attributes = [ 'user_uid' => null, 'worker_uid' => null, diff --git a/app/Entities/CommonEntity.php b/app/Entities/CommonEntity.php index 578b6f8..37d562a 100644 --- a/app/Entities/CommonEntity.php +++ b/app/Entities/CommonEntity.php @@ -13,8 +13,6 @@ abstract class CommonEntity extends Entity * 이 엔티티에서 "빈문자/공백 입력은 NULL로 저장"해야 하는 필드 목록. * 기본은 빈 배열이고, 각 Entity에서 필요한 것만 override해서 채우면 됨. */ - protected array $nullableFields = []; - public function __construct(array|null $data = null) { parent::__construct($data); @@ -35,21 +33,8 @@ abstract class CommonEntity extends Entity final public function __set(string $key, $value = null) { if (array_key_exists($key, $this->attributes)) { - - // 이 엔티티에서 NULL로 보정할 필드만 처리 (화이트리스트) - if (!empty($this->nullableFields) && in_array($key, $this->nullableFields, true)) { - if (is_string($value)) { - $value = trim($value); - } - $this->attributes[$key] = ($value === '' || $value === null) ? null : $value; - return; - } - - // 기본: 그대로 저장 - $this->attributes[$key] = $value; - return; + return $this->attributes[$key] = $value; } - parent::__set($key, $value); } @@ -72,21 +57,21 @@ abstract class CommonEntity extends Entity final public function getStatus(): string { - return $this->status ?? ""; + return $this->attributes['status'] ?? ""; } final public function getUpdatedAt(): string { - return $this->updated_at ?? ""; + return $this->attributes['updated_at'] ?? ""; } final public function getCreatedAt(): string { - return $this->created_at ?? ""; + return $this->attributes['created_at'] ?? ""; } final public function getDeletedAt(): string { - return $this->deleted_at ?? ""; + return $this->attributes['deleted_at'] ?? ""; } } diff --git a/app/Entities/UserEntity.php b/app/Entities/UserEntity.php index 8f936b8..d87467b 100644 --- a/app/Entities/UserEntity.php +++ b/app/Entities/UserEntity.php @@ -10,11 +10,6 @@ class UserEntity extends CommonEntity const PK = Model::PK; const TITLE = Model::TITLE; - protected array $nullableFields = [ - 'mobile', - // uid 같은 숫자 PK가 nullable이면 여기에 추가 - ]; - // ✅ role은 반드시 "문자열" 기본값 (DB 저장형) protected $attributes = [ 'id' => '', diff --git a/app/Forms/CommonForm.php b/app/Forms/CommonForm.php index 1f09d15..7ebf17a 100644 --- a/app/Forms/CommonForm.php +++ b/app/Forms/CommonForm.php @@ -158,130 +158,6 @@ abstract class CommonForm return $this->_batchjobButtons; } - /* --------------------------------------------------------------------- - * Normalize / Sanitize - * --------------------------------------------------------------------- */ - - /** - * 1) 깊은 배열 구조 정리(배열은 유지) - * - 여기서는 null -> '' 같은 변환을 절대 하지 않습니다. - */ - protected function sanitizeFormDatas($data, string $path = '') - { - if (!is_array($data)) { - return $data; - } - - foreach ($data as $k => $v) { - if (is_array($v)) { - $data[$k] = $this->sanitizeFormDatas($v, ($path !== '' ? "{$path}.{$k}" : (string) $k)); - } - } - - return $data; - } - - /** - * 2) 숫자/FK 필드 정규화 - * - '' -> null - * - 숫자 문자열 -> int - */ - protected function normalizeNumericEmptyToNull(array $data, array $numericFields): array - { - foreach ($numericFields as $f) { - if (!array_key_exists($f, $data)) { - continue; - } - - if ($data[$f] === '') { - $data[$f] = null; - continue; - } - - if (is_string($data[$f]) && ctype_digit($data[$f])) { - $data[$f] = (int) $data[$f]; - } - } - - return $data; - } - - /** - * 3) role.* 같은 배열 원소 규칙이 있을 때, 부모 배열 존재/타입 보정 - */ - protected function ensureParentArrayForWildcardRules(array &$formDatas, array $formRules): void - { - foreach ($formRules as $fieldKey => $ruleDef) { - $fieldName = (string) $fieldKey; - - if (!str_contains($fieldName, '.*')) { - continue; - } - - $parent = str_replace('.*', '', $fieldName); - - // 1) 부모가 없거나 ''/null 이면 빈 배열 - if (!array_key_exists($parent, $formDatas) || $formDatas[$parent] === '' || $formDatas[$parent] === null) { - $formDatas[$parent] = []; - continue; - } - - // 2) 문자열이면 CSV로 분해 (혹시 JSON 문자열이면 JSON 우선) - if (is_string($formDatas[$parent])) { - $raw = trim($formDatas[$parent]); - if ($raw === '') { - $formDatas[$parent] = []; - } else { - $decoded = json_decode($raw, true); - if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { - $formDatas[$parent] = $decoded; - } else { - $formDatas[$parent] = explode(DEFAULTS["DELIMITER_COMMA"], $raw); - } - } - } - - // 3) 배열이 아니면 강제 빈 배열 - if (!is_array($formDatas[$parent])) { - $formDatas[$parent] = []; - } - - // 4) 배열 원소 정리 - $clean = array_map( - fn($v) => is_scalar($v) ? trim((string) $v) : '', - $formDatas[$parent] - ); - $clean = array_values(array_filter($clean, fn($v) => $v !== '')); - - $formDatas[$parent] = $clean; - } - } - - /** - * 4) 검증 rule에 따라 numeric(FK 포함) 필드 수집 - */ - protected function collectNumericFieldsFromRules(array $formRules): array - { - $numericFields = []; - - foreach ($formRules as $field => $rule) { - $fieldName = (string) $field; - - if (str_contains($fieldName, '.*')) { - continue; - } - - // hook 적용 - [$fieldName, $ruleStr] = $this->getValidationRule($fieldName, (string) $rule); - - if (is_string($ruleStr) && str_contains($ruleStr, 'numeric')) { - $numericFields[] = $fieldName; - } - } - - return array_values(array_unique($numericFields)); - } - /* --------------------------------------------------------------------- * Validation * --------------------------------------------------------------------- */ @@ -294,9 +170,7 @@ abstract class CommonForm */ final public function validate(array &$formDatas): void { - log_message('debug', '>>> CommonForm::validate CALLED: ' . static::class); try { - $formDatas = $this->sanitizeFormDatas($formDatas); $formFields = $this->getFormFields(); $formRules = $this->getFormRules(); @@ -304,11 +178,6 @@ abstract class CommonForm throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 지정된 Form RULE이 없습니다."); } - $this->ensureParentArrayForWildcardRules($formDatas, $formRules); - - $numericFields = $this->collectNumericFieldsFromRules($formRules); - $formDatas = $this->normalizeNumericEmptyToNull($formDatas, $numericFields); - $dynamicRules = []; foreach ($formRules as $field => $rule) { [$fieldName, $ruleStr] = $this->getValidationRule((string) $field, (string) $rule); @@ -321,15 +190,12 @@ abstract class CommonForm } else { $label = $fieldName; } - $dynamicRules[$fieldName] = [ 'label' => $label, 'rules' => $ruleStr, ]; } - $this->validation->setRules($dynamicRules); - if (!$this->validation->run($formDatas)) { throw new FormValidationException($this->validation->getErrors()); } diff --git a/app/Forms/UserForm.php b/app/Forms/UserForm.php index a8ab106..4fa0215 100644 --- a/app/Forms/UserForm.php +++ b/app/Forms/UserForm.php @@ -57,6 +57,7 @@ class UserForm extends CommonForm $formRules[$field] = sprintf("required|trim|valid_email%s", in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : ""); break; case "role": + // $formRules[$field] = 'required|trim|string'; $formRules[$field] = 'required|is_array|at_least_one'; $formRules['role.*'] = 'permit_empty|trim|in_list[manager,cloudflare,firewall,security,director,master]'; break; diff --git a/app/Helpers/UserHelper.php b/app/Helpers/UserHelper.php index 07495ef..e545519 100644 --- a/app/Helpers/UserHelper.php +++ b/app/Helpers/UserHelper.php @@ -18,18 +18,35 @@ class UserHelper extends CommonHelper $form = form_password($field, "", $extras); break; case 'role': + // ✅ value가 string이면 CSV -> array로 변환 + if (is_string($value)) { + $value = array_values(array_filter(array_map('trim', explode(',', $value)))); + } elseif ($value === null) { + $value = []; + } + + // ✅ 현재 role 목록(소문자/trim 정규화) $currentRoles = is_array($value) ? array_map('strtolower', array_map('trim', $value)) : []; + $form = ''; - //Form페이지에서는 맨앞에것 제외하기 위함 - array_shift($viewDatas['formOptions'][$field]['options']); - foreach ($viewDatas['formOptions'][$field]['options'] as $key => $label) { - $checked = in_array(strtolower(trim($key)), $currentRoles); - $form .= ''; + + // Form페이지에서는 맨앞에것 제외하기 위함 + if (isset($viewDatas['formOptions'][$field]['options']) && is_array($viewDatas['formOptions'][$field]['options'])) { + $options = $viewDatas['formOptions'][$field]['options']; + + // ✅ 원본을 건드리지 말고 복사본에서 shift (중요) + array_shift($options); + + foreach ($options as $key => $label) { + $checked = in_array(strtolower(trim((string) $key)), $currentRoles, true); + + $form .= ''; + } } break; default: diff --git a/app/Services/CommonService.php b/app/Services/CommonService.php index c9003c3..fb31963 100644 --- a/app/Services/CommonService.php +++ b/app/Services/CommonService.php @@ -250,7 +250,7 @@ abstract class CommonService } //Action 작업시 field에따른 Hook처리(각 Service에서 override); - protected function action_process_fieldhook(string $field, $value, array $formDatas): array + protected function actionForm_fieldhook_process(string $field, $value, array $formDatas): array { return $formDatas; } @@ -262,11 +262,14 @@ abstract class CommonService $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->action_process_fieldhook($field, $value, $formDatas); + $formDatas = $this->actionForm_fieldhook_process($field, $value, $formDatas); } + // log_message('debug', 'AFTER hook CREATE FORMDATA:' . print_r($formDatas ?? null, true)); $actionForm->validate($formDatas); // ✅ 여기서 검증 } + $entityClass = $this->getEntityClass(); $entity = new $entityClass($formDatas); if (!$entity instanceof $entityClass) { @@ -289,19 +292,28 @@ abstract class CommonService } //수정용 + 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->action_process_fieldhook($field, $value, $formDatas); + $formDatas = $this->actionForm_fieldhook_process($field, $value, $formDatas); } + // log_message('debug', 'AFTER hook MODIFY FORMDATA:' . print_r($formDatas ?? null, true)); $actionForm->validate($formDatas); // ✅ 여기서 검증 } // 검증 통과 후 엔티티 반영 + $formDatas = $this->save_before_fill($formDatas); + // log_message('debug', 'BEFORE MODIFY fill Entity:' . print_r($formDatas ?? null, true)); $entity->fill($formDatas); + // log_message('debug', 'AFTER MODIFY fill Entity:' . print_r($entity ?? null, true)); if (!$entity->hasChanged()) { return $entity; } diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 7466116..6b81ee9 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -2,12 +2,11 @@ namespace App\Services; -use RuntimeException; use App\Models\UserModel; use App\Helpers\UserHelper; use App\Forms\UserForm; use App\Entities\UserEntity; -use App\Entities\CommonEntity; + class UserService extends CommonService { @@ -28,6 +27,44 @@ class UserService extends CommonService { return $entity; } + protected function actionForm_fieldhook_process(string $field, $value, array $formDatas): array + { + switch ($field) { + case 'role': + if (is_string($value)) { + $formDatas['role'] = explode(',', $value) ?? []; + } + break; + default: + $formDatas = parent::actionForm_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