Laravel REST API:如何将数据库列名映射到参数的另一个名称?

huangapple go评论54阅读模式
英文:

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

抱歉,你的插件默认不支持这个功能。好消息是插件本身非常简单(只有两个类),所以现在我们放弃使用它,尝试在你的应用程序中自己实现。

有两个类,AttributeRestServiceProvider

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, &#39;fieldMapping&#39;)) {
    $field = $model-&gt;fieldMapping[$field] ?? $field;
}

So
app/Providers/RestServiceProvider.php

&lt;?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(&#39;withRestFilters&#39;, function () {
            // Model Instance
            $model = $this-&gt;getModel();
            // Filtering
            $query = request()-&gt;query-&gt;all();

            // Needs to exclude key items like &quot;sort&quot;
            collect($query)-&gt;except([&#39;sort&#39;, &#39;fields&#39;, &#39;embed&#39;, &#39;page&#39;])
                -&gt;except($model-&gt;bannedFields ?: [])
                -&gt;each(function ($value, $field) use ($model) {

                    // This is where we will map field names
                    if (property_exists($model, &#39;fieldMapping&#39;)) {
                        $field = $model-&gt;fieldMapping[$field] ?? $field;
                    }

                    // Check has Multiple Filters
                    if (Str::contains($value, &#39;,&#39;)) {
                        $this-&gt;whereIn($field, Str::of($value)-&gt;explode(&#39;,&#39;));
                    } else {
                        // Empty value. Skip.
                        if (empty($value)) {
                            return true;
                        }

                        // Check has an extra attribute
                        if (Str::contains($value, &#39;:&#39;)) {
                            $attributeAndValue = Str::of($value)-&gt;explode(&#39;:&#39;);

                            // Check Banned Attributes
                            if ($model-&gt;bannedAttributes &amp;&amp; in_array($attributeAndValue[0], $model-&gt;bannedAttributes)) {
                                return true;
                            }

                            // Replace Attribute
                            $replacedAttribute = Attribute::sobstitute($attributeAndValue[0]);

                            // If not exists returns false, so not considered.
                            if (!$replacedAttribute) {
                                return true;
                            }

                            $this-&gt;where($field, $replacedAttribute, $attributeAndValue[1]);
                        } else {
                            $this-&gt;where($field, &#39;=&#39;, $value);
                        }
                    }
                });

            // Selecting Fields
            if (request()-&gt;filled(&#39;fields&#39;)) {
                $fields = Str::of(request()-&gt;fields)-&gt;explode(&#39;,&#39;);

                $fields-&gt;each(function ($field) {
                    $this-&gt;addSelect($field);
                });
            }

            // Sorting
            if (request()-&gt;filled(&#39;sort&#39;)) {
                $fields = Str::of(request()-&gt;sort)-&gt;explode(&#39;,&#39;);

                $fields-&gt;each(function ($field) {
                    $sortDirection = Str::startsWith($field, &#39;-&#39;) ? &#39;DESC&#39; : &#39;ASC&#39;;

                    $this-&gt;orderBy(Str::replaceFirst(&#39;-&#39;, &#39;&#39;, $field), $sortDirection);
                });
            }

            // Embedding (Needs to add id field if filtered.)
            if (request()-&gt;filled(&#39;embed&#39;)) {
                $fields = Str::of(request()-&gt;embed)-&gt;explode(&#39;,&#39;);

                $fields-&gt;each(function ($field) {
                    $this-&gt;with($field);
                });
            }

            return $this;
        });
    }
}

Attribute.php. This can be left unchanged, lets just change the namespace
/app/RestFilters/Helpers/Attribute.php

&lt;?php

namespace App\RestFilters\Helpers;

class Attribute 
{
    public static function sobstitute($attribute)
    {
        $hashMap = [
            &#39;gt&#39; =&gt; &#39;&gt;&#39;,
            &#39;gte&#39; =&gt; &#39;&gt;=&#39;,
            &#39;lt&#39; =&gt; &#39;&lt;&#39;,
            &#39;lte&#39; =&gt; &#39;&lt;=&#39;,
            &#39;like&#39; =&gt; &#39;like&#39;,
            &#39;ilike&#39; =&gt; &#39;ilike&#39;
        ];

        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 = [
        &#39;location&#39; =&gt; &#39;location_relationship_id&#39;
    ];
}

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

huangapple
  • 本文由 发表于 2023年5月21日 18:11:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76299351.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定