3

I'm about to ...

  1. extend my App/Orm/MyModel.php with Http/Json/V1/MyModel.php so I can keep the $appends, $hides, toArray() neatly tucked away in a V1
  2. namespace and prefix some routing for V1
  3. probably do some custom resolvers for route model binding

And I'm thinking ... really? They haven't built this in... what am I missing here? There's gotta be a quick, turn-key for this. I'm interested in knowing how other people are doing this, so please chime in.

6
  • Are you in the need of v2 already? Most likely you won't pass by v1, so just stick with non-versioned API and when times come you can refactor. Commented Nov 21, 2018 at 16:13
  • @Kyslik just planning ahead. Commented Nov 21, 2018 at 16:31
  • 1
    Wasting your time, imagine you have v1 and its doing fine; now you decide to develop v2; whats the process? You need to copy "all the files" and change the namespace and some more stuff above (middleware / service providers / routes) and now you can start working with v2. Can't you do that later when its actually needed? v1 will have no special namespace and v2 will have thats it. Commented Nov 21, 2018 at 16:34
  • @Kyslik This is just for output versioning. Commented Nov 21, 2018 at 16:44
  • 1
    @Kyslik That would be epically hilarious if you actually checked in next year - lol. And epically sad if I find myself still sitting in this same spot tapping away at a keyboard. Commented Nov 21, 2018 at 16:54

1 Answer 1

4

Try Resources instead of Models

Have a look at resources: https://laravel.com/docs/5.7/eloquent-resources

And add your logic to resources so that you display different versions of a model depending on the API version. You can still make use of $appends and $hidden.

With this approach we return a Resource of a model rather than the model itself.

Here is an example of a UserResource for different API versions:

class UserResource extends JsonResource
{
    private $apiVersion;

    public function __construct($resource, int $apiVersion = 2) {
        $this->apiVersion = $apiVersion; // OPTION 1: Pass API version in the constructor
        parent::__construct($resource);
    }

    public function toArray($request): array
    {
        // OPTION 2: Get API version in the request (ideally header)
        // $apiVersion = $request->header('x-api-version', 2);

        /** @var User $user */
        $user = $this->resource;

        return [
            'type' => 'user',
            'id' => $user->id,
            $this->mergeWhen($this->apiVersion < 2, [
                'name' => "{$user->first_name} {$user->last_name}",
            ], [
                'name' => [
                    'first' => $user->first_name,
                    'last' => $user->last_name
                ],
            ]),
            'score' => $user->score,
        ];
    }
}

The you can call:

$user = User::find(5);
return new UserResource($user);

If you need a different connection you can do:

$user = User::on('second_db_connection')->find(5);

So V1 API gets:

{
    id: 5,
    name: "John Smith",
    score: 5
}

and V2 API gets:

{
    id: 5,
    name: {
         first: "John",
         last: "Smith",
    },
    score: 5
}

Now if later you wanted to rename score to points in your DB, and in V3 of your API you also wanted to change your JSON output, but maintain backwards compatibility you can do:

   $this->mergeWhen($this->apiVersion < 3, [
       'score' => $user->points,    
   ], [
       'points' => $user->points,
   ])

Prefix routes

You can easily prefix routes as mentioned here: https://laravel.com/docs/5.7/routing#route-group-prefixes

Route::prefix('v1')->group(function () {
    Route::get('users', function () {
        // ...
    });
});

Explicit Route Model Binding

To do custom route model bindings have a look at: https://laravel.com/docs/5.7/routing#route-model-binding

e.g.

Route::bind('user', function ($value) {
    return App\User::where('name', $value)->first() ?? abort(404); // your customer logic
});
Sign up to request clarification or add additional context in comments.

22 Comments

Eloquent resource is on the right track, thanks. Looks pretty limited, but maybe sometimes limits are a good thing. Cheers!
@TarekAdam Please mark this answer as correct if it solved your problem
Still looking into the issue.
Took another look, but I'm just not seeing the benefit ~ other than ... it's light. On the drawback side, I can't set a connection - and that's a deal breaker since the Json output namespaced models will be read from slaves.
Got it. You can specify different connections for read/ write by editing your database config file as mentioned here: laravel.com/docs/5.7/database#read-and-write-connections
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.