daemon-idc init
This commit is contained in:
parent
a8b50340ac
commit
209b86180c
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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__);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user