Initial commit
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace app\mzl\common\traits;
|
||||
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait DataTransformer
|
||||
{
|
||||
/**
|
||||
* 核心方法:对任意数据进行转换并返回数组
|
||||
*
|
||||
* @param mixed $rawData 原始数据,可为 Paginator / Model / Collection / array 等
|
||||
* @param callable|null $postProcessor 可选的后置处理回调,接受处理完的数组再返回新的数组
|
||||
* @param callable|null $fieldFormatter 可选的字段命名转换回调,如 snake_case
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function transformData(
|
||||
mixed $rawData,
|
||||
?callable $postProcessor = null,
|
||||
?callable $fieldFormatter = null
|
||||
): array {
|
||||
try {
|
||||
$fieldFormatter = $fieldFormatter ?: fn($field) => $field;
|
||||
|
||||
if ($rawData instanceof LengthAwarePaginator) {
|
||||
$formatted = $this->formatPaginated($rawData, $postProcessor, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Model) {
|
||||
$formatted = $this->formatModel($rawData, $fieldFormatter);
|
||||
} elseif ($rawData instanceof Collection) {
|
||||
$formatted = $this->formatCollection($rawData, $fieldFormatter);
|
||||
} elseif (is_array($rawData)) {
|
||||
$formatted = $this->formatArray($rawData, $fieldFormatter);
|
||||
} else {
|
||||
$formatted = (array) $rawData;
|
||||
}
|
||||
|
||||
if (is_callable($postProcessor)) {
|
||||
$formatted = $postProcessor($formatted);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
} catch (\Throwable $e) {
|
||||
return [
|
||||
'error' => true,
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化分页数据:将每条记录也进行 transformData 递归处理
|
||||
*
|
||||
* @param LengthAwarePaginator $paginator
|
||||
* @param callable|null $postProcessor
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatPaginated(
|
||||
LengthAwarePaginator $paginator,
|
||||
?callable $postProcessor,
|
||||
callable $fieldFormatter
|
||||
): array {
|
||||
$list = $paginator->getCollection()->map(function ($item) use ($postProcessor, $fieldFormatter) {
|
||||
return $this->transformData($item, $postProcessor, $fieldFormatter);
|
||||
})->toArray();
|
||||
|
||||
$meta = [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'first_page_url' => $paginator->url(1),
|
||||
'last_page_url' => $paginator->url($paginator->lastPage()),
|
||||
'next_page_url' => $paginator->nextPageUrl(),
|
||||
'prev_page_url' => $paginator->previousPageUrl(),
|
||||
];
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'meta' => $meta,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Eloquent 模型:递归嵌套关联
|
||||
*
|
||||
* @param Model $model
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatModel(Model $model, callable $fieldFormatter): array
|
||||
{
|
||||
$formatted = [];
|
||||
|
||||
// 主表字段
|
||||
foreach ($model->getAttributes() as $key => $value) {
|
||||
$formatted[$fieldFormatter($key)] = $value;
|
||||
}
|
||||
|
||||
// 关联数据,递归嵌套
|
||||
foreach ($model->getRelations() as $relation => $relationData) {
|
||||
if ($relationData instanceof Model) {
|
||||
// 单个关联模型
|
||||
$formatted[$relation] = $this->formatModel($relationData, $fieldFormatter);
|
||||
} elseif ($relationData instanceof Collection) {
|
||||
// 关联集合
|
||||
$formatted[$relation] = $relationData
|
||||
->map(fn($item) => $this->transformData($item, null, $fieldFormatter))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化集合
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatCollection(Collection $collection, callable $fieldFormatter): array
|
||||
{
|
||||
return $collection->map(fn($item) => $this->transformData($item, null, $fieldFormatter))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化普通数组
|
||||
*
|
||||
* @param array $array
|
||||
* @param callable $fieldFormatter
|
||||
* @return array
|
||||
*/
|
||||
protected function formatArray(array $array, callable $fieldFormatter): array
|
||||
{
|
||||
return array_map(fn($item) => $this->transformData($item, null, $fieldFormatter), $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归裁剪数组,只保留指定字段
|
||||
*
|
||||
* @param array $data 当前要裁剪的数据(可能是单条关联,也可能是 list)
|
||||
* @param array $fieldsMap 白名单映射,key=>[allowed fields]
|
||||
* @param string $path 当前路径('' / 'items' / 'items.sub' …)
|
||||
* @return array
|
||||
*/
|
||||
protected function pruneFields(array $data, array $fieldsMap, string $path = ''): array
|
||||
{
|
||||
// —— 新增:如果这是一个纯列表(list),逐项递归 prune 并返回
|
||||
if ($this->isListArray($data)) {
|
||||
return array_map(
|
||||
fn($item) => is_array($item)
|
||||
? $this->pruneFields($item, $fieldsMap, $path)
|
||||
: $item,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
// 如果当前层没有配置白名单,原样返回(或也可以 return [] 视业务)
|
||||
if (! isset($fieldsMap[$path])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$allowed = $fieldsMap[$path];
|
||||
$out = [];
|
||||
|
||||
foreach ($allowed as $key) {
|
||||
if (! array_key_exists($key, $data)) {
|
||||
continue;
|
||||
}
|
||||
$val = $data[$key];
|
||||
|
||||
// 如果下一层还是个数组,继续递归
|
||||
if (is_array($val)) {
|
||||
$nextPath = ltrim($path . '.' . $key, '.');
|
||||
$out[$key] = $this->pruneFields($val, $fieldsMap, $nextPath);
|
||||
} else {
|
||||
$out[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 Paginator/Model/Collection/array 统一格式化,并按 fieldsMap 裁剪
|
||||
*
|
||||
* @param mixed $rawData
|
||||
* @param array|null $fieldsMap 顶层 '' + 各关联路径的白名单
|
||||
* @param callable|null $fieldFormatter 字段格式化回调
|
||||
* @param callable|null $postProcessor 单条记录后置处理回调
|
||||
* @return array
|
||||
*/
|
||||
protected function transformAndPrune(
|
||||
mixed $rawData,
|
||||
?array $fieldsMap = null,
|
||||
?callable $fieldFormatter = null,
|
||||
?callable $postProcessor = null
|
||||
): array {
|
||||
// 1) 统一转换成数组
|
||||
$data = $this->transformData($rawData, null, $fieldFormatter);
|
||||
|
||||
// 2) 如果没有白名单配置,直接返回原始数据
|
||||
if (empty($fieldsMap) || !isset($fieldsMap[''])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 3) 分页结构:['list'=>[...],'meta'=>[...]]
|
||||
if (isset($data['list'], $data['meta'])) {
|
||||
$data['list'] = array_map(
|
||||
fn($item) => $this->processItem($item, $fieldsMap, $postProcessor),
|
||||
$data['list']
|
||||
);
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 4) 数字索引数组 => 当作“非分页列表”处理
|
||||
if ($this->isListArray($data)) {
|
||||
return array_map(
|
||||
fn($item) => $this->processItem($item, $fieldsMap, $postProcessor),
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
// 5) 单条记录
|
||||
return $this->processItem($data, $fieldsMap, $postProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪并做后置处理
|
||||
*/
|
||||
private function processItem(
|
||||
array $item,
|
||||
array $fieldsMap,
|
||||
?callable $postProcessor
|
||||
): array {
|
||||
$pruned = $this->pruneFields($item, $fieldsMap);
|
||||
return $postProcessor ? $postProcessor($pruned) : $pruned;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个数组是不是“索引从 0 开始的列表”
|
||||
*/
|
||||
private function isListArray(array $arr): bool
|
||||
{
|
||||
if (empty($arr)) {
|
||||
return true;
|
||||
}
|
||||
return array_keys($arr) === range(0, count($arr) - 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace app\mzl\common\traits;
|
||||
|
||||
use support\Response;
|
||||
|
||||
trait ResponseTrait
|
||||
{
|
||||
/**
|
||||
* 返回成功响应
|
||||
*
|
||||
* @param mixed $data 响应数据
|
||||
* @param string $message 响应消息,默认“请求成功”
|
||||
* @param int $code 内部状态码,默认 0 表示成功
|
||||
* @param int $httpStatus HTTP 状态码,默认 200
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function successResponse(
|
||||
$data = [],
|
||||
string $message = '请求成功',
|
||||
int $code = 0,
|
||||
int $httpStatus = 200,
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result('success', $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回错误响应
|
||||
*
|
||||
* @param string $message 响应错误消息,默认“请求失败”
|
||||
* @param int $code 内部状态码,默认 1 表示错误
|
||||
* @param int $httpStatus HTTP 状态码,默认 400
|
||||
* @param mixed $data 可选的错误数据
|
||||
* @param array $header 响应头,默认 JSON 格式
|
||||
* @return Response
|
||||
*/
|
||||
public function errorResponse(
|
||||
string $message = '请求失败',
|
||||
int $code = 1,
|
||||
int $httpStatus = 400,
|
||||
$data = [],
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result('error', $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一响应格式方法
|
||||
*
|
||||
* @param string $status 响应状态,例如 "success" 或 "error"
|
||||
* @param int $code 内部状态码
|
||||
* @param int $httpStatus HTTP 状态码
|
||||
* @param string $message 响应消息
|
||||
* @param mixed $data 响应数据
|
||||
* @param array $header 响应头
|
||||
* @return Response
|
||||
*/
|
||||
protected function result(
|
||||
string $status,
|
||||
int $code,
|
||||
int $httpStatus,
|
||||
string $message,
|
||||
$data,
|
||||
array $header
|
||||
): Response {
|
||||
$body = [
|
||||
'status' => $status,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
];
|
||||
return response(json_encode($body, JSON_UNESCAPED_UNICODE), $httpStatus, $header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace app\mzl\common\traits;
|
||||
|
||||
/**
|
||||
* 调用服务类的便捷工具
|
||||
*/
|
||||
trait ServiceCallTrait
|
||||
{
|
||||
/**
|
||||
* 实例缓存
|
||||
*/
|
||||
protected array $serviceCache = [];
|
||||
|
||||
/**
|
||||
* 调用指定 Service
|
||||
*
|
||||
* @param string $serviceClass 服务类名(必须继承 BaseService)
|
||||
* @return object
|
||||
*/
|
||||
protected function callService(string $serviceClass): object
|
||||
{
|
||||
if (!isset($this->serviceCache[$serviceClass])) {
|
||||
$this->serviceCache[$serviceClass] = new $serviceClass();
|
||||
}
|
||||
return $this->serviceCache[$serviceClass];
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷调用某服务下某个方法
|
||||
*
|
||||
* @param string $serviceClass
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
*/
|
||||
protected function callServiceMethod(string $serviceClass, string $method, array $args = []): mixed
|
||||
{
|
||||
$service = $this->callService($serviceClass);
|
||||
|
||||
if (!method_exists($service, $method)) {
|
||||
throw new \BadMethodCallException("Method {$method} does not exist in {$serviceClass}");
|
||||
}
|
||||
|
||||
return call_user_func_array([$service, $method], $args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user