init
This commit is contained in:
159
extend/Aether/PHP/Hyperf/Traits/AetherEnum.php
Normal file
159
extend/Aether/PHP/Hyperf/Traits/AetherEnum.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aether\Traits;
|
||||
|
||||
use App\Notice\Enum\NoticeStatusEnum;
|
||||
use App\Notice\Model\NoticeStatsModel;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
|
||||
trait AetherEnum
|
||||
{
|
||||
/**
|
||||
* 获取所有枚举值数组(严格保持定义顺序).
|
||||
* @return array<int|string> 枚举值集合
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
self::validateEnumStructure();
|
||||
|
||||
$values = [];
|
||||
foreach (self::cases() as $case) {
|
||||
$values[] = $case->value;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有枚举描述数组(与values()顺序一一对应).
|
||||
* @return array<string> 描述文本集合
|
||||
*/
|
||||
public static function descriptions(): array
|
||||
{
|
||||
self::validateEnumStructure();
|
||||
|
||||
$descriptions = [];
|
||||
foreach (self::cases() as $case) {
|
||||
$descriptions[] = $case->description();
|
||||
}
|
||||
return $descriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值-描述映射数组(用于下拉选择等场景).
|
||||
* @return array<int|string, string> 键为枚举值,值为描述文本
|
||||
*/
|
||||
public static function valueMap(): array
|
||||
{
|
||||
self::validateEnumStructure();
|
||||
|
||||
$map = [];
|
||||
foreach (self::cases() as $case) {
|
||||
$map[$case->value] = $case->description();
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据值获取枚举实例(严格模式).
|
||||
* @param int|string $value 枚举值
|
||||
* @return AetherEnum|NoticeStatsModel|NoticeStatusEnum 枚举实例
|
||||
*/
|
||||
public static function fromValue(int|string $value): self
|
||||
{
|
||||
self::validateEnumStructure();
|
||||
|
||||
// 检查值类型是否与枚举类型匹配(基于第一个case的类型)
|
||||
$firstCase = self::cases()[0] ?? null;
|
||||
if ($firstCase) {
|
||||
$expectedType = gettype($firstCase->value);
|
||||
$actualType = gettype($value);
|
||||
if ($expectedType !== $actualType) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'枚举值类型不匹配,%s期望%s类型,实际为%s',
|
||||
self::class,
|
||||
$expectedType,
|
||||
$actualType
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$enum = self::tryFrom($value);
|
||||
if (! $enum) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'无效的%s值: %s,允许值: %s',
|
||||
self::class,
|
||||
$value,
|
||||
implode(', ', self::values())
|
||||
));
|
||||
}
|
||||
return $enum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据描述获取枚举实例(精确匹配).
|
||||
* @param string $description 描述文本
|
||||
* @return null|AetherEnum|NoticeStatsModel|NoticeStatusEnum 匹配的枚举实例,无匹配时返回null
|
||||
*/
|
||||
public static function fromDescription(string $description): ?self
|
||||
{
|
||||
self::validateEnumStructure();
|
||||
|
||||
foreach (self::cases() as $case) {
|
||||
if ($case->description() === $description) {
|
||||
return $case;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查值是否为有效的枚举值(严格类型检查).
|
||||
* @param int|string $value 待检查的值
|
||||
* @return bool 是否有效
|
||||
*/
|
||||
public static function isValidValue(int|string $value): bool
|
||||
{
|
||||
self::validateEnumStructure();
|
||||
|
||||
foreach (self::cases() as $case) {
|
||||
if ($case->value === $value) { // 严格相等,避免类型松散匹配
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验枚举结构合法性(替代__init,私有静态方法).
|
||||
*/
|
||||
private static function validateEnumStructure(): void
|
||||
{
|
||||
// 检查当前类是否为枚举
|
||||
if (! (new ReflectionClass(self::class))->isEnum()) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'AetherEnum trait仅允许枚举类使用,%s不是枚举',
|
||||
self::class
|
||||
));
|
||||
}
|
||||
|
||||
// 检查枚举是否实现了description()方法
|
||||
if (! method_exists(self::class, 'description')) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'枚举类%s必须实现description()方法(返回字符串描述)',
|
||||
self::class
|
||||
));
|
||||
}
|
||||
|
||||
// 检查description()方法返回值是否为字符串
|
||||
$sampleCase = self::cases()[0] ?? null;
|
||||
if ($sampleCase && ! is_string($sampleCase->description())) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'枚举类%s的description()方法必须返回字符串',
|
||||
self::class
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
10
extend/Aether/PHP/Hyperf/Traits/AetherSearchable.php
Normal file
10
extend/Aether/PHP/Hyperf/Traits/AetherSearchable.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Aether\Traits;
|
||||
|
||||
trait AetherSearchable
|
||||
{
|
||||
|
||||
}
|
||||
24
extend/Aether/PHP/Hyperf/Traits/AetherSoftDelete.php
Normal file
24
extend/Aether/PHP/Hyperf/Traits/AetherSoftDelete.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Aether\Traits;
|
||||
|
||||
use Hyperf\Database\Model\SoftDeletes;
|
||||
|
||||
/**
|
||||
* 通用软删除Trait,供需要软删除的模型使用
|
||||
*/
|
||||
trait AetherSoftDelete
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 初始化软删除相关配置(自动隐藏deleted_at字段)
|
||||
*/
|
||||
protected function initializeAetherSoftDeletes(): void
|
||||
{
|
||||
// 自动将deleted_at添加到隐藏字段,避免序列化时暴露
|
||||
if (!in_array('deleted_at', $this->hidden, true)) {
|
||||
$this->hidden[] = 'deleted_at';
|
||||
}
|
||||
}
|
||||
}
|
||||
125
extend/Aether/PHP/Hyperf/Traits/AetherTree.php
Normal file
125
extend/Aether/PHP/Hyperf/Traits/AetherTree.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Aether\Traits;
|
||||
|
||||
use Aether\AetherModel;
|
||||
use Hyperf\Database\Model\Collection;
|
||||
use LogicException;
|
||||
|
||||
trait AetherTree
|
||||
{
|
||||
// 初始化时检查当前类是否继承AetherModel
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
|
||||
if (!$this instanceof AetherModel) {
|
||||
throw new LogicException(
|
||||
"使用AetherTree trait的类必须继承AetherModel,当前类: " . get_class($this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 抽象方法:获取父ID字段名(由子类实现)
|
||||
*/
|
||||
abstract protected function getParentIdField(): string;
|
||||
|
||||
/**
|
||||
* 抽象方法:获取排序字段名(由子类实现)
|
||||
*/
|
||||
abstract protected function getSortField(): string;
|
||||
|
||||
/**
|
||||
* 构建树形结构
|
||||
*/
|
||||
public static function buildTree($items, int $parentId = 0): array
|
||||
{
|
||||
$self = new static();
|
||||
$parentField = $self->getParentIdField();
|
||||
$sortField = $self->getSortField();
|
||||
|
||||
$items = $items instanceof Collection ? $items->toArray() : $items;
|
||||
$tree = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item[$parentField] == $parentId) {
|
||||
$children = static::buildTree($items, $item['id']);
|
||||
if (!empty($children)) {
|
||||
$item['children'] = $children;
|
||||
}
|
||||
$tree[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$self->sortTreeItems($tree, $sortField);
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形节点排序
|
||||
*/
|
||||
protected function sortTreeItems(array &$items, string $sortField): void
|
||||
{
|
||||
usort($items, function ($a, $b) use ($sortField) {
|
||||
$direction = $this->treeSortDirection ?? 'asc';
|
||||
return $direction === 'desc'
|
||||
? $b[$sortField] <=> $a[$sortField]
|
||||
: $a[$sortField] <=> $b[$sortField];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定节点的所有子节点ID
|
||||
*/
|
||||
public function getChildIds(int $id): array
|
||||
{
|
||||
$parentField = $this->getParentIdField();
|
||||
// 现在可以安全调用newQuery(),因为已通过继承检查
|
||||
$allItems = $this->newQuery()->get(['id', $parentField])->toArray();
|
||||
$ids = [$id];
|
||||
|
||||
$this->collectChildIds($allItems, $id, $parentField, $ids);
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集子节点ID
|
||||
*/
|
||||
private function collectChildIds(array $items, int $parentId, string $parentField, array &$ids): void
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
if ($item[$parentField] == $parentId) {
|
||||
$ids[] = $item['id'];
|
||||
$this->collectChildIds($items, $item['id'], $parentField, $ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的完整路径
|
||||
*/
|
||||
public function getPath(int $id): array
|
||||
{
|
||||
$parentField = $this->getParentIdField();
|
||||
// 安全调用newQuery()
|
||||
$node = $this->newQuery()->find($id);
|
||||
if (!$node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$path = [$node->toArray()];
|
||||
$parentId = $node[$parentField];
|
||||
|
||||
while ($parentId > 0) {
|
||||
$parent = $this->newQuery()->find($parentId);
|
||||
if (!$parent) {
|
||||
break;
|
||||
}
|
||||
array_unshift($path, $parent->toArray());
|
||||
$parentId = $parent[$parentField];
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user