From fcebaa0ae0642427cf6d3fc5b8de284b3abd3a83 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 11:33:57 +0900 Subject: [PATCH] daemon-idc init --- app/Config/Validation.php | 3 +- app/Controllers/AbstractCRUDController.php | 324 ++++++++------------- app/Controllers/AbstractWebController.php | 157 +++++++--- app/Controllers/Auth/GoogleController.php | 4 +- app/Controllers/Auth/LocalController.php | 3 +- app/Controllers/BaseController copy.php | 45 --- app/Controllers/CommonController.php | 177 +++++------ app/Controllers/Front/FrontController.php | 1 - app/DTOs/Auth/AuthDTO.php | 13 - app/DTOs/Auth/GoogleDTO.php | 13 - app/DTOs/Auth/LocalDTO.php | 14 - app/DTOs/BoardDTO.php | 19 -- app/DTOs/CommonDTO.php | 72 ----- app/DTOs/InquiryDTO.php | 17 -- app/DTOs/UserDTO.php | 40 --- app/Helpers/util_helper.php | 22 ++ app/Language/ko/Validation.php | 5 +- app/Services/Auth/AuthService.php | 6 +- app/Services/Auth/GoogleService.php | 9 - app/Services/Auth/LocalService.php | 9 - app/Services/BoardService.php | 9 - app/Services/CommonService.php | 4 - app/Services/InquiryService.php | 9 - app/Services/UserService.php | 9 - 24 files changed, 369 insertions(+), 615 deletions(-) delete mode 100644 app/Controllers/BaseController copy.php delete mode 100644 app/DTOs/Auth/AuthDTO.php delete mode 100644 app/DTOs/Auth/GoogleDTO.php delete mode 100644 app/DTOs/Auth/LocalDTO.php delete mode 100644 app/DTOs/BoardDTO.php delete mode 100644 app/DTOs/CommonDTO.php delete mode 100644 app/DTOs/InquiryDTO.php delete mode 100644 app/DTOs/UserDTO.php diff --git a/app/Config/Validation.php b/app/Config/Validation.php index 6342dbb..fb88021 100644 --- a/app/Config/Validation.php +++ b/app/Config/Validation.php @@ -25,6 +25,7 @@ class Validation extends BaseConfig FormatRules::class, FileRules::class, CreditCardRules::class, + \App\Validation\CustomRules::class ]; /** @@ -34,7 +35,7 @@ class Validation extends BaseConfig * @var array */ public array $templates = [ - 'list' => 'CodeIgniter\Validation\Views\list', + 'list' => 'CodeIgniter\Validation\Views\list', 'single' => 'CodeIgniter\Validation\Views\single', ]; diff --git a/app/Controllers/AbstractCRUDController.php b/app/Controllers/AbstractCRUDController.php index 59da195..44f4e88 100644 --- a/app/Controllers/AbstractCRUDController.php +++ b/app/Controllers/AbstractCRUDController.php @@ -1,12 +1,19 @@ ' . $action . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다."); + } + } + + protected function assertEntityClass(CommonEntity $entity, string $action): void + { + $entityClass = $this->service->getEntityClass(); + if (!$entity instanceof $entityClass) { + throw new RuntimeException(static::class . '->' . $action . "에서 오류발생:Return Type은 {$entityClass}만 가능"); + } + } + + protected function buildViewRedirectUrl(CommonEntity $entity): string + { + return '/' . implode('/', [...$this->getActionPaths(), 'view']) . '/' . $entity->getPK(); + } + + protected function buildListRedirectUrl(): string + { + return implode(DIRECTORY_SEPARATOR, $this->getActionPaths()); + } + + // ========================================================= + // Create Form + // ========================================================= + protected function create_form_process(array $formDatas = []): array { - //초기 기본 Default값 지정 - $formDatas = $this->request->getVar(); - return $formDatas; + return $this->request->getVar(); } - protected function create_form_result_process(string $action): string|RedirectResponse + public function create_form(): string|RedirectResponse|ResponseInterface { - return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate')); - } + $action = __FUNCTION__; - public function create_form(): string|RedirectResponse - { - try { - $action = __FUNCTION__; + return $this->runAction($action, function () use ($action) { $formDatas = $this->create_form_process(); $this->action_init_process($action, $formDatas); $this->addViewDatas('formDatas', $formDatas); - return $this->create_form_result_process($action); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 생성폼 오류:" . $e->getMessage()); - } + return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate')); + }); } + // ========================================================= + // Create + // ========================================================= + protected function create_process(array $formDatas): CommonEntity { - // POST 데이터를 DTO 객체로 변환 - $dto = $this->service->createDTO($formDatas); - - //DTO 타입 체크 로직을 일반화 - $dtoClass = $this->service->getDTOClass(); - if (!$dto instanceof $dtoClass) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . get_class($dto) . "는 사용할 수 없습니다. ({$dtoClass} 필요)"); - } - - // 💡 여기서 service->create() 내부에서 CommonForm::validate()가 실행되고 - // 실패 시 FormValidationException이 throw 된다고 가정(권장) - return $this->service->create($dto->toArray()); + return $this->service->create($formDatas); } - protected function create_result_process(CommonEntity $entity, ?string $redirect_url = null): string|RedirectResponse + final public function create(): RedirectResponse|ResponseInterface|string { - return $this->action_redirect_process( - 'info', - "{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다.", - $redirect_url ?? '/' . implode('/', [...$this->getActionPaths(), 'view']) . '/' . $entity->getPK() - ); - } + $action = __FUNCTION__; - final public function create(): string|RedirectResponse|ResponseInterface - { - try { - $action = __FUNCTION__; + return $this->runAction($action, function () use ($action) { $this->action_init_process($action); $entity = $this->create_process($this->request->getPost()); + $this->assertEntityClass($entity, $action); - // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 - $entityClass = $this->service->getEntityClass(); - if (!$entity instanceof $entityClass) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능"); - } - - // ✅ AJAX 요청이면 JSON 성공 응답 - if ($this->request->isAJAX()) { - return $this->response->setJSON([ - 'ok' => true, - 'message' => "{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다.", - 'id' => $entity->getPK(), - ]); - } - - return $this->create_result_process($entity); - - } catch (FormValidationException $e) { - - // ✅ AJAX 요청이면 422 + 필드별 오류 - if ($this->request->isAJAX()) { - return $this->response - ->setStatusCode(422) - ->setJSON([ - 'ok' => false, - 'errors' => $e->errors, - ]); - } - - // 기존 redirect 방식 유지 - return $this->action_redirect_process( - 'error', - static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 생성 오류:\n" . implode("\n", $e->errors) - ); - - } catch (\Throwable $e) { - log_message('error', 'EXCEPTION_CLASS=' . get_class($e)); - // ✅ AJAX면 500 JSON - if ($this->request->isAJAX()) { - return $this->response - ->setStatusCode(500) - ->setJSON([ - 'ok' => false, - 'message' => static::class . '->' . __FUNCTION__ . "에서 오류:" . $e->getMessage(), - ]); - } - - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 생성 오류:" . $e->getMessage()); - } + $message = "{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다."; + return $this->okResponse($message, ['id' => $entity->getPK()], $this->buildViewRedirectUrl($entity)); + }); } - // --- 수정 (Modify) --- + // ========================================================= + // Modify Form + // ========================================================= + protected function modify_form_process($uid): CommonEntity { return $this->service->getEntity($uid); } - protected function modify_form_result_process(string $action): string|RedirectResponse + protected function modify_form_result_process(string $action): string { return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate')); } - final public function modify_form($uid): string|RedirectResponse + final public function modify_form($uid): string|RedirectResponse|ResponseInterface { - try { - if (!$uid) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다."); - } + $action = __FUNCTION__; + + return $this->runAction($action, function () use ($action, $uid) { + $this->assertUid($uid, $action); + $entity = $this->modify_form_process($uid); $this->addViewDatas('entity', $entity); - $action = __FUNCTION__; + $this->action_init_process($action, $entity->toArray()); return $this->modify_form_result_process($action); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 수정폼 오류:" . $e->getMessage()); - } + }); } + // ========================================================= + // Modify + // ========================================================= + protected function modify_process($uid, array $formDatas): CommonEntity { - $formDatas[$this->service->getPKField()] = $uid; - $dto = $this->service->createDTO($formDatas); - //DTO 타입 체크 로직을 일반화 - $dtoClass = $this->service->getDTOClass(); - if (!$dto instanceof $dtoClass) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: " . get_class($dto) . "는 사용할 수 없습니다. ({$dtoClass} 필요)"); - } - return $this->service->modify($uid, $dto->toArray()); + return $this->service->modify($uid, $formDatas); + } + + /** + * 기본 결과 처리 (필요시 자식에서 override) + */ + protected function result_process(CommonEntity $entity, string $message): RedirectResponse|ResponseInterface + { + return $this->okResponse($message, ['id' => $entity->getPK()], $this->buildViewRedirectUrl($entity)); } final public function modify($uid): string|RedirectResponse|ResponseInterface { - try { - if (!$uid) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다."); - } + $action = __FUNCTION__; + + return $this->runAction($action, function () use ($action, $uid) { + $this->assertUid($uid, $action); - $action = __FUNCTION__; $this->action_init_process($action); $entity = $this->modify_process($uid, $this->request->getPost()); - - // 💡 동적으로 가져온 Entity 클래스 이름으로 instanceof 검사 - $entityClass = $this->service->getEntityClass(); - if (!$entity instanceof $entityClass) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 {$entityClass}만 가능"); - } + $this->assertEntityClass($entity, $action); $this->addViewDatas('entity', $entity); - - // ✅ AJAX 요청이면 JSON 성공 응답 - if ($this->request->isAJAX()) { - return $this->response->setJSON([ - 'ok' => true, - 'message' => "{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다.", - 'id' => $entity->getPK(), - ]); - } - - return $this->modify_result_process($entity); - - } catch (FormValidationException $e) { - - // ✅ AJAX 요청이면 422 + 필드별 오류 - if ($this->request->isAJAX()) { - return $this->response - ->setStatusCode(422) - ->setJSON([ - 'ok' => false, - 'errors' => $e->errors, - ]); - } - - // 기존 redirect 방식 유지 - return $this->action_redirect_process( - 'error', - static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 수정 오류:\n" . implode("\n", $e->errors) - ); - - } catch (\Throwable $e) { - log_message('error', 'EXCEPTION_CLASS=' . get_class($e)); - // ✅ AJAX면 500 JSON - if ($this->request->isAJAX()) { - return $this->response - ->setStatusCode(500) - ->setJSON([ - 'ok' => false, - 'message' => static::class . '->' . __FUNCTION__ . "에서 오류:" . $e->getMessage(), - ]); - } - - return $this->action_redirect_process( - 'error', - static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 수정 오류:" . $e->getMessage() - ); - } + return $this->result_process($entity, "{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다."); + }); } + // ========================================================= + // Delete + // ========================================================= - protected function modify_result_process(CommonEntity $entity, ?string $redirect_url = null): string|RedirectResponse - { - return $this->action_redirect_process( - 'info', - "{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다.", - $redirect_url ?? '/' . implode('/', [...$this->getActionPaths(), 'view']) . '/' . $entity->getPK() - ); - } - - // --- 삭제 (Delete) --- protected function delete_process($uid): CommonEntity { return $this->service->delete($uid); } - protected function delete_result_process($entity, ?string $redirect_url = null): string|RedirectResponse + protected function delete_result_process(CommonEntity $entity, ?string $redirect_url = null): RedirectResponse|ResponseInterface { + // AJAX면 action_redirect_process가 JSON으로 자동 변환 + $redirect_url = $redirect_url ?? $this->buildListRedirectUrl(); return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다.", $redirect_url); } - final public function delete($uid): RedirectResponse + final public function delete($uid): RedirectResponse|ResponseInterface|string { - try { - if (!$uid) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다."); - } - $entity = $this->service->getEntity($uid); + $action = __FUNCTION__; + + return $this->runAction($action, function () use ($action, $uid) { + $this->assertUid($uid, $action); + + $this->action_init_process($action); + + // 삭제 전 존재 확인(기존 로직 유지) + $this->service->getEntity($uid); + $entity = $this->delete_process($uid); + $this->assertEntityClass($entity, $action); + return $this->delete_result_process($entity); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 삭제 오류:" . $e->getMessage()); - } + }); } - // --- 상세보기 (View) --- + // ========================================================= + // View + // ========================================================= + protected function view_process($uid): CommonEntity { return $this->service->getEntity($uid); @@ -277,19 +212,18 @@ abstract class AbstractCRUDController extends AbstractWebController return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate')); } - final public function view($uid): string|RedirectResponse + final public function view($uid): string|RedirectResponse|ResponseInterface { - try { - if (!$uid) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다."); - } + $action = __FUNCTION__; + + return $this->runAction($action, function () use ($action, $uid) { + $this->assertUid($uid, $action); + $entity = $this->view_process($uid); - $action = __FUNCTION__; $this->action_init_process($action, $entity->toArray()); $this->addViewDatas('entity', $entity); + return $this->view_result_process($action); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 상세보기 오류:" . $e->getMessage()); - } + }); } } diff --git a/app/Controllers/AbstractWebController.php b/app/Controllers/AbstractWebController.php index 07cd88e..7b8953e 100644 --- a/app/Controllers/AbstractWebController.php +++ b/app/Controllers/AbstractWebController.php @@ -1,4 +1,13 @@ 400 +// * critical/alert/emergency => 500 +// * info/notice/debug/default => 200 +// - RedirectResponse|ResponseInterface로 엄격 정리 +// ========================================================= namespace App\Controllers; @@ -9,28 +18,26 @@ 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; + private array $_action_paths = []; private array $_viewDatas = []; private ?string $_title = null; + protected $layouts = []; protected $service = null; - // --- 초기화 및 DI --- + // --- 초기화 및 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(); @@ -39,15 +46,13 @@ 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) + final protected function addActionPaths(string $path): void { $this->_action_paths[] = $path; } @@ -57,24 +62,19 @@ abstract class AbstractWebController extends Controller return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths); } - final protected function addViewDatas(string $key, mixed $value) + final protected function addViewDatas(string $key, mixed $value): void { $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,10 +84,49 @@ 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 + protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse|ResponseInterface { + $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': @@ -95,37 +134,26 @@ abstract class AbstractWebController extends Controller case 'alert': case 'emergency': log_message($type, $message); - $result = redirect()->back()->withInput()->with('message', $message); - break; + return redirect()->back()->withInput()->with('message', $message); + case 'debug': case 'info': case 'notice': default: - $redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths()); - $result = redirect()->to($redirect_url)->with('message', $message); - break; + return redirect()->to($resolvedRedirect)->with('message', $message); } - return $result; } /** - * 뷰 경로와 데이터를 이용하여 최종 HTML을 렌더링합니다. + * 뷰 렌더링 */ - protected function action_render_process( - string $view_file, - array $viewDatas, - ?string $template_path = null - ): string { + protected function action_render_process(string $view_file, array $viewDatas, ?string $template_path = null): string + { helper(['form', 'utility']); - /** - * 🔥 핵심: - * View name은 항상 슬래시 기반 - */ - $baseViewPath = trim($viewDatas['layout']['path'], '/'); - if ($template_path) { + $baseViewPath = trim($viewDatas['layout']['path'], '/'); + if ($template_path) $baseViewPath .= '/' . trim($template_path, '/'); - } $viewName = $baseViewPath . '/' . ltrim($view_file, '/'); @@ -139,4 +167,53 @@ abstract class AbstractWebController extends Controller ], ]); } -} \ No newline at end of file + + // ========================================================= + // 공통화: runAction / okResponse / failResponse + // ========================================================= + + 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, + ]); + } + return $this->action_redirect_process('error', dev_exception($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(); + return $this->action_redirect_process('error', dev_exception($msg)); + } +} diff --git a/app/Controllers/Auth/GoogleController.php b/app/Controllers/Auth/GoogleController.php index a2683e7..fcf9b52 100644 --- a/app/Controllers/Auth/GoogleController.php +++ b/app/Controllers/Auth/GoogleController.php @@ -2,7 +2,6 @@ namespace App\Controllers\Auth; -use App\DTOs\Auth\GoogleDTO; use App\Entities\UserEntity; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -25,8 +24,7 @@ class GoogleController extends AuthController //로그인처리 protected function login_process(): UserEntity { - //요청 데이터를 DTO 객체로 변환 - return $this->service->login(new GoogleDTO($this->request->getPost())); + return $this->service->login($this->request->getPost()); } protected function logout_process(): void { diff --git a/app/Controllers/Auth/LocalController.php b/app/Controllers/Auth/LocalController.php index 9d1b913..ef675d6 100644 --- a/app/Controllers/Auth/LocalController.php +++ b/app/Controllers/Auth/LocalController.php @@ -2,7 +2,6 @@ namespace App\Controllers\Auth; -use App\DTOs\Auth\LocalDTO; use App\Entities\UserEntity; use App\Services\Auth\LocalService; use CodeIgniter\HTTP\RequestInterface; @@ -25,7 +24,7 @@ class LocalController extends AuthController //로그인처리 protected function login_process(): UserEntity { - return $this->service->login(new LocalDTO($this->request->getPost())); + return $this->service->login($this->request->getPost()); } protected function logout_process(): void { diff --git a/app/Controllers/BaseController copy.php b/app/Controllers/BaseController copy.php deleted file mode 100644 index ab45a77..0000000 --- a/app/Controllers/BaseController copy.php +++ /dev/null @@ -1,45 +0,0 @@ -helpers = ['form', 'url']; - - // Caution: Do not edit this line. - parent::initController($request, $response, $logger); - - // Preload any models, libraries, etc, here. - // $this->session = service('session'); - } -} diff --git a/app/Controllers/CommonController.php b/app/Controllers/CommonController.php index ba3d2b2..f741ee1 100644 --- a/app/Controllers/CommonController.php +++ b/app/Controllers/CommonController.php @@ -1,13 +1,22 @@ getTitle()}에서 일괄작업에 적용할 리스트을 선택하셔야합니다."); } - // 2. 변경할 데이터 추출 및 정리 - unset($postDatas['batchjob_uids'], $postDatas['batchjob_submit']); //formDatas에 포함되지 않게하기위함 + + unset($postDatas['batchjob_uids'], $postDatas['batchjob_submit']); $formDatas = array_filter($postDatas, fn($value) => $value !== "" && $value !== null); if (empty($formDatas)) { - throw new RuntimeException(message: "{$this->getTitle()}에서 일괄작업에 변경할 조건항목을 선택하셔야합니다."); + throw new RuntimeException("{$this->getTitle()}에서 일괄작업에 변경할 조건항목을 선택하셔야합니다."); } - // 3. 데이터가 있는 필드 추출 - return array($uids, $formDatas); + + return [$uids, $formDatas]; } protected function batchjob_process(array $uids, array $formDatas): array @@ -38,7 +54,7 @@ abstract class CommonController extends AbstractCRUDController return $this->service->batchjob($uids, $formDatas); } - protected function batchjob_result_process(array $uids, array $entities): string|RedirectResponse + protected function batchjob_result_process(array $uids, array $entities): RedirectResponse|ResponseInterface { return $this->action_redirect_process('info', sprintf( "%s에서 %s개 처리완료 총:%s개 수정이 완료되었습니다.", @@ -47,19 +63,21 @@ abstract class CommonController extends AbstractCRUDController count($uids) )); } - final public function batchjob(): string|RedirectResponse + + final public function batchjob(): RedirectResponse|ResponseInterface|string { - try { - $action = __FUNCTION__; - // 사전작업 및 데이터 추출 초기화 - list($uids, $formDatas) = $this->batchjob_pre_process($this->request->getPost()); + $action = __FUNCTION__; + + return $this->runAction($action, function () use ($action) { + [$uids, $formDatas] = $this->batchjob_pre_process($this->request->getPost()); $entities = $this->batchjob_process($uids, $formDatas); return $this->batchjob_result_process($uids, $entities); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 일괄수정 오류:" . $e->getMessage()); - } + }); } - // --- 일괄 삭제 (Batch Job Delete) --- + + // ========================================================= + // Batch Job Delete + // ========================================================= protected function batchjob_delete_pre_process(array $postDatas): array { @@ -70,7 +88,12 @@ abstract class CommonController extends AbstractCRUDController return $uids; } - protected function batchjob_delete_result_process(array $uids, array $entities): string|RedirectResponse + protected function batchjob_delete_process(array $uids): array + { + return $this->service->batchjob_delete($uids); + } + + protected function batchjob_delete_result_process(array $uids, array $entities): RedirectResponse|ResponseInterface { return $this->action_redirect_process('info', sprintf( "%s에서 %s개 처리완료, 총:%s개 일괄삭제가 완료되었습니다.", @@ -80,51 +103,38 @@ abstract class CommonController extends AbstractCRUDController )); } - /** - * 단일 삭제 로직을 재사용 (Override 가능) - */ - protected function batchjob_delete_process(array $uids): array + final public function batchjob_delete(): RedirectResponse|ResponseInterface|string { - return $this->service->batchjob_delete($uids); - } - final public function batchjob_delete(): string|RedirectResponse - { - try { + $action = __FUNCTION__; + + return $this->runAction($action, function () use ($action) { $uids = $this->batchjob_delete_pre_process($this->request->getPost()); $entities = $this->batchjob_delete_process($uids); return $this->batchjob_delete_result_process($uids, $entities); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 일괄삭제 오류:" . $e->getMessage()); - } + }); } - // --- 목록 (Index / List) 관련 --- + // ========================================================= + // Index / List + // ========================================================= - /** - * 조건절(필터, 검색어, 날짜, 정렬)을 처리합니다. (Override 가능) - */ protected function index_condition_process(string $action): void { - // Filter조건절 처리 $index_filters = []; - // dd($this->service->getActionForm()->getIndexFilters($action)); foreach ($this->service->getActionForm()->getIndexFilters($action) as $field) { $value = $this->request->getVar($field) ?? null; - if ($value) { + if ($value) $this->service->setFilter($field, $value); - } $index_filters[$field] = $value; } $this->addViewDatas('index_filters', $index_filters); - // 검색어조건절 처리 $index_word = $this->request->getVar('index_word'); if ($index_word !== null && $index_word !== '') { $this->service->setSearchWord($index_word); } $this->addViewDatas('index_word', $index_word); - // 날자검색 $index_start = $this->request->getVar('index_start'); $index_end = $this->request->getVar('index_end'); if ($index_start !== null && $index_start !== '' && $index_end !== null && $index_end !== '') { @@ -133,7 +143,6 @@ abstract class CommonController extends AbstractCRUDController $this->addViewDatas('index_start', $index_start); $this->addViewDatas('index_end', $index_end); - // OrderBy처리 $order_field = $this->request->getVar('order_field'); $order_value = $this->request->getVar('order_value'); $this->service->setOrderBy($order_field, $order_value); @@ -141,23 +150,15 @@ abstract class CommonController extends AbstractCRUDController $this->addViewDatas('order_value', $order_value); } - /** - * Pagenation Select Box 옵션을 생성합니다. (Override 가능) - */ protected function pagenation_options_process(int $index_totalcount, int $perpage): array { $page_options = ["" => "줄수선택"]; - // 기존 로직 유지 - for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) { + for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) $page_options[$i] = $i; - } $page_options[$index_totalcount] = $index_totalcount; return $page_options; } - /** - * PageNation 링크를 생성하고 뷰 데이터에 추가합니다. (Override 가능) - */ protected function pagenation_process(int $index_totalcount, int $page, int $perpage, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): mixed { $pager = service("pager"); @@ -166,14 +167,10 @@ abstract class CommonController extends AbstractCRUDController return $pager->links($pager_group, $template); } - /** - * Service에서 엔티티 목록을 가져와 처리합니다. (Override 가능) - */ protected function index_entities_process(array $entities = []): array { - foreach ($this->service->getEntities() as $entity) { + foreach ($this->service->getEntities() as $entity) $entities[] = $entity; - } return $entities; } @@ -182,63 +179,71 @@ abstract class CommonController extends AbstractCRUDController return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate')); } - /** - * 인덱스(목록) 페이지의 메인 로직입니다. - */ protected function index_process(string $action): void { - // 현재 URL을 이전 URL 스택에 저장 - $this->getAuthContext()->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : "")); + $this->getAuthContext()->pushCurrentUrl( + $this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : "") + ); $this->addViewDatas('uri', $this->request->getUri()); - // Paging 설정 + $page = (int) $this->request->getVar('page') ?: 1; $perpage = (int) $this->request->getVar('perpage') ?: intval(DEFAULTS['INDEX_PERPAGE'] ?? 10); $this->addViewDatas('page', $page); $this->addViewDatas('perpage', $perpage); - // 1. Total Count 계산을 위한 조건절 처리 (오버라이드 가능) + $this->index_condition_process($action); $index_totalcount = $this->service->getTotalCount(); $this->addViewDatas('index_totalcount', $index_totalcount); - // Pagination 설정 + $this->addViewDatas('index_pagination', $this->pagenation_process($index_totalcount, $page, $perpage)); $this->addViewDatas('index_pagination_options', $this->pagenation_options_process($index_totalcount, $perpage)); - // 2. 실제 리스트를 위한 조건절, LIMIT, OFFSET 처리 (오버라이드 가능) - $this->index_condition_process($action); // 조건절을 다시 호출하여 필터/검색어 유지 + + $this->index_condition_process($action); $this->service->setLimit($perpage); $this->service->setOffset(($page - 1) * $perpage); - // Entities 처리 + $this->addViewDatas('entities', $this->index_entities_process()); helper(['form']); $this->addViewDatas('formDatas', $this->request->getVar() ?? []); } - public function index(): string + public function index(): string|RedirectResponse|ResponseInterface { $action = __FUNCTION__; - $this->action_init_process($action); - $this->index_process($action); - return $this->index_result_process($action); + + return $this->runAction($action, function () use ($action) { + $this->action_init_process($action); + $this->index_process($action); + return $this->index_result_process($action); + }); } - // --- 문서 다운로드 (Download) --- + // ========================================================= + // Download + // ========================================================= + protected function downloadByDocumentType(string $document_type, mixed $loaded_data): array { $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "download"; + switch ($document_type) { case 'excel': $file_name = sprintf("%s_%s.xlsx", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); $writer = IOFactory::createWriter($loaded_data, 'Xlsx'); $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); break; + case 'pdf': $file_name = sprintf("%s_%s.pdf", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm')); $writer = new Mpdf($loaded_data); $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name); break; + default: throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 지원하지 않는 다운로드 타입입니다: {$document_type}"); } - return array($full_path, $file_name); + + return [$full_path, $file_name]; } protected function download_process(string $action, string $output_type, mixed $uid = null): DownloadResponse|RedirectResponse|string @@ -247,45 +252,45 @@ abstract class CommonController extends AbstractCRUDController case 'excel': case 'pdf': helper(['form']); - // 전체 목록을 다운로드하므로, 목록 조건절을 처리합니다. + $this->index_condition_process($action); $this->addViewDatas('entities', $this->index_entities_process()); - // HTML로 렌더링된 내용을 가져옵니다. $html = $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate')); - // HTML을 PhpSpreadsheet 객체로 로드합니다. $reader = new Html(); $loaded_data = $reader->loadFromString($html); - // 파일 저장 및 정보 가져오기 - list($full_path, $file_name) = $this->downloadByDocumentType($output_type, $loaded_data); + [$full_path, $file_name] = $this->downloadByDocumentType($output_type, $loaded_data); $full_path .= DIRECTORY_SEPARATOR . $file_name; break; + default: - // 개별 파일 다운로드 로직 if (!$uid) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$output_type}은 반드시 uid의 값이 필요합니다."); } + $entity = $this->service->getEntity($uid); if (!$entity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$uid}에 대한 정보를 찾을수 없습니다."); } + $this->addViewDatas('entity', $entity); - list($file_name, $uploaded_filename) = $entity->getDownlaodFile(); + [$file_name, $uploaded_filename] = $entity->getDownlaodFile(); $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename; break; } + return $this->response->download($full_path, null)->setFileName($file_name); } - final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string + + final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string|ResponseInterface { - try { - $action = __FUNCTION__; + $action = __FUNCTION__; + + return $this->runAction($action, function () use ($action, $output_type, $uid) { $this->action_init_process($action); return $this->download_process($action, $output_type, $uid); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 다운로드 오류:" . $e->getMessage()); - } + }); } } diff --git a/app/Controllers/Front/FrontController.php b/app/Controllers/Front/FrontController.php index 721d18d..410292d 100644 --- a/app/Controllers/Front/FrontController.php +++ b/app/Controllers/Front/FrontController.php @@ -15,7 +15,6 @@ abstract class FrontController extends CommonController parent::initController($request, $response, $logger); $this->addActionPaths($this->_layout); $this->layouts = LAYOUTS[$this->_layout]; - helper('util'); } protected function action_init_process(string $action, array $formDatas = []): void { diff --git a/app/DTOs/Auth/AuthDTO.php b/app/DTOs/Auth/AuthDTO.php deleted file mode 100644 index b0124bb..0000000 --- a/app/DTOs/Auth/AuthDTO.php +++ /dev/null @@ -1,13 +0,0 @@ - $value) { - if (!$reflection->hasProperty($key)) - continue; - - $property = $reflection->getProperty($key); - $type = $property->getType(); - $assignValue = $value; - - // *_uid 규칙 처리 - if ($value === '' && preg_match('/_uid$/', $key)) { - if ($type instanceof ReflectionNamedType && $type->allowsNull()) { - $this->{$key} = null; - continue; - } - } - - // 1) 기존: 빈 문자열('') 처리 - if ($value === '') { - if ($type instanceof ReflectionNamedType && $type->allowsNull()) { - $assignValue = null; - } else { - $typeName = ($type instanceof ReflectionNamedType) ? $type->getName() : ''; - $assignValue = ($typeName === 'int' || $typeName === 'float') ? 0 : ''; - } - } - // 2) 기존: 타입별 캐스팅 - elseif ($type instanceof ReflectionNamedType) { - $typeName = $type->getName(); - - if ($typeName === 'array' && is_string($value)) { - $assignValue = explode(DEFAULTS["DELIMITER_COMMA"], $value); - } elseif ($typeName === 'int' && is_numeric($value)) { - $assignValue = (int) $value; - } elseif ($typeName === 'float' && is_numeric($value)) { - $assignValue = (float) $value; - } - } - - $this->{$key} = $assignValue; - } - } - - public function toArray(): array - { - $reflection = new ReflectionClass($this); - $properties = $reflection->getProperties(); - $result = []; - - foreach ($properties as $property) { - $name = $property->getName(); - $result[$name] = $this->{$name}; - } - - return $result; - } -} diff --git a/app/DTOs/InquiryDTO.php b/app/DTOs/InquiryDTO.php deleted file mode 100644 index 1b1377f..0000000 --- a/app/DTOs/InquiryDTO.php +++ /dev/null @@ -1,17 +0,0 @@ -role); - } -} diff --git a/app/Helpers/util_helper.php b/app/Helpers/util_helper.php index a90a78d..9f0e6ab 100644 --- a/app/Helpers/util_helper.php +++ b/app/Helpers/util_helper.php @@ -20,3 +20,25 @@ if (!function_exists('alertTrait')) { return $util->alertTrait($msg, $url); } } + +if (!function_exists('dev_exception')) { + function dev_exception(string $message): RuntimeException + { + if (ENVIRONMENT === 'development') { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $caller = $trace[1] ?? null; + + if ($caller) { + $message = sprintf( + '%s->%s에서 오류발생: %s', + $caller['class'] ?? '', + $caller['function'] ?? '', + $message + ); + } + } + + return new RuntimeException($message); + } +} + diff --git a/app/Language/ko/Validation.php b/app/Language/ko/Validation.php index 5be2db7..452fce7 100644 --- a/app/Language/ko/Validation.php +++ b/app/Language/ko/Validation.php @@ -1,6 +1,9 @@ login_process($dto->toArray()); + $entity = $this->login_process($formDatas); //인증 세션처리 $this->getAuthContext()->setAuthSession($entity); return $entity; diff --git a/app/Services/Auth/GoogleService.php b/app/Services/Auth/GoogleService.php index ca2b909..1f7db7b 100644 --- a/app/Services/Auth/GoogleService.php +++ b/app/Services/Auth/GoogleService.php @@ -2,7 +2,6 @@ namespace App\Services\Auth; -use App\DTOs\Auth\GoogleDTO; use App\Entities\UserEntity; use App\Forms\Auth\GoogleForm; use App\Libraries\MySocket\GoogleSocket\CURL; @@ -18,14 +17,6 @@ class GoogleService extends AuthService parent::__construct($model); $this->addClassPaths('Google'); } - public function createDTO(array $formDatas): GoogleDTO - { - return new GoogleDTO($formDatas); - } - public function getDTOClass(): string - { - return GoogleDTO::class; - } protected function getEntity_process(mixed $entity): UserEntity { return $entity; diff --git a/app/Services/Auth/LocalService.php b/app/Services/Auth/LocalService.php index 3beaa3e..9eff5f4 100644 --- a/app/Services/Auth/LocalService.php +++ b/app/Services/Auth/LocalService.php @@ -2,7 +2,6 @@ namespace App\Services\Auth; -use App\DTOs\Auth\LocalDTO; use App\Entities\UserEntity; use App\Forms\Auth\LocalForm; use App\Models\UserModel; @@ -17,14 +16,6 @@ class LocalService extends AuthService parent::__construct($model); $this->addClassPaths('Local'); } - public function createDTO(array $formDatas): LocalDTO - { - return new LocalDTO($formDatas); - } - public function getDTOClass(): string - { - return LocalDTO::class; - } protected function getEntity_process(mixed $entity): UserEntity { return $entity; diff --git a/app/Services/BoardService.php b/app/Services/BoardService.php index a8c2cd6..0bacf93 100644 --- a/app/Services/BoardService.php +++ b/app/Services/BoardService.php @@ -6,7 +6,6 @@ use App\Models\BoardModel; use App\Helpers\BoardHelper; use App\Forms\BoardForm; use App\Entities\BoardEntity; -use App\DTOs\BoardDTO; class BoardService extends CommonService { @@ -18,14 +17,6 @@ class BoardService extends CommonService parent::__construct($model); $this->addClassPaths('Board'); } - public function getDTOClass(): string - { - return BoardDTO::class; - } - public function createDTO(array $formDatas): BoardDTO - { - return new BoardDTO($formDatas); - } public function getEntityClass(): string { return BoardEntity::class; diff --git a/app/Services/CommonService.php b/app/Services/CommonService.php index c12820e..c9003c3 100644 --- a/app/Services/CommonService.php +++ b/app/Services/CommonService.php @@ -3,7 +3,6 @@ namespace App\Services; use App\Forms\CommonForm; -use App\DTOs\CommonDTO; use App\Entities\CommonEntity; use App\Models\CommonModel; use App\Libraries\AuthContext; @@ -26,9 +25,6 @@ abstract class CommonService protected function __construct(protected CommonModel $model) { } - - abstract public function getDTOClass(): string; - abstract public function createDTO(array $formDatas): CommonDTO; abstract public function getEntityClass(): string; /** diff --git a/app/Services/InquiryService.php b/app/Services/InquiryService.php index 77d590f..f45011b 100644 --- a/app/Services/InquiryService.php +++ b/app/Services/InquiryService.php @@ -2,7 +2,6 @@ namespace App\Services; -use App\DTOs\InquiryDTO; use App\Forms\InquiryForm; use App\Models\InquiryModel; use App\Entities\CommonEntity; @@ -19,14 +18,6 @@ class InquiryService extends CommonService parent::__construct($model); $this->addClassPaths('Inquiry'); } - public function getDTOClass(): string - { - return InquiryDTO::class; - } - public function createDTO(array $formDatas): InquiryDTO - { - return new InquiryDTO($formDatas); - } public function getEntityClass(): string { return InquiryEntity::class; diff --git a/app/Services/UserService.php b/app/Services/UserService.php index d2c738d..7466116 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -8,7 +8,6 @@ use App\Helpers\UserHelper; use App\Forms\UserForm; use App\Entities\UserEntity; use App\Entities\CommonEntity; -use App\DTOs\UserDTO; class UserService extends CommonService { @@ -20,14 +19,6 @@ class UserService extends CommonService parent::__construct($model); $this->addClassPaths('User'); } - public function getDTOClass(): string - { - return UserDTO::class; - } - public function createDTO(array $formDatas): UserDTO - { - return new UserDTO($formDatas); - } public function getEntityClass(): string { return UserEntity::class;