Files
hyperf_data/extend/Aether/PHP/Hyperf/AetherModel.php
Aether db760bb276 ..
2025-09-25 10:46:45 +08:00

492 lines
14 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 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 = '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 static function createOne(array $data): AetherModel|Builder|HyperfModel
{
return static::query()->create($data);
}
/**
* 快捷更新.
*/
public static function updateById(int $id, array $data): bool
{
return static::query()->where('id', $id)->update($data) > 0;
}
/**
* 快捷删除指定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 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)) {
continue;
}
// 处理嵌套关系查询(如:user.name
if (str_contains($field, '.')) {
[$relation, $relationField] = explode('.', $field, 2);
$query->whereHas($relation, function ($q) use ($relationField, $value) {
$q->where($relationField, $value);
});
continue;
}
// 检查是否有自定义搜索器方法
$searchMethod = 'search' . ucfirst($field);
if (method_exists($this, $searchMethod)) {
$this->{$searchMethod}($query, $value);
continue;
}
// 应用搜索规则配置
if (isset($this->search[$field])) {
$this->applySearchRule($query, $field, $value, $this->search[$field]);
continue;
}
// 默认精确匹配(仅对$search中允许的字段生效因已通过白名单过滤
$query->where($field, $value);
}
}
/**
* 应用搜索规则.
*/
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);
}
}
}