Files
hyperf_data/extend/Aether/PHP/Hyperf/AetherModel.php
2025-09-28 15:57:07 +08:00

682 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace Aether;
use Aether\Contract\TreeableInterface;
use Closure;
use DateTime;
use Exception;
use Hyperf\Context\ApplicationContext;
use Hyperf\Contract\LengthAwarePaginatorInterface;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Collection;
use Hyperf\Database\Model\ModelNotFoundException;
use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Model\Model as HyperfModel;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\ModelCache\Cacheable;
use Hyperf\ModelCache\CacheableInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
abstract class AetherModel extends HyperfModel implements CacheableInterface
{
use Cacheable;
// use AetherSoftDelete; 移除,由子类决定是否使用
/**
* 批量赋值白名单.
*/
protected array $fillable = [];
/**
* 时间戳格式.
*/
protected ?string $dateFormat = 'Y-m-d H:i:s';
/**
* 搜索器规则配置.
*/
protected array $search = [];
/**
* 获取器规则配置.
*/
protected array $append = [];
/**
* 隐藏字段.
*/
protected array $hidden = [
'created_at',
'updated_at',
'deleted_at',
// 'deleted_at' // 移除,由子类决定是否使用
];
/**
* 排序配置:
* - false: 禁用排序
* - 字符串: 排序字段(默认升序)
* - 数组: ['field' => '字段名', 'direction' => 'asc/desc'].
*/
protected array|bool|string $sortable = false; // 'sort'; // 默认按sort字段升序
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->bootBaseModel();
}
/**
* 获取排序配置.
* @return null|array [field, direction] 或 null禁用排序
*/
public function getSortConfig(): ?array
{
if ($this->sortable === false) {
return null;
}
// 处理字符串配置(如 'sort' 或 'create_time'
if (is_string($this->sortable)) {
return [
'field' => $this->sortable,
'direction' => 'asc',
];
}
return null;
}
/**
* 列表查询.
*/
// public function list(array $params = []): array
// {
// $query = $this->newQuery();
//
// // 通过模型配置自动应用所有搜索条件
// $this->applySearch($query, $params);
//
// // 动态应用排序
// $sortConfig = $this->getSortConfig();
// if ($sortConfig) {
// $query->orderBy($sortConfig['field'], $sortConfig['direction']);
// }
//
// $withDeleted = filter_var($params['with_deleted'] ?? false, FILTER_VALIDATE_BOOLEAN);
// if ($withDeleted) {
// $query->withTrashed();
// }
//
// // 存在分页参数page或size则进行分页查询
// if (isset($params['page']) || isset($params['size'])) {
// $page = (int) ($params['page'] ?? 1);
// $size = (int) ($params['size'] ?? 10);
// $page = max(1, $page);
// $size = max(1, min(100, $size));
// $result = $query->paginate($size, ['*'], 'page', $page);
// return [
// 'total' => $result->total(),
// 'list' => $result->items(),
// ];
// }
//
// // 无分页参数时返回完整数据集合
// $items = $query->get()->toArray();
//
// // 若模型支持树形结构则构建树形,否则返回普通数组
// if ($this instanceof TreeableInterface) {
// return $this::buildTree($items, (int) ($params['parent_id'] ?? 0));
// }
//
// return $items;
// }
/**
* 列表查询.
*/
// public function list(array $params = []): array
// {
// $query = $this->buildQueryFromParams($params);
// return $this->listResult($query, $params);
// }
public function list(array $params = []): array
{
$query = static::query();
// 通过模型配置自动应用所有搜索条件
$this->applySearch($query, $params);
// 动态应用排序
if ($this->sortable) {
$sortConfig = $this->getSortConfig();
$query->orderBy($sortConfig['field'], $sortConfig['direction']);
}
$withDeleted = filter_var($params['with_deleted'] ?? false, FILTER_VALIDATE_BOOLEAN);
if ($withDeleted) {
$query->withTrashed();
}
// 存在分页参数page或size则进行分页查询
if (isset($params['page']) || isset($params['size'])) {
$page = (int) ($params['page'] ?? 1);
$size = (int) ($params['size'] ?? 10);
// 确保分页参数合法性
$page = max(1, $page);
$size = max(1, min(100, $size)); // 限制最大页大小为100
$result = $query->paginate($size, ['*'], 'page', $page);
return [
'list' => $result->items(),
'total' => $result->total(),
];
}
// 无分页参数时返回完整数据集合
$items = $query->get()->toArray();
// 若模型支持树形结构则构建树形,否则返回普通数组
if ($this instanceof TreeableInterface) {
return $this::buildTree($items, (int) ($params['parent_id'] ?? 0));
}
return $items;
}
/**
* 根据模型的$search配置自动应用搜索条件到查询构建器.
*/
// public function applySearch(Builder $query, array $params): void
// {
// foreach ($this->search as $field => $rule) {
// // 跳过未传递的参数
// if (!isset($params[$field])) {
// continue;
// }
//
// $value = $params[$field];
// $this->applySearchRule($query, $field, $value, $rule);
// }
// }
//
// /**
// * 应用单个搜索规则
// */
// protected function applySearchRule(Builder $query, string $field, $value, $rule): void
// {
// // 处理规则格式(支持字符串简写或数组配置)
// $config = is_array($rule) ? $rule : ['type' => $rule];
// $type = $config['type'];
//
// switch ($type) {
// case '=': // 精确匹配
// $query->where($field, $value);
// break;
// case 'like': // 模糊匹配
// $query->where($field, 'like', "%{$value}%");
// break;
// case 'between': // 范围查询(支持数组或两个参数)
// $values = is_array($value) ? $value : [$value, $params[$field . '_end'] ?? $value];
// $query->whereBetween($field, $values);
// break;
// case 'callback': // 自定义回调
// if (isset($config['handler']) && is_callable($config['handler'])) {
// call_user_func($config['handler'], $query, $value);
// }
// break;
// // 可扩展其他类型in、>、< 等
// }
// }
/**
* 快捷创建.
*/
public static function createOne(array $data): AetherModel|Builder|HyperfModel
{
return static::query()->create($data);
}
/**
* 快捷更新.
*/
public static function updateById(int $id, array $data): int
{
return static::query()->where('id', $id)->update($data);
}
/**
* 快捷删除指定ID的记录.
*
* @param int $id 要删除的记录ID
* @return bool 成功删除返回true
* @throws ModelNotFoundException 当记录不存在时抛出
* @throws Exception 当删除操作发生其他错误时抛出
*/
public static function deleteById(int $id): bool
{
if (! static::query()->where('id', $id)->exists()) {
throw new ModelNotFoundException(sprintf(
'找不到 ID 为 %d 的记录',
$id,
));
}
return static::query()->where('id', $id)->delete() > 0;
}
/**
* 快捷查找.
* @return Builder|Builder[]|Collection|HyperfModel
* @throws Exception 当删除操作发生其他错误时抛出
* @throws ModelNotFoundException
*/
public static function findById(int $id): array|Builder|Collection|HyperfModel
{
$record = static::query()->find($id);
if (is_null($record)) {
throw new ModelNotFoundException(sprintf('找不到 ID 为 %d 的记录', $id));
}
return $record;
}
/**
* 快捷查找或失败.
* @param int $id 要查找的记录ID
* @return AetherModel 根据ID查找记录不存在则抛出异常
* 根据ID查找记录不存在则抛出异常
* @throws Exception 当删除操作发生其他错误时抛出
* @throws ModelNotFoundException 当记录不存在时抛出
*/
public static function findOrFailById(int $id): static
{
$record = static::query()->find($id);
if (is_null($record)) {
throw new ModelNotFoundException(sprintf(
'找不到 ID 为 %d 的记录',
$id
));
}
return $record; // static::query()->findOrFail($id);
}
/**
* 获取列表.
*/
public static function getList(
array $conditions = [],
array $columns = ['*'],
array $orders = []
): Collection {
$query = static::buildQuery($conditions, $orders);
return $query->get($columns);
}
/**
* 分页查询列表.
*/
public static function getPageList(
array $conditions = [],
int $page = 1,
int $pageSize = 10,
array $columns = ['*'],
array $orderBy = []
): LengthAwarePaginatorInterface {
// 直接通过静态方法链构建查询
$query = static::query();
// 应用条件
foreach ($conditions as $field => $value) {
$query->where($field, $value);
}
// 应用排序
foreach ($orderBy as $field => $direction) {
$query->orderBy($field, $direction);
}
// 执行分页
return $query->paginate($pageSize, $columns, 'page', $page);
}
/**
* 获取器处理.
*/
public function getAttribute(string $key): mixed
{
$value = parent::getAttribute($key);
// 检查是否有自定义获取器
$getterMethod = 'get' . ucfirst($key) . 'Attr';
if (method_exists($this, $getterMethod)) {
return $this->{$getterMethod}($value);
}
// 应用获取器规则
if (isset($this->append[$key])) {
return $this->applyGetterRule($value, $this->append[$key]);
}
return $value;
}
/**
* 批量更新.
*/
public static function batchUpdate(array $conditions, array $data): int
{
return static::query()->where($conditions)->update($data);
}
/**
* 事务处理.
* @throws Throwable
*/
public static function transaction(Closure $closure): mixed
{
return Db::transaction($closure);
}
/**
* 应用排序.
*/
protected function applySorting(Builder $query, array $params = []): void
{
// 优先使用传入的排序参数
if (isset($params['sort_field'], $params['sort_direction'])) {
$query->orderBy($params['sort_field'], $params['sort_direction']);
return;
}
// 使用模型配置的排序
$sortConfig = $this->getSortConfig();
if (! empty($sortConfig)) {
$query->orderBy($sortConfig['field'], $sortConfig['direction']);
}
}
/**
* 根据分页参数获取结果.
*/
protected function listResult(Builder $query, array $params = []): array
{
// 分页处理
if (isset($params['page'], $params['size'])) {
$page = max(1, (int) $params['page']);
$size = max(1, min(100, (int) $params['size']));
$result = $query->paginate($size, ['*'], 'page', $page);
return [
'list' => $result->items(),
'total' => $result->total(),
];
}
// 获取所有数据
$result = $query->get()->toArray();
// 如果实现了树结构接口,构建树
if ($this instanceof TreeableInterface) {
return $this->buildTree($result, $params['parent_id'] ?? 0);
}
return $result;
}
/**
* 初始化模型.
*/
protected function bootBaseModel(): void
{
// 自动注册搜索器和获取器
$this->registerSearchers();
$this->registerGetters();
}
/**
* 注册搜索器.
*/
protected function registerSearchers(): void
{
// 为模型查询添加全局作用域,自动应用搜索规则
static::addGlobalScope('auto_search', function (Builder $query) {
$searchConditions = $this->getSearchConditions();
if (empty($searchConditions)) {
return;
}
// 应用搜索条件到查询
$this->applySearch($query, $searchConditions);
});
}
/**
* 获取搜索条件.
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function getSearchConditions(): array
{
$request = ApplicationContext::getContainer()->get(RequestInterface::class);
$allParams = $request->all();
if (is_array($allParams) && count($allParams) === 1 && is_array($allParams[0])) {
$allParams = $allParams[0];
}
$allowedFields = array_keys($this->search);
return array_intersect_key($allParams, array_flip($allowedFields));
}
protected function registerGetters(): void
{
foreach ($this->append as $field => $rule) {
// 为字段注册获取器回调
$this->registerGetter($field, $rule);
}
}
/**
* 为单个字段注册获取器.
* @param string $field 字段名
* @param array|Closure|string $rule 规则(类型/带参数的类型/闭包)
*/
protected function registerGetter(string $field, array|Closure|string $rule): void
{
// 生成获取器方法名(遵循 Hyperf 模型获取器规范get{Field}Attr
$getterMethod = 'get' . ucfirst($field) . 'Attr';
// 若已存在自定义获取器方法,则不覆盖
if (method_exists($this, $getterMethod)) {
return;
}
// 动态定义获取器方法
$this->{$getterMethod} = function ($value) use ($rule) {
// 闭包规则直接执行
if ($rule instanceof Closure) {
return $rule($value, $this); // 传入当前模型实例方便关联字段处理
}
// 解析规则类型和参数
if (is_array($rule)) {
$type = $rule[0];
$params = $rule[1] ?? [];
} else {
$type = $rule;
$params = [];
}
// 应用规则(复用/扩展 applyGetterRule 方法)
return $this->applyGetterRule($value, $type, $params);
};
}
/**
* 应用获取器规则(支持参数).
* @param mixed $value 字段原始值
* @param string $type 规则类型
* @param array $params 规则参数
*/
protected function applyGetterRule(mixed $value, string $type, array $params = []): mixed
{
return match ($type) {
// 基础规则
'date' => $value instanceof DateTime ? $value->format('Y-m-d') : $value,
'datetime' => $value instanceof DateTime ? $value->format('Y-m-d H:i:s') : $value,
'timestamp' => $value instanceof DateTime ? $value->getTimestamp() : $value,
'boolean' => (bool) $value,
'json' => is_string($value) ? json_decode($value, true) : $value,
// 参数化规则
'number' => $this->formatNumber($value, $params), // 数字格式化
'truncate' => $this->truncateString($value, $params), // 字符串截断
'enum' => $this->mapEnum($value, $params), // 枚举映射
default => $value,
};
}
// 数字格式化(示例参数化实现)
protected function formatNumber($value, array $params): string
{
$precision = $params['precision'] ?? 2; // 默认保留2位小数
return number_format((float) $value, $precision);
}
// 字符串截断(示例参数化实现)
protected function truncateString($value, array $params): string
{
$length = $params['length'] ?? 20; // 默认截断到20字符
$suffix = $params['suffix'] ?? '...'; // 省略符
if (! is_string($value)) {
$value = (string) $value;
}
return mb_strlen($value) > $length
? mb_substr($value, 0, $length) . $suffix
: $value;
}
// 枚举映射(示例参数化实现)
protected function mapEnum($value, array $params): mixed
{
$map = $params['map'] ?? []; // 枚举映射表,如 [1 => '男', 2 => '女']
return $map[$value] ?? $value;
}
// 修正 buildQuery 方法,避免使用 new static()
protected static function buildQuery(array $conditions = [], array $orderBy = []): Builder
{
// 使用静态 query() 方法获取查询构建器
$query = static::query();
// 处理搜索条件
foreach ($conditions as $field => $value) {
$query->where($field, $value);
}
// 处理排序
foreach ($orderBy as $field => $direction) {
$query->orderBy($field, $direction);
}
return $query;
}
/**
* 应用搜索条件.
*/
protected function applySearch(Builder $query, array $conditions): void
{
foreach ($conditions as $field => $value) {
// 基础过滤:非字符串字段名或未设置值的参数直接跳过
if (! is_string($field) || ! isset($value)) {
continue;
}
// 核心限制:只处理$search数组中定义的字段
if (! isset($this->search[$field])) {
continue;
}
// 处理嵌套关系查询(如:user.name需在$search中配置完整键名
if (str_contains($field, '.')) {
[$relation, $relationField] = explode('.', $field, 2);
$query->whereHas($relation, function ($q) use ($relationField, $value) {
// 嵌套查询默认使用精确匹配,如需特殊规则可在$search中自定义处理
$q->where($relationField, $value);
});
continue;
}
// 优先使用自定义搜索器方法(仅对$search中存在的字段生效
$searchMethod = 'search' . ucfirst($field);
if (method_exists($this, $searchMethod)) {
$this->{$searchMethod}($query, $value);
continue;
}
// 应用$search中定义的搜索规则如=、like等
$this->applySearchRule($query, $field, $value, $this->search[$field]);
}
}
/**
* 应用搜索规则.
*/
protected function applySearchRule(Builder $query, string $field, mixed $value, array|string $rule): void
{
if (is_array($rule)) {
$type = $rule[0];
$params = $rule[1] ?? [];
} else {
$type = $rule;
$params = [];
}
switch ($type) {
case 'like':
$query->where($field, 'like', "%{$value}%");
break;
case 'like_left':
$query->where($field, 'like', "%{$value}");
break;
case 'like_right':
$query->where($field, 'like', "{$value}%");
break;
case 'in':
$query->whereIn($field, (array) $value);
break;
case 'not_in':
$query->whereNotIn($field, (array) $value);
break;
case 'gt':
$query->where($field, '>', $value);
break;
case 'lt':
$query->where($field, '<', $value);
break;
case 'gte':
$query->where($field, '>=', $value);
break;
case 'lte':
$query->where($field, '<=', $value);
break;
case 'between':
$query->whereBetween($field, (array) $value);
break;
case 'null':
$query->whereNull($field);
break;
case 'not_null':
$query->whereNotNull($field);
break;
default:
$query->where($field, $type, $value);
}
}
}