Files
fdesk/app/common/traits/FormatKvTrait.php
T

155 lines
4.5 KiB
PHP
Raw Normal View History

2025-07-29 09:00:04 +08:00
<?php
namespace app\common\traits;
use support\Request;
trait FormatKvTrait
{
/**
* 主入口:一行把前端扁平化参数变成完整的查询配置
*
* @param Request $request
* @param string $modelClass Eloquent 模型类名
* @return array{
* model: string,
* where: array,
* order: array<string,string>,
* fields: string[],
* include: string[]
* }
*/
protected function buildQueryConfig(Request $request, string $modelClass): array
{
$all = $request->all();
return [
'model' => $modelClass,
'where' => $this->parseFilters($all),
'order' => $this->parseSort($all['sort'] ?? ''),
'fields' => $this->parseCsv($all['fields'] ?? ''),
'include' => $this->parseCsv($all['include'] ?? ''),
];
}
/**
* 把除 sort/fields/include/page/limit 以外的所有参数
* 智能转换为 where 条件
*
* 支持:
* - 等值:foo=bar
* - NOT:foo=!bar 或 foo=!bar1,bar2
* - 范围:foo=2025-07-01,2025-07-15 → between
* - 列表:foo=a,b,c → in
* - NULL:foo=null
* - NOT NULL:foo=!null
* - 通配符:foo=xia* → like 'xia%'
*
* 关联字段直接写 relation.field=xxx
*/
protected function parseFilters(array $params): array
{
$where = [];
$reserved = ['sort','fields','include','page','limit'];
foreach ($params as $key => $val) {
if (in_array($key, $reserved, true)) {
continue;
}
// NULL / NOT NULL
if ($val === 'null') {
$where[$key] = ['is null'];
continue;
}
if ($val === '!null') {
$where[$key] = ['is not null'];
continue;
}
// NOT equal or NOT IN
if (str_starts_with($val, '!')) {
$v = substr($val, 1);
if (str_contains($v, ',')) {
$list = array_map('trim', explode(',', $v));
$where[$key] = ['not in', $list];
} else {
$where[$key] = ['<>', $this->cast($v)];
}
continue;
}
// BETWEEN or IN
if (str_contains($val, ',')) {
$parts = array_map('trim', explode(',', $val));
if (count($parts) === 2 && $this->isRangeValue($parts[0], $parts[1])) {
$where[$key] = ['between', $parts];
} else {
$where[$key] = ['in', $this->castArray($parts)];
}
continue;
}
// LIKE
if (str_contains($val, '*') || str_contains($val, '%')) {
$pattern = str_replace('*', '%', $val);
$where[$key] = ['like', $pattern];
continue;
}
// 默认 EQ
$where[$key] = $this->cast($val);
}
return $where;
}
/**
* 解析 sort=id,-created_at → ['id'=>'asc','created_at'=>'desc']
*/
protected function parseSort(string $sort): array
{
$order = [];
foreach (explode(',', $sort) as $piece) {
$f = trim($piece);
if ($f === '') {
continue;
}
if ($f[0] === '-') {
$order[substr($f, 1)] = 'desc';
} else {
$order[$f] = 'asc';
}
}
return $order;
}
/**
* 逗号分割成数组
*/
protected function parseCsv(string $csv): array
{
if ($csv === '') {
return [];
}
return array_filter(array_map('trim', explode(',', $csv)));
}
/** 简单类型转换:全数字→int/float,其余保留原字符串 */
protected function cast(string $v): mixed
{
if (is_numeric($v)) {
return strpos($v, '.') !== false ? (float)$v : (int)$v;
}
return $v;
}
/** 批量类型转换 */
protected function castArray(array $arr): array
{
return array_map(fn($v) => $this->cast($v), $arr);
}
/** 判断两端是否可做 BETWEEN(数字或 YYYY-MM-DD 日期) */
protected function isRangeValue(string $a, string $b): bool
{
return (is_numeric($a) && is_numeric($b))
|| (preg_match('/^\d{4}-\d{2}-\d{2}$/', $a)
&& preg_match('/^\d{4}-\d{2}-\d{2}$/', $b));
}
}