345 lines
11 KiB
PHP
345 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Forms;
|
|
|
|
use App\Exceptions\FormValidationException;
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* CommonForm
|
|
* - 모든 Form의 공통 베이스
|
|
* - 핵심 개선점:
|
|
* 1) FK/숫자 필드 미입력('')을 NULL로 정규화 ('' -> null)
|
|
* 2) 전역 null -> '' 변환 제거 (FK/숫자/날짜 타입 깨짐 방지)
|
|
* 3) validate()에서 dynamicRules 누적 버그 수정 (마지막 규칙만 남는 문제 해결)
|
|
* 4) "필드 존재 보장"으로 임의 '' 삽입 제거 (미입력 필드가 FK/숫자 규칙을 깨는 문제 방지)
|
|
* 5) role.* 같은 배열 원소 규칙을 위해 부모 배열 보정 로직 유지/강화
|
|
*
|
|
* ✅ 추가:
|
|
* - validate() 실패 시 RuntimeException(implode) 대신
|
|
* FormValidationException(errors 배열)을 throw하여
|
|
* Controller에서 AJAX(422 JSON errors) 응답이 가능하게 함
|
|
*/
|
|
abstract class CommonForm
|
|
{
|
|
private array $_attributes = [];
|
|
private array $_formFields = [];
|
|
private array $_formRules = [];
|
|
private array $_formFilters = [];
|
|
private array $_indexFilters = [];
|
|
private array $_batchjobFilters = [];
|
|
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');
|
|
}
|
|
|
|
public function action_init_process(string $action, array &$formDatas = []): void
|
|
{
|
|
$actionButtons = ['view' => ICONS['SEARCH'], 'delete' => ICONS['DELETE']];
|
|
$batchjobButtons = [];
|
|
$this->setActionButtons($actionButtons);
|
|
$this->setBatchjobButtons($batchjobButtons);
|
|
}
|
|
|
|
final public function setAttributes(array $attributes): void
|
|
{
|
|
$this->_attributes = $attributes;
|
|
}
|
|
|
|
final public function getAttribute(string $key): string
|
|
{
|
|
if (!array_key_exists($key, $this->_attributes)) {
|
|
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$key}에 해당하는 속성이 정의되지 않았습니다.");
|
|
}
|
|
return $this->_attributes[$key];
|
|
}
|
|
|
|
final public function setFormFields(array $fields): void
|
|
{
|
|
foreach ($fields as $field) {
|
|
$this->_formFields[$field] = $this->getFormFieldLabel($field);
|
|
}
|
|
}
|
|
|
|
// $fields 매치된것만 반환, []->전체
|
|
final public function getFormFields(array $fields = []): array
|
|
{
|
|
if (empty($fields)) {
|
|
return $this->_formFields;
|
|
}
|
|
return array_intersect_key($this->_formFields, array_flip($fields));
|
|
}
|
|
|
|
public function setFormRules(string $action, array $fields, $formRules = []): void
|
|
{
|
|
foreach ($fields as $field) {
|
|
$formRules = $this->getFormRule($action, $field, $formRules);
|
|
}
|
|
$this->_formRules = $formRules;
|
|
}
|
|
|
|
final public function getFormRules(array $fields = []): array
|
|
{
|
|
if (empty($fields)) {
|
|
return $this->_formRules;
|
|
}
|
|
return array_intersect_key($this->_formRules, array_flip($fields));
|
|
}
|
|
|
|
final public function setFormOptions(string $action, array $fields, array $formDatas = [], $formOptions = []): void
|
|
{
|
|
foreach ($fields as $field) {
|
|
$formOptions[$field] = $formOptions[$field] ?? $this->getFormOption($action, $field, $formDatas);
|
|
}
|
|
$this->_formOptions = $formOptions;
|
|
}
|
|
|
|
// $fields 매치된것만 반환, []->전체
|
|
final public function getFormOptions(array $fields = []): array
|
|
{
|
|
if (empty($fields)) {
|
|
return $this->_formOptions;
|
|
}
|
|
return array_intersect_key($this->_formOptions, array_flip($fields));
|
|
}
|
|
|
|
final public function setFormFilters(array $fields): void
|
|
{
|
|
$this->_formFilters = $fields;
|
|
}
|
|
|
|
final public function getFormFilters(): array
|
|
{
|
|
return $this->_formFilters;
|
|
}
|
|
|
|
final public function setIndexFilters(array $fields): void
|
|
{
|
|
$this->_indexFilters = $fields;
|
|
}
|
|
|
|
final public function getIndexFilters(): array
|
|
{
|
|
return $this->_indexFilters;
|
|
}
|
|
|
|
final public function setBatchjobFilters(array $fields): void
|
|
{
|
|
$this->_batchjobFilters = $fields;
|
|
}
|
|
|
|
final public function getBatchjobFilters(): array
|
|
{
|
|
return $this->_batchjobFilters;
|
|
}
|
|
|
|
final public function setActionButtons(array $buttons): array
|
|
{
|
|
return $this->_actionButtons = $buttons;
|
|
}
|
|
|
|
final public function getActionButtons(): array
|
|
{
|
|
return $this->_actionButtons;
|
|
}
|
|
|
|
final public function setBatchjobButtons(array $buttons): array
|
|
{
|
|
return $this->_batchjobButtons = $buttons;
|
|
}
|
|
|
|
final public function getBatchjobButtons(): array
|
|
{
|
|
return $this->_batchjobButtons;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Validation
|
|
* --------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* 데이터를 검증하고 유효하지 않을 경우 예외를 발생시킵니다.
|
|
* ✅ 변경점:
|
|
* - 실패 시 FormValidationException(errors 배열)을 throw
|
|
* (AJAX에서 422로 내려보내기 위함)
|
|
*/
|
|
final public function validate(array &$formDatas): void
|
|
{
|
|
try {
|
|
$formFields = $this->getFormFields();
|
|
$formRules = $this->getFormRules();
|
|
|
|
if (empty($formRules)) {
|
|
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 지정된 Form RULE이 없습니다.");
|
|
}
|
|
|
|
$dynamicRules = [];
|
|
foreach ($formRules as $field => $rule) {
|
|
[$fieldName, $ruleStr] = $this->getValidationRule((string) $field, (string) $rule);
|
|
|
|
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);
|
|
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) {
|
|
throw new RuntimeException('유효성 검사 중 시스템 오류 발생: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Overridable hooks
|
|
* --------------------------------------------------------------------- */
|
|
|
|
protected function getValidationRule(string $field, string $rule): array
|
|
{
|
|
return [$field, $rule];
|
|
}
|
|
|
|
public function getFormFieldLabel(string $field, ?string $label = null): string
|
|
{
|
|
switch ($field) {
|
|
default:
|
|
$label = $label ?? lang("{$this->getAttribute('class_path')}.label.{$field}");
|
|
break;
|
|
}
|
|
return $label;
|
|
}
|
|
|
|
public function getFormRule(string $action, string $field, array $formRules): array
|
|
{
|
|
switch ($field) {
|
|
case $this->getAttribute('pk_field'):
|
|
if (!$this->getAttribute('useAutoIncrement')) {
|
|
$formRules[$field] = sprintf(
|
|
"required|regex_match[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/]%s",
|
|
in_array($action, ["create"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : ""
|
|
);
|
|
} else {
|
|
$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;
|
|
}
|
|
|
|
return $formRules;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Options
|
|
* --------------------------------------------------------------------- */
|
|
|
|
protected function getFormOption_process($service, string $action, string $field, array $formDatas = []): array
|
|
{
|
|
$entities = [];
|
|
|
|
switch ($field) {
|
|
default:
|
|
if (in_array($action, ['create_form', 'modify_form', 'alternative_create_form'])) {
|
|
if (array_key_exists($field, $formDatas)) {
|
|
$where = sprintf("status = '%s' OR %s='%s'", STATUS['AVAILABLE'], $this->getAttribute('pk_field'), $formDatas[$field]);
|
|
} else {
|
|
$where = sprintf("status = '%s'", STATUS['AVAILABLE']);
|
|
}
|
|
$entities = $service->getEntities([$where => null]);
|
|
} else {
|
|
$entities = $service->getEntities();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $entities;
|
|
}
|
|
|
|
public function getFormOption(string $action, string $field, array $formDatas = [], array $options = ['options' => [], 'atttributes' => []]): array
|
|
{
|
|
$tempOptions = ['' => lang("{$this->getAttribute('class_path')}.label.{$field}") . " 선택"];
|
|
|
|
switch ($field) {
|
|
case 'user_uid':
|
|
foreach ($this->getFormOption_process(service('userservice'), $action, $field, $formDatas) as $entity) {
|
|
$tempOptions[$entity->getPK()] = $entity->getTitle();
|
|
}
|
|
$options['options'] = $tempOptions;
|
|
break;
|
|
|
|
case 'clientinfo_uid':
|
|
foreach ($this->getFormOption_process(service('customer_clientservice'), $action, $field, $formDatas) as $entity) {
|
|
$tempOptions[$entity->getPK()] = $entity->getCustomTitle();
|
|
}
|
|
$options['options'] = $tempOptions;
|
|
break;
|
|
|
|
default:
|
|
$optionDatas = lang($this->getAttribute('class_path') . "." . strtoupper($field));
|
|
if (!is_array($optionDatas)) {
|
|
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:{$field}가 배열값이 아닙니다.");
|
|
}
|
|
foreach ($optionDatas as $key => $label) {
|
|
$tempOptions[$key] = $label;
|
|
}
|
|
$options['options'] = $tempOptions;
|
|
break;
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
}
|