daemon-idc init

This commit is contained in:
최준흠 2026-03-02 12:07:14 +09:00
parent a8b50340ac
commit 209b86180c
5 changed files with 118 additions and 454 deletions

View File

@ -1,13 +1,4 @@
<?php
// =========================================================
// AbstractWebController.php (FINAL)
// - runAction / okResponse / failResponse 내장
// - action_redirect_process: AJAX 방어 + 상태코드 정책 고정
// * warning/error => 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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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__);
}

View File

@ -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