3

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.

John Slegers
  • 45,213
  • 22
  • 199
  • 169
braindamage
  • 2,226
  • 4
  • 24
  • 33

2 Answers2

3

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.

Jeff Lambert
  • 24,395
  • 4
  • 69
  • 96
  • I ended it up making an array of parameters on the constructor since I have a lot of complex relationships (unfortunately losing the type hints). Looks ugly but it works! And I don't pass the entity but the ID since I might need, for example, the pivot value for a single item on a Many to Many relationship. – braindamage Sep 22 '15 at 13:14
2

I know this is older, but I just ran into this issue. Here is how I resolved it.

Added the withPivot to the relationships, in my case category and users.

In the CategoryTransformer, I defined my includeUsers method:

/**
 * Include Users
 * @param Category $category
 * @return \League\Fractal\Resource\Collection
 */
 public function includeUsers(Category $category)
 {
     # Just Add withPivot Here.
     $users = $category->users()->withPivot('role')->get();
     return $this->collection($users, new UserTransformer);
 }

Then in your UserTransformer class, on the transform() method:

public function transform($user)
{
    return [
        'username' => $user['username'],
        'lastname' => $user['lastname'],
        // ... truncated
        'role' => isset($user['pivot']) ? $user['pivot']['role'] : null,
    ]
}

Then when I call my api for categories and include users I get:

{
  "data": [
    {
      "name": "Network",
      "description": "some description of category here.",
      "users": {
        "data": [
          {
            "id": 1,
            "username": "jborne",
            "firstname": "Jason",
            "lastname": "Borne",
            "title": "Department Head",
            "company": "Borne Inc",
            "email": "jason@somedomain.com",
            "display_name": "Jason Borne",
            "mobile": "555-5555",
            "active": true,
            "role": "IM",
          }
        ]
      }
    }
  ]
}

As you see, I get the role relationship I wanted from the pivot. Otherwise you just get null for that field. Still not ideal, but much less messy in my opinion.

Matthew Brown
  • 4,876
  • 3
  • 18
  • 21