英文:
Laravel REST API: how to map database column name with another name for parameters?
问题
I have a column named location_relationship_id
which is a relationship that belongs to Location
model.
我有一个名为location_relationship_id
的列,它是属于Location
模型的关联。
In postman, this parameter works: http://localhost/api/v1/traffic-violations?location_relationship_id=25
在Postman中,这个参数有效:http://localhost/api/v1/traffic-violations?location_relationship_id=25
Results:
结果:
"data": [
{
"id": 1,
"violationType": "Pass traffic light",
"location": "Al-Morooj",
"workingShift": "(B) Evening",
}
],
In the json, the naming/mapping is correct. As you can see it's named location
rather than location_relationship_id
.
在JSON中,命名/映射是正确的。如您所见,它被命名为location
而不是location_relationship_id
。
How can I replace the url to be something like this: http://localhost/api/v1/traffic-violations?location=Al-Morooj
? Or should I leave it as is and not bother?
我如何将URL替换为类似于http://localhost/api/v1/traffic-violations?location=Al-Morooj
这样的形式?还是应该保持原样不予理会?
When I do this I get a column doesn't exist error (obviously).
当我这样做时,会出现列不存在的错误(显然)。
I am using this package to return the data itsrennyman/laravel-rest-filters
我正在使用这个包来返回数据 itsrennyman/laravel-rest-filters
This is my controller:
这是我的控制器:
/**
* 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));
}
And this is my Resource class:
这是我的资源类:
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];
}
}
英文:
I have a column named location_relationship_id
which is a relationship that belongs to Location
model.
In postman, this parameter works: http://localhost/api/v1/traffic-violations?location_relationship_id=25
Results:
"data": [
{
"id": 1,
"violationType": "Pass traffic light",
"location": "Al-Morooj",
"workingShift": "(B) Evening",
}
],
In the json, the naming/mapping is correct. As you can see it's named location
rather than location_relationship_id
.
How can I replace the url to be something like this: http://localhost/api/v1/traffic-violations?location=Al-Morooj
? Or should I leave it as is and not bother?
When I do this I get a column doesn't exist error (obviously).
I am using this package to return the data itsrennyman/laravel-rest-filters
This is my controller:
/**
* 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));
}
And this is my Resource class:
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];
}
}
答案1
得分: 2
抱歉,你的插件默认不支持这个功能。好消息是插件本身非常简单(只有两个类),所以现在我们放弃使用它,尝试在你的应用程序中自己实现。
有两个类,Attribute
和 RestServiceProvider
。
RestServiceProvider.php 大部分内容也将是相同的。让我们更改它的命名空间并添加映射字段的选项,如下所示:
// 这是我们将映射字段名称的地方
if (property_exists($model, 'fieldMapping')) {
$field = $model->fieldMapping[$field] ?? $field;
}
所以,app/Providers/RestServiceProvider.php
:
<?php
namespace App\Providers;
use App\RestFilters\Helpers\Attribute;
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Builder;
class RestServiceProvider extends ServiceProvider
{
//...(此处省略未变更的内容)
// 这里是添加的代码
if (property_exists($model, 'fieldMapping')) {
$field = $model->fieldMapping[$field] ?? $field;
}
//...(此处省略未变更的内容)
}
Attribute.php 可以保持不变,只需更改命名空间:
/app/RestFilters/Helpers/Attribute.php
:
<?php
namespace App\RestFilters\Helpers;
class Attribute
{
//...(此处省略未变更的内容)
}
为了使其工作,你现在可以将 $fieldMapping
属性添加到你的模型中:
// App/Models/TrafficViolation.php
class TrafficViolation extends Model
{
public array $fieldMapping = [
'location' => 'location_relationship_id',
];
}
并且不要忘记在 app.php
中注册你的新 ServiceProvider
:
// config/app.php
$providers = [
//...(其他内容)
\App\Providers\RestServiceProvider::class,
];
这样你现在就能够在每个模型上定义映射了。
英文:
Sadly, the plugin you are using does not support this out of the box. But good news is that the plugin itself is very simple ( just two classes ), so lets ditch it for now and try implementing it ourself in your application
So there are two classes, Attribute
and RestServiceProvider
RestServiceProvider.php will mostly also be identical. Let's change its namespace and add option to map fields, like
// This is where we will map field names
if (property_exists($model, 'fieldMapping')) {
$field = $model->fieldMapping[$field] ?? $field;
}
So
app/Providers/RestServiceProvider.php
<?php
namespace App\Providers;
use App\RestFilters\Helpers\Attribute;
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Builder;
class RestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Builder::macro('withRestFilters', function () {
// Model Instance
$model = $this->getModel();
// Filtering
$query = request()->query->all();
// Needs to exclude key items like "sort"
collect($query)->except(['sort', 'fields', 'embed', 'page'])
->except($model->bannedFields ?: [])
->each(function ($value, $field) use ($model) {
// This is where we will map field names
if (property_exists($model, 'fieldMapping')) {
$field = $model->fieldMapping[$field] ?? $field;
}
// Check has Multiple Filters
if (Str::contains($value, ',')) {
$this->whereIn($field, Str::of($value)->explode(','));
} else {
// Empty value. Skip.
if (empty($value)) {
return true;
}
// Check has an extra attribute
if (Str::contains($value, ':')) {
$attributeAndValue = Str::of($value)->explode(':');
// Check Banned Attributes
if ($model->bannedAttributes && in_array($attributeAndValue[0], $model->bannedAttributes)) {
return true;
}
// Replace Attribute
$replacedAttribute = Attribute::sobstitute($attributeAndValue[0]);
// If not exists returns false, so not considered.
if (!$replacedAttribute) {
return true;
}
$this->where($field, $replacedAttribute, $attributeAndValue[1]);
} else {
$this->where($field, '=', $value);
}
}
});
// Selecting Fields
if (request()->filled('fields')) {
$fields = Str::of(request()->fields)->explode(',');
$fields->each(function ($field) {
$this->addSelect($field);
});
}
// Sorting
if (request()->filled('sort')) {
$fields = Str::of(request()->sort)->explode(',');
$fields->each(function ($field) {
$sortDirection = Str::startsWith($field, '-') ? 'DESC' : 'ASC';
$this->orderBy(Str::replaceFirst('-', '', $field), $sortDirection);
});
}
// Embedding (Needs to add id field if filtered.)
if (request()->filled('embed')) {
$fields = Str::of(request()->embed)->explode(',');
$fields->each(function ($field) {
$this->with($field);
});
}
return $this;
});
}
}
Attribute.php. This can be left unchanged, lets just change the namespace
/app/RestFilters/Helpers/Attribute.php
<?php
namespace App\RestFilters\Helpers;
class Attribute
{
public static function sobstitute($attribute)
{
$hashMap = [
'gt' => '>',
'gte' => '>=',
'lt' => '<',
'lte' => '<=',
'like' => 'like',
'ilike' => 'ilike'
];
return array_key_exists($attribute, $hashMap) ?
$hashMap[$attribute] :
false;
}
}
To make it work, you can now add the $fieldMapping
property to your model
// App/Models/TrafficViolation.php
class TrafficViolation extends Model
{
public array $fieldMapping = [
'location' => 'location_relationship_id'
];
}
And don't forget to register your new ServiceProvider in app.php
// config/app.php
$providers = [
...
\App\Providers\RestServiceProvider::class,
]
This should allow you to now define mappings on each of your models
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论