nowox
nowox

Reputation: 29086

Simplify API boilerplate in Laravel's controllers?

When I write APIs with Laravel, I often use the same method as GitHub API v3. I add URLs to ease the navigation during development and also for the users that will develop using that API.

In this example, I manually add the URLs on every field then add a count for the pagers on the frontend. I sometime have more complicated stuff if I want to add the necessary to filter the results (if used with Vuetify or Kendo Grid).

class UserController extends Controller
{
    function index() {
        $users = User::all()->each(function ($item, $key) {
            $item['activities'] = url("/api/users/{$item['id']}/activities");
            $item['classrooms'] = url("/api/users/{$item['id']}/classrooms");
        });
        return [
            'count' => count($users),
            'courses' => $users,
        ];
    }
}

Is there a way to make this code less boilerplate? Is there a package that does everything out of the box?

Upvotes: 1

Views: 153

Answers (1)

mrhn
mrhn

Reputation: 18916

I'm a big fan of fractal especially spaties fractal package. This enables you to transform objects into responses.

There are two concepts in fractal serializers and transformers. Serializers is about the whole request, meta information data etc. Transformer is how you transform each model or object. Normally you would not make custom serializers, but in your case in can solve most of your trouble.

use League\Fractal\Serializer\ArraySerializer;

class YourSerializer extends ArraySerializer {
    public function collection($resourceKey, array $data)
    {
        return [
            $resourceKey => $data,
            'count' => count($data),
        ];
    }
}

Create your transformer for your user. The other big thing you gain for using this, is you have one plays to be responsible for transformation. Instead of each controller having to have the logic, which will be spread out and you have to remember to include it.

use League\Fractal\TransformerAbstract;

class AccountBalanceTransformer extends TransformerAbstract
{
    public function transform(User $user)
    {
        return [
            'id' => $user->id,
            'name' => $user->id,
            // other user fields
            'activities' => url("/api/users/{$user->id}/activities"),
            'classrooms' => url("/api/users/{$user->id}/classrooms"),
        ];
    }
}

You have to assign the serializer in your fractal.php config.

'default_serializer' => YourSerializer::class,

Now you should be able to transform you responses like so.

return fractal($users, new UserTransformer())
    ->withResourceName('courses')
    ->respond(Response::HTTP_OK);

For making it easier to use and avoid repeating your self, i usually do a method on a parent controller like so. While setting the transformer on the object.

public function respond($data, $resourceKey, $status = Response::HTTP_OK) {
    return fractal($data, $this->transformer)
        ->withResourceName($resourceKey)
        ->respond($status);
}

This will produce a response similar to what was specified in the question.

Upvotes: 1

Related Questions