如何在Laravel中通用化API Resource实现跨控制器复用
优雅实现Laravel API资源的动态通用化复用
我之前在做Laravel API开发的时候,也烦透了每个控制器里都要重复写资源转换的代码,刚好可以基于你现有的ApiResponser Trait来实现一个通用化方案,彻底消除跨模型、跨控制器的代码冗余。
核心思路
我们要让ApiResponser自动识别传入集合对应的模型,动态加载其对应的Resource类,不需要在控制器里手动调用UserResource::collection()这类代码,完全复用现有的showAll方法逻辑。
修改ApiResponser Trait
重点改造resourceData方法,让它具备动态解析Resource的能力:
namespace App\Traits; use Illuminate\Support\Collection; trait ApiResponser { private function successResponse($data, $code) { return response()->json($data, $code); } protected function showAll(Collection $collection, $code = 200) { $collection = $this->resourceData($collection); $collection = $this->filterData($collection); $collection = $this->sortData($collection); $collection = $this->paginate($collection); $collection = $this->cacheResponse($collection); // 调整响应结构为标准REST格式,把数据放在data键下 return $this->successResponse(['data' => $collection, 'code' => $code], $code); } protected function resourceData(Collection $collection) { // 处理空集合的边界情况 if ($collection->isEmpty()) { return $collection; } // 获取集合中第一个模型的完整类名 $modelClass = $collection->first()::class; // 动态构建对应的Resource类名(默认规则:模型名+Resource,存放在App\Http\Resources下) $resourceClass = str_replace('App\\', 'App\\Http\\Resources\\', $modelClass) . 'Resource'; // 检查Resource类是否存在,避免类不存在导致的致命错误 if (!class_exists($resourceClass)) { // 可根据需求抛出异常,或直接返回原集合降级处理 return $collection; } // 用Resource自动转换集合 return $resourceClass::collection($collection); } }
关键代码解释
- 动态模型识别:从集合中取出第一个模型实例,通过
::class获取其完整命名空间类名(比如App\User) - Resource路径构建:把模型的命名空间前缀
App\替换为App\Http\Resources\,再加上Resource后缀,自动匹配到App\Http\Resources\UserResource - 安全校验:先判断Resource类是否存在,避免因模型未定义对应Resource导致的报错
- 集合转换:调用Laravel Resource自带的
collection()方法完成批量数据转换
控制器使用(无需修改原代码)
你原来的UserController代码完全不用改动,直接调用showAll($users)就会自动应用UserResource:
namespace App\Http\Controllers\User; use App\User; use Illuminate\Http\Request; use App\Http\Controllers\ApiController; class UserController extends ApiController { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $users = User::all(); // 现在会自动应用UserResource,无需手动写转换逻辑 return $this->showAll($users); } }
扩展:支持单个模型的响应
如果需要支持单个模型的详情接口(比如show方法),可以在Trait里添加showOne方法,同样实现动态Resource解析:
protected function showOne($model, $code = 200) { $modelClass = get_class($model); $resourceClass = str_replace('App\\', 'App\\Http\\Resources\\', $modelClass) . 'Resource'; if (class_exists($resourceClass)) { $model = new $resourceClass($model); } return $this->successResponse(['data' => $model, 'code' => $code], $code); }
控制器里直接使用:
public function show(User $user) { return $this->showOne($user); }
灵活扩展:自定义Resource命名
如果你的Resource不是按“模型名+Resource”的规则命名(比如App\Http\Resources\AdminUserResource),可以在模型里定义一个静态属性来指定Resource类:
// App\User.php class User extends Model { // 自定义Resource类路径 public static $resourceClass = \App\Http\Resources\AdminUserResource::class; }
然后修改resourceData方法里的Resource类获取逻辑:
// 优先读取模型定义的resourceClass,否则用默认规则 $resourceClass = property_exists($modelClass, 'resourceClass') ? $modelClass::$resourceClass : str_replace('App\\', 'App\\Http\\Resources\\', $modelClass) . 'Resource';
这样就可以完全自定义每个模型对应的Resource了。
内容的提问来源于stack exchange,提问作者Amr Ragab
相关产品推荐
相关产品推荐

