diff --git a/app/Config/Routes.php b/app/Config/Routes.php index b0b376f..baf8ed7 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -17,15 +17,18 @@ $routes->addPlaceholder('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4} $routes->group('cli', ['namespace' => 'App\Controllers\CLI'], function ($routes) { }); -$routes->group('', ['namespace' => 'App\Controllers'], function ($routes) { - $routes->get('/', 'Welcome::index'); - $routes->group('auth', ['namespace' => 'App\Controllers\Auth'], function ($routes) { - $routes->get('login', 'LocalController::login_form'); - $routes->post('login', 'LocalController::login'); - $routes->get('google_login', 'GoogleController::login'); - $routes->get('logout', 'LocalController::logout'); +$routes->group('', ['namespace' => 'App\Controllers\Front'], function ($routes) { + $routes->get('/', 'WelcomeController::index'); + $routes->group('inquiry', function ($routes) { + $routes->post('create', 'InquiryController::create'); }); }); +$routes->group('auth', ['namespace' => 'App\Controllers\Auth'], function ($routes) { + $routes->get('login', 'LocalController::login_form'); + $routes->post('login', 'LocalController::login'); + $routes->get('google_login', 'GoogleController::login'); + $routes->get('logout', 'LocalController::logout'); +}); //Admin ๊ด๋ จ $routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'authFilter:manager'], function ($routes) { $routes->get('/', 'Welcome::index'); @@ -59,5 +62,18 @@ $routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'au $routes->get('download/(:alpha)', 'BoardController::download/$1'); $routes->get('latest/(:alpha)', 'BoardController::latest/$1'); }); + $routes->group('inquiry', function ($routes) { + $routes->get('/', 'InquiryController::index'); + $routes->get('create', 'InquiryController::create_form'); + $routes->post('create', 'InquiryController::create'); + $routes->get('modify/(:num)', 'InquiryController::modify_form/$1'); + $routes->post('modify/(:num)', 'InquiryController::modify/$1'); + $routes->get('view/(:num)', 'InquiryController::view/$1'); + $routes->get('delete/(:num)', 'InquiryController::delete/$1'); + $routes->get('toggle/(:num)/(:any)', 'InquiryController::toggle/$1/$2'); + $routes->post('batchjob', 'InquiryController::batchjob'); + $routes->post('batchjob_delete', 'InquiryController::batchjob_delete'); + $routes->get('download/(:alpha)', 'InquiryController::download/$1'); + }); }); //choi.jh \ No newline at end of file diff --git a/app/Config/Services.php b/app/Config/Services.php index be04340..76d1190 100644 --- a/app/Config/Services.php +++ b/app/Config/Services.php @@ -7,8 +7,9 @@ use CodeIgniter\Config\BaseService; //choi.jh use App\Services\Auth\GoogleService; use App\Services\Auth\LocalService; -use App\Services\BoardService; use App\Services\UserService; +use App\Services\BoardService; +use App\Services\InquiryService; //choi.jh /** @@ -88,5 +89,14 @@ class Services extends BaseService new \App\Models\BoardModel(), ); } + public static function inquiryservice($getShared = true): InquiryService + { + if ($getShared) { + return static::getSharedInstance(__FUNCTION__); + } + return new InquiryService( + new \App\Models\InquiryModel(), + ); + } //choi.jh } diff --git a/app/Controllers/AbstractCRUDController.php b/app/Controllers/AbstractCRUDController.php index 0e74611..59da195 100644 --- a/app/Controllers/AbstractCRUDController.php +++ b/app/Controllers/AbstractCRUDController.php @@ -2,9 +2,11 @@ namespace App\Controllers; +use RuntimeException; use App\Entities\CommonEntity; use CodeIgniter\HTTP\RedirectResponse; -use RuntimeException; +use CodeIgniter\HTTP\ResponseInterface; +use App\Exceptions\FormValidationException; /** * AbstractCRUDController @@ -12,8 +14,6 @@ use RuntimeException; */ abstract class AbstractCRUDController extends AbstractWebController { - // ๐ก ํต์ฌ 1: ๊ฐ ์์ ํด๋์ค๊ฐ ์ฌ์ฉํ Entity ํด๋์ค ๊ฒฝ๋ก๋ฅผ ๋ฐํํ๋๋ก ๊ฐ์ - // ์ด ๋ฉ์๋๋ ์์ ํด๋์ค์์ ๋ฐ๋์ ๊ตฌํ๋์ด์ผ ํฉ๋๋ค. // --- ์์ฑ (Create) --- protected function create_form_process(array $formDatas = []): array { @@ -39,20 +39,24 @@ abstract class AbstractCRUDController extends AbstractWebController return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "์์ {$this->getTitle()} ์์ฑํผ ์ค๋ฅ:" . $e->getMessage()); } } + protected function create_process(array $formDatas): CommonEntity { // POST ๋ฐ์ดํฐ๋ฅผ DTO ๊ฐ์ฒด๋ก ๋ณํ $dto = $this->service->createDTO($formDatas); - // dd($dto->toArray()); + //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()); } - protected function create_result_process($entity, ?string $redirect_url = null): string|RedirectResponse + protected function create_result_process(CommonEntity $entity, ?string $redirect_url = null): string|RedirectResponse { return $this->action_redirect_process( 'info', @@ -61,19 +65,61 @@ abstract class AbstractCRUDController extends AbstractWebController ); } - final public function create(): string|RedirectResponse + final public function create(): string|RedirectResponse|ResponseInterface { try { $action = __FUNCTION__; $this->action_init_process($action); + $entity = $this->create_process($this->request->getPost()); + // ๐ก ๋์ ์ผ๋ก ๊ฐ์ ธ์จ 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()); } } @@ -98,7 +144,6 @@ abstract class AbstractCRUDController extends AbstractWebController $entity = $this->modify_form_process($uid); $this->addViewDatas('entity', $entity); $action = __FUNCTION__; - //FormService์์ ํ์ํ ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ $entity์์ ์ถ์ถํด์ ๋๊น $this->action_init_process($action, $entity->toArray()); return $this->modify_form_result_process($action); } catch (\Throwable $e) { @@ -108,7 +153,6 @@ abstract class AbstractCRUDController extends AbstractWebController protected function modify_process($uid, array $formDatas): CommonEntity { - // POST ๋ฐ์ดํฐ๋ฅผ DTO ๊ฐ์ฒด๋ก ๋ณํ $formDatas[$this->service->getPKField()] = $uid; $dto = $this->service->createDTO($formDatas); //DTO ํ์ ์ฒดํฌ ๋ก์ง์ ์ผ๋ฐํ @@ -119,7 +163,76 @@ abstract class AbstractCRUDController extends AbstractWebController return $this->service->modify($uid, $dto->toArray()); } - protected function modify_result_process($entity, ?string $redirect_url = null): string|RedirectResponse + final public function modify($uid): string|RedirectResponse|ResponseInterface + { + try { + if (!$uid) { + throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ {$this->getTitle()}์ ๋ฒํธ๊ฐ ์ ์ ๋์ง ์์์ต๋๋ค."); + } + + $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->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() + ); + } + } + + + protected function modify_result_process(CommonEntity $entity, ?string $redirect_url = null): string|RedirectResponse { return $this->action_redirect_process( 'info', @@ -127,31 +240,18 @@ abstract class AbstractCRUDController extends AbstractWebController $redirect_url ?? '/' . implode('/', [...$this->getActionPaths(), 'view']) . '/' . $entity->getPK() ); } - final public function modify($uid): string|RedirectResponse - { - try { - if (!$uid) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ {$this->getTitle()}์ ๋ฒํธ๊ฐ ์ ์ ๋์ง ์์์ต๋๋ค."); - } - $action = __FUNCTION__; - $this->action_init_process($action); - $entity = $this->modify_process($uid, $this->request->getPost()); - $this->addViewDatas('entity', $entity); - return $this->modify_result_process($entity); - } catch (\Throwable $e) { - return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "์์ {$this->getTitle()} ์์ ์ค๋ฅ:" . $e->getMessage()); - } - } // --- ์ญ์ (Delete) --- protected function delete_process($uid): CommonEntity { return $this->service->delete($uid); } + protected function delete_result_process($entity, ?string $redirect_url = null): string|RedirectResponse { return $this->action_redirect_process('info', "{$this->getTitle()}์์ {$entity->getTitle()} ์ญ์ ๊ฐ ์๋ฃ๋์์ต๋๋ค.", $redirect_url); } + final public function delete($uid): RedirectResponse { try { @@ -159,7 +259,6 @@ abstract class AbstractCRUDController extends AbstractWebController throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ {$this->getTitle()}์ ๋ฒํธ๊ฐ ์ ์ ๋์ง ์์์ต๋๋ค."); } $entity = $this->service->getEntity($uid); - //Delete์ฒ๋ฆฌ $entity = $this->delete_process($uid); return $this->delete_result_process($entity); } catch (\Throwable $e) { @@ -172,20 +271,20 @@ abstract class AbstractCRUDController extends AbstractWebController { return $this->service->getEntity($uid); } + protected function view_result_process(string $action): string { return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate')); } + final public function view($uid): string|RedirectResponse { try { if (!$uid) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ {$this->getTitle()}์ ๋ฒํธ๊ฐ ์ ์ ๋์ง ์์์ต๋๋ค."); } - //View์ฒ๋ฆฌ $entity = $this->view_process($uid); $action = __FUNCTION__; - //FormService์์ ํ์ํ ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ $entity์์ ์ถ์ถํด์ ๋๊น $this->action_init_process($action, $entity->toArray()); $this->addViewDatas('entity', $entity); return $this->view_result_process($action); diff --git a/app/Controllers/Admin/InquiryController.php b/app/Controllers/Admin/InquiryController.php new file mode 100644 index 0000000..924d5da --- /dev/null +++ b/app/Controllers/Admin/InquiryController.php @@ -0,0 +1,22 @@ +service === null) { + $this->service = service('inquiryservice'); + } + $this->addActionPaths('Inquiry'); + } + //Action์์ ๊ด๋ จ + //๊ธฐ๋ณธ ํจ์ ์์ + //Custom ์ถ๊ฐ ํจ์ +} diff --git a/app/Controllers/FrontController.php b/app/Controllers/Front/FrontController.php similarity index 96% rename from app/Controllers/FrontController.php rename to app/Controllers/Front/FrontController.php index aeaa223..721d18d 100644 --- a/app/Controllers/FrontController.php +++ b/app/Controllers/Front/FrontController.php @@ -1,6 +1,6 @@ addActionPaths($this->_layout); $this->layouts = LAYOUTS[$this->_layout]; + helper('util'); } protected function action_init_process(string $action, array $formDatas = []): void { diff --git a/app/Controllers/Front/InquiryController.php b/app/Controllers/Front/InquiryController.php new file mode 100644 index 0000000..6039fed --- /dev/null +++ b/app/Controllers/Front/InquiryController.php @@ -0,0 +1,31 @@ +service === null) { + $this->service = service('inquiryservice'); + } + $this->addActionPaths('Inquiry'); + } + //Action์์ ๊ด๋ จ + //๊ธฐ๋ณธ ํจ์ ์์ + //Custom ์ถ๊ฐ ํจ์ + + protected function create_result_process($entity, ?string $redirect_url = null): string|RedirectResponse + { + return $this->action_redirect_process( + 'info', + "{$this->getTitle()}์์ {$entity->getTitle()} ๋ฌธ์ ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค.", + ); + } +} diff --git a/app/Controllers/Welcome.php b/app/Controllers/Front/WelcomeController.php similarity index 94% rename from app/Controllers/Welcome.php rename to app/Controllers/Front/WelcomeController.php index f2214a7..04f096e 100644 --- a/app/Controllers/Welcome.php +++ b/app/Controllers/Front/WelcomeController.php @@ -1,12 +1,12 @@ '', + 'email' => '', + 'status' => '', + 'content' => '' + ]; + public function __construct(array|null $data = null) + { + parent::__construct($data); + } +} diff --git a/app/Exceptions/FormValidationException.php b/app/Exceptions/FormValidationException.php new file mode 100644 index 0000000..6f7c205 --- /dev/null +++ b/app/Exceptions/FormValidationException.php @@ -0,0 +1,16 @@ +errors = $errors; + parent::__construct($message, $code, $previous); + } +} diff --git a/app/Forms/CommonForm.php b/app/Forms/CommonForm.php index aff5df4..1f09d15 100644 --- a/app/Forms/CommonForm.php +++ b/app/Forms/CommonForm.php @@ -2,6 +2,7 @@ namespace App\Forms; +use App\Exceptions\FormValidationException; use RuntimeException; /** @@ -13,11 +14,14 @@ use RuntimeException; * 3) validate()์์ dynamicRules ๋์ ๋ฒ๊ทธ ์์ (๋ง์ง๋ง ๊ท์น๋ง ๋จ๋ ๋ฌธ์ ํด๊ฒฐ) * 4) "ํ๋ ์กด์ฌ ๋ณด์ฅ"์ผ๋ก ์์ '' ์ฝ์ ์ ๊ฑฐ (๋ฏธ์ ๋ ฅ ํ๋๊ฐ FK/์ซ์ ๊ท์น์ ๊นจ๋ ๋ฌธ์ ๋ฐฉ์ง) * 5) role.* ๊ฐ์ ๋ฐฐ์ด ์์ ๊ท์น์ ์ํด ๋ถ๋ชจ ๋ฐฐ์ด ๋ณด์ ๋ก์ง ์ ์ง/๊ฐํ + * + * โ ์ถ๊ฐ: + * - validate() ์คํจ ์ RuntimeException(implode) ๋์ + * FormValidationException(errors ๋ฐฐ์ด)์ throwํ์ฌ + * Controller์์ AJAX(422 JSON errors) ์๋ต์ด ๊ฐ๋ฅํ๊ฒ ํจ */ abstract class CommonForm { - private $_validation = null; - private array $_attributes = []; private array $_formFields = []; private array $_formRules = []; @@ -27,10 +31,11 @@ abstract class CommonForm private array $_formOptions = []; private array $_actionButtons = ['view' => ICONS['SEARCH'], 'delete' => ICONS['DELETE']]; private array $_batchjobButtons = ['batchjob' => '์ผ๊ด์ฒ๋ฆฌ', 'batchjob_delete' => '์ผ๊ด์ญ์ ']; + protected $validation = null; protected function __construct() { - $this->_validation = service('validation'); + $this->validation = service('validation'); } public function action_init_process(string $action, array &$formDatas = []): void @@ -160,7 +165,6 @@ abstract class CommonForm /** * 1) ๊น์ ๋ฐฐ์ด ๊ตฌ์กฐ ์ ๋ฆฌ(๋ฐฐ์ด์ ์ ์ง) * - ์ฌ๊ธฐ์๋ null -> '' ๊ฐ์ ๋ณํ์ ์ ๋ ํ์ง ์์ต๋๋ค. - * - ์ด์ : FK/์ซ์/๋ ์ง ํ๋๊ฐ ''๋ก ๋ณํ๋ฉด validation/DB์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํจ. */ protected function sanitizeFormDatas($data, string $path = '') { @@ -179,11 +183,8 @@ abstract class CommonForm /** * 2) ์ซ์/FK ํ๋ ์ ๊ทํ - * - ํผ์์ ๋ฏธ์ ํ์ ๋ณดํต ''๋ก ๋ค์ด์ด -> NULL๋ก ๋ณํ - * - ์ซ์ ๋ฌธ์์ด์ int ์บ์คํ (์ ํ) - * - * ์ฃผ์: - * - "๋น๊ฐ์ 0์ผ๋ก ์ทจ๊ธ" ๊ฐ์ ์ ์ฑ ์ด ์๋ค๋ฉด ์ฌ๊ธฐ์์ ์กฐ์ ํด์ผ ํจ. + * - '' -> null + * - ์ซ์ ๋ฌธ์์ด -> int */ protected function normalizeNumericEmptyToNull(array $data, array $numericFields): array { @@ -245,7 +246,7 @@ abstract class CommonForm $formDatas[$parent] = []; } - // โ 4) ํต์ฌ: ๋ฐฐ์ด ์์์ null/'' ์ ๊ฑฐ + ๋ฌธ์์ดํ(Trim์ด null ๋ฐ์ง ์๋๋ก) + // 4) ๋ฐฐ์ด ์์ ์ ๋ฆฌ $clean = array_map( fn($v) => is_scalar($v) ? trim((string) $v) : '', $formDatas[$parent] @@ -256,14 +257,8 @@ abstract class CommonForm } } - /** - * 4) ๊ฒ์ฆ rule์ ๋ฐ๋ผ "numeric(ํนํ FK)"๋ก ์ทจ๊ธํ ํ๋๋ฅผ ์์ง - * - getFormRule()์์ permit_empty|numeric ๋ก ์ ์๋๋ ํ๋๋ฅผ ๊ณตํต ์ฒ๋ฆฌํ๊ธฐ ์ํจ - * - * ๊ตฌํ ์ ๋ต: - * - formRules์์ rule ๋ฌธ์์ด์ 'numeric'๊ฐ ํฌํจ๋ ํ๋๋ฅผ ๋ชจ์ - * - wildcard(role.*) ์ ์ธ + * 4) ๊ฒ์ฆ rule์ ๋ฐ๋ผ numeric(FK ํฌํจ) ํ๋ ์์ง */ protected function collectNumericFieldsFromRules(array $formRules): array { @@ -276,7 +271,7 @@ abstract class CommonForm continue; } - // getValidationRule hook ์ ์ฉ (ํ๋๋ช /๋ฃฐ์ด ๋ฐ๋ ์ ์์ผ๋) + // hook ์ ์ฉ [$fieldName, $ruleStr] = $this->getValidationRule($fieldName, (string) $rule); if (is_string($ruleStr) && str_contains($ruleStr, 'numeric')) { @@ -284,7 +279,6 @@ abstract class CommonForm } } - // ์ค๋ณต ์ ๊ฑฐ return array_values(array_unique($numericFields)); } @@ -294,20 +288,15 @@ abstract class CommonForm /** * ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ๊ณ ์ ํจํ์ง ์์ ๊ฒฝ์ฐ ์์ธ๋ฅผ ๋ฐ์์ํต๋๋ค. - * 2025 CI4 ํ์ค: ๊ท์น ๋ฐฐ์ด ๋ด์ label์ ํฌํจํ์ฌ ํ๊ธ ๋ฉ์์ง ์ถ๋ ฅ์ ๋ณด์ฅํฉ๋๋ค. + * โ ๋ณ๊ฒฝ์ : + * - ์คํจ ์ FormValidationException(errors ๋ฐฐ์ด)์ throw + * (AJAX์์ 422๋ก ๋ด๋ ค๋ณด๋ด๊ธฐ ์ํจ) */ final public function validate(array &$formDatas): void { log_message('debug', '>>> CommonForm::validate CALLED: ' . static::class); - if ($this->_validation === null) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ ์ค๋ฅ๋ฐ์: Validation ์๋น์ค๊ฐ ์ด๊ธฐํ๋์ง ์์์ต๋๋ค."); - } - try { - // 0) ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ๋ฆฌ (null ๋ณํ X) $formDatas = $this->sanitizeFormDatas($formDatas); - - // 1) ํ๋ ๋ผ๋ฒจ/๊ท์น $formFields = $this->getFormFields(); $formRules = $this->getFormRules(); @@ -315,61 +304,42 @@ abstract class CommonForm throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ ์ค๋ฅ๋ฐ์: ์ง์ ๋ Form RULE์ด ์์ต๋๋ค."); } - // 2) wildcard(role.*) ๋ถ๋ชจ ๋ฐฐ์ด ๋ณด์ $this->ensureParentArrayForWildcardRules($formDatas, $formRules); - // 3) numeric(FK ํฌํจ) ํ๋: '' -> null, ์ซ์ ๋ฌธ์์ด -> int - // (๊ท์น ๊ธฐ๋ฐ ์๋ ์์ง) $numericFields = $this->collectNumericFieldsFromRules($formRules); $formDatas = $this->normalizeNumericEmptyToNull($formDatas, $numericFields); - // 4) dynamicRules ๋์ ๊ตฌ์ฑ (๋ฒ๊ทธ ์์ : ๋ฃจํ๋ง๋ค ์ด๊ธฐํ ๊ธ์ง) $dynamicRules = []; foreach ($formRules as $field => $rule) { - try { - // ํ๋๋ช /๊ท์น ์ถ์ถ(ํ์ฅ ํฌ์ธํธ) - [$fieldName, $ruleStr] = $this->getValidationRule((string) $field, (string) $rule); + [$fieldName, $ruleStr] = $this->getValidationRule((string) $field, (string) $rule); - // label ๊ฒฐ์ - if (isset($formFields[$fieldName])) { - $label = $formFields[$fieldName]; - } elseif (str_contains($fieldName, '.*')) { - $parentField = str_replace('.*', '', $fieldName); - $label = ($formFields[$parentField] ?? $fieldName) . " ํญ๋ชฉ"; - } else { - $label = $fieldName; - } - - $dynamicRules[$fieldName] = [ - 'label' => $label, - 'rules' => $ruleStr, - ]; - - // โ ์กด์ฌ ๋ณด์ฅ์ผ๋ก '' ์ฝ์ ํ์ง ์์ - // - required๋ CI4๊ฐ "ํค ์์"๋ ์คํจ ์ฒ๋ฆฌ ๊ฐ๋ฅ(์ผ๋ฐ์ ์ผ๋ก) - // - permit_empty๋ ํค ์์ด๋ ํต๊ณผ (๊ฐ์ ๋ก '' ๋ง๋ค๋ฉด FK/์ซ์ ๋ฌธ์ ๋ฐ์) - - } catch (\Throwable $e) { - throw new RuntimeException("์ ํจ์ฑ ๊ฒ์ฌ ๊ท์น ์ค๋น ์ค ์ค๋ฅ ๋ฐ์ (ํ๋: {$field}): " . $e->getMessage()); + if (isset($formFields[$fieldName])) { + $label = $formFields[$fieldName]; + } elseif (str_contains($fieldName, '.*')) { + $parentField = str_replace('.*', '', $fieldName); + $label = ($formFields[$parentField] ?? $fieldName) . ' ํญ๋ชฉ'; + } else { + $label = $fieldName; } + + $dynamicRules[$fieldName] = [ + 'label' => $label, + 'rules' => $ruleStr, + ]; } - $this->_validation->setRules($dynamicRules); + $this->validation->setRules($dynamicRules); - try { - if (!$this->_validation->run($formDatas)) { - $errors = $this->_validation->getErrors(); - throw new RuntimeException(implode("\n", $errors)); - } - } catch (\TypeError $e) { - throw new RuntimeException("๊ฒ์ฆ ๋์ค ํ์ ์ค๋ฅ ๋ฐ์: " . $e->getMessage()); + if (!$this->validation->run($formDatas)) { + throw new FormValidationException($this->validation->getErrors()); } + } catch (FormValidationException $e) { + throw $e; // โ ํ๋๋ณ errors ์ ์ง + } catch (\TypeError $e) { + throw new RuntimeException('๊ฒ์ฆ ๋์ค ํ์ ์ค๋ฅ ๋ฐ์: ' . $e->getMessage()); } catch (\Throwable $e) { - if ($e instanceof RuntimeException) { - throw $e; - } - throw new RuntimeException("์ ํจ์ฑ ๊ฒ์ฌ ์ค ์์คํ ์ค๋ฅ ๋ฐ์: " . $e->getMessage()); + throw new RuntimeException('์ ํจ์ฑ ๊ฒ์ฌ ์ค ์์คํ ์ค๋ฅ ๋ฐ์: ' . $e->getMessage()); } } @@ -377,7 +347,6 @@ abstract class CommonForm * Overridable hooks * --------------------------------------------------------------------- */ - // ์ฌ์ฉ์ ์ ์ hook: ํ๋/๋ฃฐ ์ปค์คํฐ๋ง์ด์ฆ protected function getValidationRule(string $field, string $rule): array { return [$field, $rule]; @@ -393,11 +362,6 @@ abstract class CommonForm return $label; } - /** - * Form rule ์ ์ - * - permit_empty|numeric ์ธ FK๋ค์ด ์ฌ๊ธฐ์ ์ ์๋๋ฉด, - * validate()์์ ์๋์ผ๋ก ''->null ์ ๊ทํ ๋์์ ํฌํจ๋ฉ๋๋ค. - */ public function getFormRule(string $action, string $field, array $formRules): array { switch ($field) { @@ -411,32 +375,39 @@ abstract class CommonForm $formRules[$field] = "required|numeric"; } break; + case $this->getAttribute('title_field'): $formRules[$field] = sprintf( "required|trim|string%s", in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : "" ); break; + case "code": $formRules[$field] = sprintf( "required|regex_match[/^[a-zA-Z0-9๊ฐ-ํฃ\-\_]+$/]|min_length[4]%s", in_array($action, ["create"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : "" ); break; + case "user_uid": $formRules[$field] = "required|numeric"; break; + case "status": $formRules[$field] = "required|trim|string"; break; + case 'picture': $formRules[$field] = "is_image[{$field}]|mime_in[{$field},image/jpg,image/jpeg,image/gif,image/png,image/webp]|max_size[{$field},300]|max_dims[{$field},2048,768]"; break; + case "updated_at": case "created_at": case "deleted_at": $formRules[$field] = "permit_empty|trim|valid_date"; break; + default: $formRules[$field] = "permit_empty|trim|string"; break; diff --git a/app/Forms/InquiryForm.php b/app/Forms/InquiryForm.php new file mode 100644 index 0000000..b91537a --- /dev/null +++ b/app/Forms/InquiryForm.php @@ -0,0 +1,82 @@ +setFormFields($fields); + $this->setFormRules($action, $fields); + $this->setFormFilters($filters); + $this->setFormOptions($action, $filters, $formDatas); + $this->setIndexFilters($indexFilter); + $this->setBatchjobFilters($batchjobFilters); + } + + public function getFormRule(string $action, string $field, array $formRules): array + { + switch ($field) { + case "title": + case "content": + $formRules[$field] = "required|trim|string"; + break; + case "email": + $formRules[$field] = sprintf("required|trim|valid_email%s", in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : ""); + break; + case "status": + $formRules[$field] = "permit_empty|trim|string"; + break; + default: + $formRules = parent::getFormRule($action, $field, $formRules); + break; + } + return $formRules; + } +} diff --git a/app/Helpers/InquiryHelper.php b/app/Helpers/InquiryHelper.php new file mode 100644 index 0000000..86aa7fd --- /dev/null +++ b/app/Helpers/InquiryHelper.php @@ -0,0 +1,11 @@ +alertTrait($msg, $url); + } +} diff --git a/app/Language/ko/Inquiry.php b/app/Language/ko/Inquiry.php new file mode 100644 index 0000000..ee05668 --- /dev/null +++ b/app/Language/ko/Inquiry.php @@ -0,0 +1,19 @@ + "๋ฌธ์์ ๋ณด", + 'label' => [ + 'uid' => "๋ฒํธ", + 'title' => "์ ๋ชฉ", + 'email' => "์ด๋ฉ์ผ", + 'content' => "๋ด์ฉ", + 'status' => "์ํ", + 'updated_at' => "์์ ์ผ", + 'created_at' => "์์ฑ์ผ", + 'deleted_at' => "์ญ์ ์ผ", + ], + "STATUS" => [ + STATUS['AVAILABLE'] => "๋ฌธ์", + STATUS['PAUSE'] => "์ผ์์ ์ง", + STATUS['TERMINATED'] => "์๋ฃ", + ], +]; diff --git a/app/Models/CommonModel.php b/app/Models/CommonModel.php index 2c60d32..81e16c9 100644 --- a/app/Models/CommonModel.php +++ b/app/Models/CommonModel.php @@ -9,21 +9,15 @@ 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 = true; (๊ธฐ๋ณธ๊ฐ) - // ์ด ๊ธฐ๋ณธ ์ค์ ๋๋ฌธ์ PHP์ null ๊ฐ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ์ ๋ฌ๋ ๋ ์ค์ SQL์ NULL ํค์๋๊ฐ ์๋ **๋น ๋ฌธ์์ด ('')**๋ก ๋ณํ๋๊ณ ์์ต๋๋ค. - // ๊ทธ๋ฆฌ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค(MySQL ๋ฑ)์ ์ค์ ์ ๋ฐ๋ผ ๋น ๋ฌธ์์ด์ด ์ ๋ฐ์ดํธ ์ฟผ๋ฆฌ์์ ๋ฌด์๋๊ฑฐ๋, ํด๋น ์ปฌ๋ผ์ ๊ธฐ์กด ๊ฐ์ด ์ ์ง๋๋ ํ์์ด ๋ฐ์ํฉ๋๋ค. - protected $useEmptyStringIfNull = false; //NULL๊ฐ๋ ๋ฃ์๋ ค๋ฉด false + protected $useEmptyStringIfNull = false; // NULL๋ DB๋ก ๋ณด๋ด๊ธฐ protected array $casts = []; protected array $castHandlers = []; @@ -43,64 +37,97 @@ abstract class CommonModel extends Model // Callbacks protected $allowCallbacks = true; - protected $beforeInsert = ['emptyStringToNull']; //Field ๊ฐ์ด NULL์ผ ๊ฒฝ์ฐ DB Default๊ฐ ์ ์ฉ์ฉ + + /** + * โ ๋ณ๊ฒฝ: + * - beforeInsert: emptyStringToNull + applyDbDefaultsOnInsert + * - beforeUpdate: emptyStringToNull๋ง ์ ์ง (UPDATE์์๋ DB default๋ฅผ ์ฐ๋ ค๊ณ ์ปฌ๋ผ์ ๋นผ๋ฉด ์ํ/์๋์ ๋ค๋ฆ) + */ + protected $beforeInsert = ['emptyStringToNull', 'applyDbDefaultsOnInsert']; protected $afterInsert = []; - protected $beforeUpdate = ['emptyStringToNull']; //Field ๊ฐ์ด NULL์ผ ๊ฒฝ์ฐ DB Default๊ฐ ์ ์ฉ์ฉ + protected $beforeUpdate = ['emptyStringToNull']; protected $afterUpdate = []; + protected $beforeFind = []; protected $afterFind = []; protected $beforeDelete = []; protected $afterDelete = []; - protected array $nullableFields = []; // ๋ชจ๋ธ๋ณ๋ก override + /** + * ๋น ๋ฌธ์์ด์ 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'])) { - $v = $data['data'][$field]; + if (!array_key_exists($field, $data['data'])) { + continue; + } - // ๋ฌธ์์ด์ด๋ฉด trim ํ, ๋น๋ฌธ์๋ฉด null - if (is_string($v)) { - $v = trim($v); - $data['data'][$field] = ($v === '') ? null : $v; - } else { - // ๋ฌธ์์ด์ด ์๋๋ฐ๋ '' ๊ฐ์ ์ผ์ด์ค ๋ฐฉ์ด (๊ฑฐ์ ์์) - if ($v === '') - $data['data'][$field] = null; + $v = $data['data'][$field]; + + if (is_string($v)) { + $v = trim($v); + $data['data'][$field] = ($v === '') ? null : $v; + } else { + if ($v === '') { + $data['data'][$field] = null; } } } @@ -108,4 +135,50 @@ abstract class CommonModel extends Model 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; + } } diff --git a/app/Models/InquiryModel.php b/app/Models/InquiryModel.php new file mode 100644 index 0000000..27e0ff5 --- /dev/null +++ b/app/Models/InquiryModel.php @@ -0,0 +1,27 @@ +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 +56,7 @@ abstract class CommonService throw new RuntimeException($errorMessage, $e->getCode(), $e); } catch (\Throwable $e) { $db->transRollback(); - throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + throw $e; // โ ์ฌ๊ธฐ์๋ RuntimeException์ผ๋ก ๊ฐ์ธ์ง ๋ง ๊ฒ (๊ถ์ฅ) } } @@ -259,18 +263,13 @@ abstract class CommonService protected function create_process(array $formDatas): CommonEntity { try { - log_message('debug', "*** ENTER" . __METHOD__ . " ***"); $actionForm = $this->getActionForm(); - log_message('debug', 'FORMCLASS=' . $this->formClass . ' / FORMINST=' . (is_object($actionForm) ? get_class($actionForm) : 'NULL')); - log_message('debug', 'IS_COMMONFORM=' . (is_object($actionForm) && $actionForm instanceof CommonForm ? 'YES' : 'NO')); if ($actionForm instanceof CommonForm) { $actionForm->action_init_process('create', $formDatas); foreach ($formDatas as $field => $value) { $formDatas = $this->action_process_fieldhook($field, $value, $formDatas); } - log_message('debug', '>>> BEFORE validate: ' . get_class($actionForm)); $actionForm->validate($formDatas); // โ ์ฌ๊ธฐ์ ๊ฒ์ฆ - log_message('debug', '>>> AFTER validate'); } $entityClass = $this->getEntityClass(); $entity = new $entityClass($formDatas); @@ -278,6 +277,8 @@ abstract class CommonService throw new RuntimeException("Return Type์ {$entityClass}๋ง ๊ฐ๋ฅ"); } return $this->save_process($entity); + } catch (FormValidationException $e) { + throw $e; // โ ๊ฐ์ธ์ง ๋ง๊ณ ๊ทธ๋๋ก } catch (\Throwable $e) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ ์ค๋ฅ๋ฐ์:" . $e->getMessage()); } @@ -295,18 +296,13 @@ abstract class CommonService protected function modify_process($entity, array $formDatas): CommonEntity { try { - log_message('debug', "*** ENTER" . __METHOD__ . " ***"); $actionForm = $this->getActionForm(); - log_message('debug', 'FORMCLASS=' . $this->formClass . ' / FORMINST=' . (is_object($actionForm) ? get_class($actionForm) : 'NULL')); - log_message('debug', 'IS_COMMONFORM=' . (is_object($actionForm) && $actionForm instanceof CommonForm ? 'YES' : 'NO')); if ($actionForm instanceof CommonForm) { $actionForm->action_init_process('modify', $formDatas); foreach ($formDatas as $field => $value) { $formDatas = $this->action_process_fieldhook($field, $value, $formDatas); } - log_message('debug', '>>> BEFORE validate: ' . get_class($actionForm)); $actionForm->validate($formDatas); // โ ์ฌ๊ธฐ์ ๊ฒ์ฆ - log_message('debug', '>>> AFTER validate'); } // ๊ฒ์ฆ ํต๊ณผ ํ ์ํฐํฐ ๋ฐ์ $entity->fill($formDatas); @@ -314,9 +310,10 @@ abstract class CommonService return $entity; } return $this->save_process($entity); - + } catch (FormValidationException $e) { + throw $e; // โ ๊ฐ์ธ์ง ๋ง๊ณ ๊ทธ๋๋ก } catch (\Throwable $e) { - throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ ์ค๋ฅ๋ฐ์:" . $e->getMessage() . "\n" . var_export($entity, true)); + throw new RuntimeException(static::class . '->' . __FUNCTION__ . "์์ ์ค๋ฅ๋ฐ์:" . $e->getMessage()); } } diff --git a/app/Services/InquiryService.php b/app/Services/InquiryService.php new file mode 100644 index 0000000..77d590f --- /dev/null +++ b/app/Services/InquiryService.php @@ -0,0 +1,51 @@ +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; + } + //๊ธฐ๋ณธ ๊ธฐ๋ฅ๋ถ๋ถ + protected function getEntity_process(mixed $entity): InquiryEntity + { + return $entity; + } + //List ๊ฒ์์ฉ + //FormFilter ์กฐ๊ฑด์ ์ฒ๋ฆฌ + //๊ฒ์์ด์กฐ๊ฑด์ ์ฒ๋ฆฌ + + //์ถ๊ฐ๊ธฐ๋ฅ๋ถ๋ถ + protected function create_process(array $formDatas): CommonEntity + { + if (!isset($formDatas['status']) || $formDatas['status'] === '' || $formDatas['status'] === null) { + $formDatas['status'] = STATUS['AVAILABLE']; // 'available' + } + return parent::create_process($formDatas); + } +} diff --git a/app/Views/admin/create_form.php b/app/Views/admin/create_form.php index 55537cd..75afda8 100644 --- a/app/Views/admin/create_form.php +++ b/app/Views/admin/create_form.php @@ -1,6 +1,5 @@ = $this->extend($viewDatas['layout']['layout']) ?> = $this->section('content') ?> -= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>