You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
3.5 KiB

1 week ago
<?php
namespace App\Support;
/**
* 课程报名表单「动态字段」答案的存储约定course_signups.form_answers
*
* 设计目标:选项的展示文案 / value 可在字典或业务表中变更,历史报名仍通过稳定 key 解析。
*
* form_answers 结构(字段 key => 答案对象):
* - 文本/日期等:{ "t": "text"|"date"|"datetime"|"file", "v": "..." }
* - 单选/下拉:{ "t": "option", "k": "<stable_key>" }
* - 多选: { "t": "options", "ks": ["<key1>", "<key2>"] }
*
* signup_form_schema 中选项字段需声明 options_source
* - static: options[] 每项 { key, label, value? }
* - dict: { type:"dict", dict_code:"signup_gender" },运行时 key = (string) dict_items.id
* - table: { type:"table", table:"signup_channels", key_field:"code", ... }(预留)
*/
class SignupFormAnswers
{
private const PLAIN_TYPES = ['text', 'textarea', 'date', 'datetime', 'file'];
private const OPTION_TYPES = ['radio', 'select'];
private const MULTI_OPTION_TYPES = ['checkbox'];
/**
* 规范化并校验 form_answers创建报名时调用
*
* @param array<string, mixed> $answers
* @param array<int, array<string, mixed>>|null $schema
* @return array<string, mixed>
*/
public static function normalize(array $answers, ?array $schema): array
{
if ($schema === null || $schema === []) {
return $answers;
}
$fieldMap = [];
foreach ($schema as $field) {
if (! is_array($field)) {
continue;
}
$key = $field['key'] ?? null;
if (is_string($key) && $key !== '') {
$fieldMap[$key] = $field;
}
}
$out = [];
foreach ($answers as $fieldKey => $raw) {
if (! is_string($fieldKey) || $fieldKey === '') {
continue;
}
if (! isset($fieldMap[$fieldKey])) {
continue;
}
$normalized = self::normalizeOne($fieldMap[$fieldKey], $raw);
if ($normalized !== null) {
$out[$fieldKey] = $normalized;
}
}
return $out;
}
/**
* @param array<string, mixed> $fieldDef
* @param mixed $raw
* @return array<string, mixed>|null
*/
protected static function normalizeOne(array $fieldDef, mixed $raw): ?array
{
if (! is_array($raw)) {
return null;
}
$type = (string) ($fieldDef['type'] ?? 'text');
if (in_array($type, self::OPTION_TYPES, true)) {
$k = $raw['k'] ?? $raw['key'] ?? null;
if ($k === null || $k === '') {
return null;
}
return ['t' => 'option', 'k' => (string) $k];
}
if (in_array($type, self::MULTI_OPTION_TYPES, true)) {
$ks = $raw['ks'] ?? $raw['keys'] ?? null;
if (! is_array($ks)) {
return null;
}
$keys = array_values(array_filter(array_map('strval', $ks), fn ($x) => $x !== ''));
return ['t' => 'options', 'ks' => $keys];
}
if (in_array($type, self::PLAIN_TYPES, true)) {
$t = $type === 'textarea' ? 'text' : $type;
if (! array_key_exists('v', $raw) && ! array_key_exists('value', $raw)) {
return null;
}
$v = $raw['v'] ?? $raw['value'];
return ['t' => $t, 'v' => is_scalar($v) ? (string) $v : json_encode($v)];
}
return null;
}
}