Modify Laravel model response to use different ID

1.6k Views Asked by At

I'm looking to sort of "intercept" and change a field in a model before it's send back to the client. I have an API with endpoints similar to the following:

Route::get('users/{user}', 'Api\UserController@user');

My application uses vinkla/laravel-hashids, so as far as the client application is concerned, the ID to query for should be something like K6LJwKJ1M8, and not 1. Currently I can query for a user providing the hash-id, but the response comes back with the numeric/auto-incrementing ID.

e.g. For a query such as /api/users/K6LJwKJ1M8 I receive the following response:

{
    "id": 1,
    "address": null,
    "telephone": null,
    "name": "TestNameHere",
    "description": null,
    ...
}

Is there a nice way in Laravel that I could modify all user objects being returned in responses to use the vinkla/laravel-hashids ID, instead of the auto-incrementing ID?

Ideally, the above response would become:

{
    "id": K6LJwKJ1M8,
    "address": null,
    "telephone": null,
    "name": "TestNameHere",
    "description": null,
    ...
}

I was thinking something like using getRouteKey would work, but it doesn't change the object that's sent out in the JSON response.

e.g.

public function getRouteKey() {
    return Hashids::encode($this->attributes['id']);
}

It'd be nice if there was one place to change this since my application has around 40 different routes that I'd otherwise need to change "manually" (e.g. before sending the response do something like $user->id = Hashids::encode($id))

3

There are 3 best solutions below

0
On BEST ANSWER

You can use API Resources https://laravel.com/docs/7.x/eloquent-resources#introduction

API Resource acts as a transformation layer that sits between your Eloquent models and the JSON responses that are actually returned to your application's users.

You may create an API resource for the user and use it wherever you're returning the user in the response.

Api resources gives you a lot more control, you could manipulate whatever field you want, send some extra fields using the combination of a few fields, change the name of the fields that you want in your response (xyz => $this->name)

UserResource

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        //You can access model properties directly using $this
        return [
            'id' => Hashids::encode($this->id),
            "address": $this->address,
            "telephone": $this->telephone,
            "name": $this->name,
            "description": $this->description,
             ...
        ];
    }
}

And then wherever you want to return a user instance.

Controller

// $user is a User Model Instance.

return new UserResource($user); 

In case you have a collection

// $users is a collection of User Model instances.

return UserResource::collection($users);

UserResource will be applied for every model instance in your collection and then returned to you as your JSON response.

0
On

You can implement the CastsAttributes interface:

Classes that implement this interface must define a get and set method. The get method is responsible for transforming a raw value from the database into a cast value, while the set method should transform a cast value into a raw value that can be stored in the database.

Inside your app directory create a directory named Casts and create a class named UserCode:

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use ...\Hashids;

class UserCode implements CastsAttributes
{
    public function get($model, $key, $value, $attributes)
    {
        return Hashids::encode($value);
    }

    public function set($model, $key, $value, $attributes)
    {
        return Hashids::decode($value);
    }
}

Then in your user model add the UserCode cast to the id value:

use App\Casts\UserCode; // <--- make sure to import the class
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $casts = [
        'id' => UserCode::class,
    ];
}

Then if you would do User::find(1)->id you will get the hashed value or visit /user/1 it will return the user with the hashed id. credit.

Note that you can't find the user by the hashed id unless you implemented something e.g. /user/hashed_id will give 404.

0
On

To achieve what you want, in your model, you would have to use set getIncrementing to false and ensure getKeyType is set to string.

class YourModel extends Model
{
    public function getIncrementing()
    {
        return false;
    }

    public function getKeyType()
    {
        return 'string';
    }
}

The above would work if you were using uuid and have the following in your migration file:

$table->uuid('id')->primary();

You would probably have to find a way to modify the package to use this approach.