Laravel REST API:如何将数据库列名映射为参数别名?
我有一个名为location_relationship_id的数据库列,它与Location模型存在关联关系。目前在Postman中,使用http://localhost/api/v1/traffic-violations?location_relationship_id=25这个参数可以正常查询,返回的JSON数据中该字段已正确映射为location而非原列名。我希望将请求URL改为http://localhost/api/v1/traffic-violations?location=Al-Morooj,但这么做会出现“列不存在”的错误。我正在使用itsrennyman/laravel-rest-filters包处理数据,以下是我的控制器和Resource类代码:
控制器代码
/** * Display a listing of the resource. * @return \Illuminate\Http\Response */ public function index(Request $request) { $violations = TrafficViolation::withRestFilters(); return new TrafficViolationCollection($violations->paginate()); } /** * Display a specified resource * @param \App\Models\TrafficViolation $violation * @return Illuminate\Http\Response */ public function show($id) { return new TrafficViolationResource(TrafficViolation::findOrFail($id)); }
Resource类代码
class TrafficViolationResource extends JsonResource { // Define the mapping configuration for each field private const MAPPING = [ 'location_relationship_id' => ['key' => 'location', 'method' => 'mapLocationField'], 'violation_type_relationship_id' => ['key' => 'violationType', 'method' => 'mapViolationTypeField'], 'working_shift_relationship_id' => ['key' => 'workingShift', 'method' => 'mapWorkingShiftField'] ]; public function toArray($request) { return $this->transformData(parent::toArray($request)); } /** * Transform the data by excluding specified fields and mapping the remaining fields. * */ private function transformData(array $data): array { return collect($data) ->except('created_at', 'updated_at') ->mapWithKeys(function ($value, $key) { // Check if the field has a mapping configuration if (array_key_exists($key, self::MAPPING)) { $mapping = self::MAPPING[$key]; // Invoke the corresponding mapping method and pass the value and output key return $this->{$mapping['method']}($value, $mapping['key']); } // If no mapping exists, convert the field to camel case return [Str::camel($key) => $value]; }) ->toArray(); } /** * Map the 'location_relationship_id' field by accessing the relationship and transforming the value. */ private function mapLocationField($value, $key): array { $name = $this->locationRelationship->name; return [$key => $name]; } /** * Map the 'violation_type_relationship_id' field by accessing the relationship and transforming the value. */ private function mapViolationTypeField($value, $key): array { $name = $this->violationTypeRelationship->name; return [$key => $name]; } /** * Map the 'working_shift_relationship_id' field by accessing the relationship and transforming the value. */ private function mapWorkingShiftField($value, $key): array { $name = $this->workingShiftRelationship->name; return [$key => $name]; } }
请问该如何实现这种参数名映射,或者是否无需修改当前方式?
报错原因是itsrennyman/laravel-rest-filters默认会把请求参数直接当作数据库列名处理,但location并不是traffic_violations表的字段,因此触发“列不存在”错误。要实现通过location=Al-Morooj查询,需要让包知道如何处理这个参数,有两种可行方案:
方案一:控制器内手动处理参数
在index方法中先拦截location参数,通过关联查询筛选数据,再移除该参数避免被rest filters错误解析:
public function index(Request $request) { $query = TrafficViolation::query(); // 处理location参数,关联查询Location模型的name字段 if ($request->has('location')) { $query->whereHas('locationRelationship', function ($q) use ($request) { $q->where('name', $request->input('location')); // 如需模糊查询,可替换为:$q->where('name', 'like', "%{$request->input('location')}%"); }); // 移除参数,防止rest filters尝试查询不存在的列 $request->request->remove('location'); } $violations = $query->withRestFilters(); return new TrafficViolationCollection($violations->paginate()); }
方案二:模型内定义自定义过滤规则(推荐)
利用laravel-rest-filters的自定义过滤功能,在TrafficViolation模型中添加对应方法,让包自动识别并处理location参数:
use Illuminate\Database\Eloquent\Model; use ItsRennyman\LaravelRestFilters\Traits\HasRestFilters; class TrafficViolation extends Model { use HasRestFilters; // 自定义location参数的过滤逻辑 public function filterLocation($value) { $this->whereHas('locationRelationship', function ($q) use ($value) { $q->where('name', $value); // 如需模糊查询,可替换为:$q->where('name', 'like', "%{$value}%"); }); } }
这种方式无需修改控制器代码,withRestFilters()会自动调用filterLocation方法处理请求中的location参数,符合包的设计规范。
补充说明
两种方案都是通过whereHas关联查询Location模型的name字段来实现需求,区别在于参数处理的位置。如果后续还有类似的关联参数需要映射,方案二的扩展性更强,只需在模型中添加对应的filterXXX方法即可。
内容的提问来源于stack exchange,提问作者Abdulrahman Mushref

