$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); } }