daemon-idc init
This commit is contained in:
parent
fcebaa0ae0
commit
a0fa225f3e
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
return $this->attributes[$key] = $value;
|
||||
}
|
||||
$this->attributes[$key] = ($value === '' || $value === null) ? null : $value;
|
||||
return;
|
||||
}
|
||||
|
||||
// 기본: 그대로 저장
|
||||
$this->attributes[$key] = $value;
|
||||
return;
|
||||
}
|
||||
|
||||
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'] ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -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' => '',
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -18,19 +18,36 @@ 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페이지에서는 맨앞에것 제외하기 위함
|
||||
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 .= '<label class="me-3">';
|
||||
$form .= form_checkbox('role[]', $key, $checked, ['id' => "role_{$key}", ...$extras]);
|
||||
$form .= form_checkbox('role[]', (string) $key, $checked, ['id' => "role_{$key}", ...$extras]);
|
||||
$form .= " {$label}";
|
||||
$form .= '</label>';
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$form = parent::getFieldForm($field, $value, $viewDatas, $extras);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user