daemon-idc/app/Controllers/AbstractCRUDController.php
2026-02-12 17:23:11 +09:00

296 lines
10 KiB
PHP

<?php
namespace App\Controllers;
use RuntimeException;
use App\Entities\CommonEntity;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface;
use App\Exceptions\FormValidationException;
/**
* AbstractCRUDController
* 단일 레코드 생성(C), 조회(R), 수정(U), 삭제(D) 로직을 담당합니다. (SRP: Single Record Management)
*/
abstract class AbstractCRUDController extends AbstractWebController
{
// --- 생성 (Create) ---
protected function create_form_process(array $formDatas = []): array
{
//초기 기본 Default값 지정
$formDatas = $this->request->getVar();
return $formDatas;
}
protected function create_form_result_process(string $action): string|RedirectResponse
{
return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate'));
}
public function create_form(): string|RedirectResponse
{
try {
$action = __FUNCTION__;
$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());
}
}
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());
}
protected function create_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()
);
}
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());
}
}
// --- 수정 (Modify) ---
protected function modify_form_process($uid): CommonEntity
{
return $this->service->getEntity($uid);
}
protected function modify_form_result_process(string $action): string|RedirectResponse
{
return $this->action_render_process($action, $this->getViewDatas(), $this->request->getVar('ActionTemplate'));
}
final public function modify_form($uid): string|RedirectResponse
{
try {
if (!$uid) {
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다.");
}
$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());
}
}
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());
}
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',
"{$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
{
return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다.", $redirect_url);
}
final public function delete($uid): RedirectResponse
{
try {
if (!$uid) {
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()}에 번호가 정의 되지 않았습니다.");
}
$entity = $this->service->getEntity($uid);
$entity = $this->delete_process($uid);
return $this->delete_result_process($entity);
} catch (\Throwable $e) {
return $this->action_redirect_process('error', static::class . '->' . __FUNCTION__ . "에서 {$this->getTitle()} 삭제 오류:" . $e->getMessage());
}
}
// --- 상세보기 (View) ---
protected function view_process($uid): CommonEntity
{
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()}에 번호가 정의 되지 않았습니다.");
}
$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());
}
}
}