2
votes

We are doing a learning exercise with Laravel 5.8 and Eloquent for developing a version of a REST api.

The database tables (company, contact & note) are rather simple in terms of their relationships:

company -> hasMany -> contacts
company -> hasOne -> note

The Model code looks like:

class Company extends Model
{
    // ....

    public function contacts()
    {
        return $this->hasMany(Contact::class);
    }

    public function note()
    {
        return $this->hasOne(Note::class);
    }
}

To access each of these resources, the api is:

/api/companies
/api/notes
/api/contacts

We are wanting to return links to the foreign resources when /api/companies is hit. e.g.:

{
    "data": [
        {
            "id": 1,
            "trading_name": "Example Company",
            "_links": {
                "contacts": [
                    "http://localhost:8100/api/contacts/1",
                    "http://localhost:8100/api/contacts/2",
                    "http://localhost:8100/api/contacts/3"
                ],
                "notes": "http://localhost:8100/api/notes/1"
            }
        }
    ]
}

We are using Laravel's JsonResource to format the response object.

The question is around the _links. For the links we use the following code:

$contacts = $this->contacts->map(
    function($contact) {
        return route('contacts.show', [ 'contact' => $contact->id ]);
    }
);

$links['contacts'] = $contacts;
$note = $this->note;
if ($note != null) {
    $links['note'] = route('notes.show', [ 'note' => $note->id ]);
}
$links['self'] = route('companies.show', [ 'company' => $this->id ]);

We only get the id field to generate the _links array.

Is the above the way these links are generated in Laravel/Eloquent?

2
What you're doing is fine. There is no specific way you have to generate those links in Laravel or Eloquent. If it works, stick with it.Aken Roberts

2 Answers

2
votes

There is no "right way" to do this, but for me what you're doing is good.

Nevertheless, there is a specification called jsonapi that describes how to share links.

There is a Laravel package for this.

1
votes

You described the most reasonable way to generate the links, although your code can be slightly optimized by removing some unnecessary assignments:

$links['contacts'] = $this->contacts->map(function ($contact) {
    return route('contacts.show', [ 'contact' => $contact->id ]);
});

if ($this->note !== null) {
    $links['note'] = route('notes.show', [ 'note' => $this->note->id ]);
}

$links['self'] = route('companies.show', [ 'company' => $this->id ]);

If, however, the id is everything you need of your contacts and note, you can only load this information from your database. Be careful to also load foreign keys required to map these foreign objects back to their parent though:

$company = Company::query()
    ->with([
        'contacts' => function ($query) {
            $query->select(['id', 'company_id']);
        },
        'note' => function ($query) {
            $query->select(['id', 'company_id']);
        },
    ])
    ->find($companyId);

An absolutely dirty way, and I'm not even sure it is much more efficient, would be to build a route template and replace the id part directly in SQL. But I don't recommend it and I only write it for the sake of demonstration:

$companyRoute = route('companies.show', ['company' => 999999]);
$contactRoute = route('contacts.show', ['contact' => 999999]);
$noteRoute    = route('notes.show', ['note' => 999999]);

$company = Company::query()
    ->with([
        'contacts' => function ($query) {
            $query->select(['id', 'company_id', \DB::raw("REPLACE(?, '999999', id) as link")])
                ->addBinding($contactRoute, 'select');
        },
        'note' => function ($query) {
            $query->select(['id', 'company_id', \DB::raw("REPLACE(?, '999999', id) as link")])
                ->addBinding($noteRoute, 'select');
        },
    ])
    ->select([
        '*',
        \DB::raw("REPLACE(?, '999999', id) as link"),
    ])
    ->addBinding($companyRoute, 'select')
    ->find($companyId);

The last snippet is untested and NOT meant to be used.