368 lines
14 KiB
PHP
368 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Forms;
|
|
|
|
use RuntimeException;
|
|
|
|
abstract class CommonForm
|
|
{
|
|
private $_validation = null;
|
|
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 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;
|
|
}
|
|
// _formFields와 키를 비교하여 교집합을 반환합니다. $fields에 지정된 필드 정의만 추출됩니다.
|
|
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용
|
|
/**
|
|
* 데이터를 검증하고 유효하지 않을 경우 예외를 발생시킵니다.
|
|
* 2025 CI4 표준: 규칙 배열 내에 label을 포함하여 한글 메시지 출력을 보장합니다.
|
|
*
|
|
* @param array $formDatas 검증할 데이터
|
|
* @throws RuntimeException
|
|
*/
|
|
final public function validate(array &$formDatas): void
|
|
{
|
|
if ($this->_validation === null) {
|
|
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: Validation 서비스가 초기화되지 않았습니다.");
|
|
}
|
|
|
|
try {
|
|
$dynamicRules = [];
|
|
|
|
// 0. Ensure all scalar/null inputs are strings to prevent trim() error on PHP 8.1+
|
|
$castToString = function (&$data, $path = '') use (&$castToString) {
|
|
foreach ($data as $key => &$value) {
|
|
$currentField = $path ? "{$path}.{$key}" : $key;
|
|
try {
|
|
if (is_array($value)) {
|
|
$castToString($value, $currentField);
|
|
} elseif ($value === null || (is_scalar($value) && !is_string($value))) {
|
|
$data[$key] = (string) ($value ?? '');
|
|
}
|
|
} catch (\Throwable $e) {
|
|
throw new RuntimeException("데이터 정제 중 오류 발생 (필드: {$currentField}): " . $e->getMessage());
|
|
}
|
|
}
|
|
};
|
|
$castToString($formDatas);
|
|
|
|
// 1. 현재 서비스의 필드 라벨 정보 로드 (언어 파일 기반)
|
|
$formFields = $this->getFormFields();
|
|
$formRules = $this->getFormRules();
|
|
|
|
if (empty($formRules)) {
|
|
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 지정된 Form RULE이 없습니다.");
|
|
}
|
|
|
|
foreach ($formRules as $field => $rule) {
|
|
try {
|
|
// 2. 필드명과 규칙 추출
|
|
list($field, $rule) = $this->getValidationRule($field, $rule);
|
|
|
|
// 3. 라벨 결정 로직 (한글 라벨 매핑)
|
|
if (isset($formFields[$field])) {
|
|
$label = $formFields[$field];
|
|
} elseif (str_contains($field, '.*')) {
|
|
// 배열 검증(role.* 등)의 경우 부모 필드의 라벨을 활용
|
|
$parentField = str_replace('.*', '', $field);
|
|
$label = ($formFields[$parentField] ?? $field) . " 항목";
|
|
} else {
|
|
$label = $field; // 언어 파일에 정의가 없는 경우 필드명 유지
|
|
}
|
|
|
|
// 4. [핵심 해결책] 규칙 배열 자체에 label을 포함시킴
|
|
// 이렇게 하면 CI4 엔진이 {field} 자리에 이 label 값을 최우선으로 사용합니다.
|
|
$dynamicRules[$field] = [
|
|
'label' => $label,
|
|
'rules' => $rule
|
|
];
|
|
|
|
// 4.5. Ensure the field exists in formDatas to prevent trim(null) in the engine
|
|
if (!array_key_exists($field, $formDatas) && !str_contains($field, '.*')) {
|
|
$formDatas[$field] = '';
|
|
}
|
|
} catch (\Throwable $e) {
|
|
throw new RuntimeException("유효성 검사 규칙 준비 중 오류 발생 (필드: {$field}): " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// 5. 검증 규칙 설정 (인자를 하나만 전달하여 설정 충돌 방지)
|
|
$this->_validation->setRules($dynamicRules);
|
|
|
|
// 6. 검증 실행
|
|
try {
|
|
if (!$this->_validation->run($formDatas)) {
|
|
// 한글 라벨이 적용된 에러 메시지들을 배열로 가져와 한 줄씩 합침
|
|
$errors = $this->_validation->getErrors();
|
|
throw new RuntimeException(implode("\n", $errors));
|
|
}
|
|
} catch (\TypeError $e) {
|
|
// TypeError(예: trim(null) 등) 발생 시 범인(field) 찾기 시도
|
|
$culpritField = "알 수 없음";
|
|
$culpritValue = "null";
|
|
|
|
foreach ($dynamicRules as $f => $r) {
|
|
if (str_contains($r['rules'], 'trim')) {
|
|
// 중첩 필드(.*)와 일반 필드 구분하여 null 체크
|
|
if (str_contains($f, '.*')) {
|
|
$parentKey = str_replace('.*', '', $f);
|
|
if (isset($formDatas[$parentKey]) && is_array($formDatas[$parentKey])) {
|
|
foreach ($formDatas[$parentKey] as $k => $v) {
|
|
if ($v === null) {
|
|
$culpritField = "{$parentKey}.{$k} ({$r['label']})";
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (!isset($formDatas[$f]) || $formDatas[$f] === null) {
|
|
$culpritField = "{$f} ({$r['label']})";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$errorMsg = $e->getMessage();
|
|
// "trim(): Argument #1 ($string) must be of type string, null given" 문구에서 Argument #1을 필드명으로 교체
|
|
$errorMsg = str_replace("Argument #1 (\$string)", "데이터(필드: {$culpritField}, 값: {$culpritValue})", $errorMsg);
|
|
throw new RuntimeException("검증 도중 타입 오류 발생: " . $errorMsg);
|
|
}
|
|
|
|
// 검증 성공 시 추가 로직 없이 종료
|
|
|
|
} catch (\Throwable $e) {
|
|
// 이미 RuntimeException으로 포장된 경우 그대로 던짐
|
|
if ($e instanceof RuntimeException) {
|
|
throw $e;
|
|
}
|
|
// 오류 발생 시 디버깅을 위해 로그 기록
|
|
log_message('debug', '--- Validation Error Detail ---');
|
|
log_message('debug', 'Rules: ' . var_export($this->getFormRules(), true));
|
|
log_message('debug', 'Data: ' . var_export($formDatas, true));
|
|
log_message('debug', 'Message: ' . $e->getMessage());
|
|
|
|
throw new RuntimeException("유효성 검사 중 시스템 오류 발생: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
|
|
//필수함수
|
|
//사용자정의 함수
|
|
protected function getValidationRule(string $field, string $rule): array
|
|
{
|
|
return array($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":
|
|
// a-zA-Z → 영문 대소문자,0-9 → 숫자,가-힣 → 한글 완성형,\- → 하이픈
|
|
$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 "clientinfo_uid":
|
|
case "serviceinfo_uid":
|
|
case "serverinfo_uid":
|
|
$formRules[$field] = "permit_empty|numeric";
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
}
|