braindamage braindamage - 1 month ago 18
PHP Question

Laravel 5.1 and Fractal: including pivot table data on the transformer

Tables:

contact
,
company
and a relationship table with a custom pivot attribute
company_contact (company_id, contact_id, is_main)


Company and Contact have a many to many relationship (
belongsTo
on both models).

Expected output when I retrieve the contacts of a company:

{
"data": [
{
"id": 1,
"name": "JohnDoe",
"is_main": false
},
{
"id": 2,
"name": "JaneDoe",
"is_main": true
}
]
}


Expected output when I retrieve the contact list with
?include=companies
:

{
"data": [
{
"id": 1,
"name": "John Doe",
"companies": {
"data": [
{
"id": 501,
"name": "My Company",
"is_main": true
},
{
"id": 745,
"name": "Another Company",
"is_main": false
}
]
}
},
{
"id": 2,
"name": "Jane Doe",
"companies": {
"data": [
{
"id": 999,
"name": "Some Company",
"is_main": true
}
]
}
}
]
}


What's the best way of adding the pivot table attribute? It doesn't seem very clean to add
is_main
on the company transformer if the attribute is set.

For the first example I was thinking about using parameters
?include=company_relationship:company_id(1)
with something like:

public function includeCompanyRelationship(Contact $contact, ParamBag $params) {
// .. retrieve the pivot table data here
$is_main = $company->is_main;

// but now I would need another transformer, when what I actually want is to push the value on the main array (same level)

return $this->item(??, ??);
}


I understand how to retrieve pivot data (related: Laravel 5.1 - pivot table between three tables, better option?) but not the best way of adding it in the https://github.com/thephpleague/fractal Transformer logic.

I already have a ContactTransformer and CompanyTransformer but if I add
is_main
to the CompanyTransformer all the calls I make (related or not to contacts) will also expect that attribute.

Answer

If I'm reading you correctly, you can utilize a single CompanyTransformer to handle whether you wish to have the is_main property set, but only if a $contact parameter is passed in to it's constructor, something along these lines:

class CompanyTransformer extends TransformerAbstract
{
    public function __construct(Contact $contact = null)
    {
        $this->contact = $contact;
    }

    public function transform(Company $company)
    {
        $output = [
            'id' => $company->id,
            'name' => $company->name,
        ];

        if($this->contact) {
            // This step may not be necessary, but I don't think the pivot data 
            // will be available on the $company object passed in
            $company = $this->contacts->find($company->id);
            // You may have to cast this to boolean if that is important
            $output['is_main'] = $company->pivot->is_main;
        }

        return $output;
    }
}

Then in your includeCompanyRelationship just pass in the new CompanyTransformer with the parameter:

public function includeCompanyRelationship(Contact $contact) 
{
    $companies = $contact->companies;

    return $this->collection($companies, new CompanyTransformer($contact));
}

This should work whether you're calling your companies endpoint directly, or calling the contact's endpoint while embedding the company relationship data.