1

I'm building a client application that needs to have the ids of related models in my server API response.

In my example I've got two models, a Post and a Tag model. The relationship between them is many-to-many, so a pivot table is required.

class Post extends Eloquent {

    protected $fillable = [ 'title', 'body' ];

    public function tags()
    {
        return $this->hasMany('Tag');
    }

}

class Tag extends Eloquent {

    protected $fillable = [ 'title' ];

    public function posts()
    {
        return $this->belongsToMany('Post');
    }

}

I've got a resourcefull controller set up on the /api/posts route like this:

class PostsController extends \BaseController {

    public function index()
    {
        $posts = Post::all();

        return Response::json([ 'posts' => $posts->toArray() ]);
    }

}

This will return a response much like this one:

{
    "posts": [
        {
            "title": "Laravel is awesome",
            "body": "Lorem Ipsum..."
        },
        {
            "title": "Did I mention how awesome Laravel is?",
            "body": "Lorem Ipsum..."
        }
    ]
}

What I'm looking for is an easy way to include the ids of the related Tags model in the response like this:

{
    "posts": [
        {
            "title": "Laravel is awesome",
            "body": "Lorem Ipsum...",
            "tags": [ 1, 2, 3 ]
        },
        {
            "title": "Did I mention how awesome Laravel is?",
            "body": "Lorem Ipsum...",
            "tags": [ 1, 2, 4 ]
        }
    ]
}
Jon Koops
  • 8,801
  • 6
  • 29
  • 51

4 Answers4

2

This isn't the most elegant solution, but it may work like you want (code not tested)

public function index()
{
    $posts = Post::all();

    $postsArray = array();

    foreach ($posts as $post)
    {
        $postArray = $post->toArray();

        $postArray['tags'] = array_values($post->tags->lists('id'));

        $postsArray[] = $postArray; 
    }

    return Response::json([ 'posts' => $postsArray]);
}
hayhorse
  • 2,652
  • 1
  • 10
  • 7
1

Add the following code to your Model/BaseModel:

/**
 * Set additional attributes as hidden on the current Model
 *
 * @return instanceof Model
 */
public function addHidden($attribute)
{
    $hidden = $this->getHidden();

    array_push($hidden, $attribute);

    $this->setHidden($hidden);

    // Make method chainable
    return $this;
}

/**
 * Convert appended collections into a list of attributes
 *
 * @param  object       $data       Model OR Collection
 * @param  string|array $levels     Levels to iterate over
 * @param  string       $attribute  The attribute we want to get listified
 * @param  boolean      $hideOrigin Hide the original relationship data from the result set
 * @return Model
 */
public function listAttributes($data, $levels, $attribute = 'id', $hideOrigin = true)
{

    // Set some defaults on first call of this function (because this function is recursive)
    if (! is_array($levels))
        $levels = explode('.', $levels);

    if ($data instanceof Illuminate\Database\Eloquent\Collection) // Collection of Model objects
    {
        // We are dealing with an array here, so iterate over its contents and use recursion to look deeper:
        foreach ($data as $row)
        {
            $this->listAttributes($row, $levels, $attribute, $hideOrigin);
        }
    }
    else
    {
        // Fetch the name of the current level we are looking at
        $curLevel = array_shift($levels);

        if (is_object($data->{$curLevel}))
        {
            if (! empty($levels))
            {
                // We are traversing the right section, but are not at the level of the list yet... Let's use recursion to look deeper:
                $this->listAttributes($data->{$curLevel}, $levels, $attribute, $hideOrigin);
            }
            else
            {
                // Hide the appended collection itself from the result set, if the user didn't request it
                if ($hideOrigin)
                    $data->addHidden($curLevel);

                // Convert Collection to Eloquent lists()
                if (is_array($attribute)) // Use specific attributes as key and value
                    $data->{$curLevel . '_' . $attribute[0]} = $data->{$curLevel}->lists($attribute[0], $attribute[1]);
                else // Use specific attribute as value (= numeric keys)
                    $data->{$curLevel . '_' . $attribute} = $data->{$curLevel}->lists($attribute);
            }
        }
    }

    return $data;
}

You can use it on your Model/Collection Object like this:

// Fetch posts data
$data = Post::with('tags')->get(); // or use ->first()

// Convert relationship data to list of id's
$data->listAttributes($data, 'tags');

$data will now contain the following object store:

{
    "posts": [
        {
            "title": "Laravel is awesome",
            "body": "Lorem Ipsum...",
            "tags_id": [ 1, 2, 3 ]
        },
        {
            "title": "Did I mention how awesome Laravel is?",
            "body": "Lorem Ipsum...",
            "tags_id": [ 1, 2, 4 ]
        }
    ]
}

It also supports nested relationships:

// Fetch posts data
$data = Post::with('comments', 'comments.tags')->get(); // or use ->first()

// Convert relationship data to list of id's
$data->listAttributes($data, 'comments.tags');
Ronald Hulshof
  • 1,986
  • 16
  • 22
1

Eloquent relationship returns a collection object which you can filter or even modify, say if you need only an array of the id's you can do this:

$posts->pluck('id');

$posts->pluck('id')->toArray();
astroanu
  • 3,901
  • 2
  • 36
  • 50
0

I think you need to use the $appends property. Take a look at the docs here http://laravel.com/docs/eloquent#converting-to-arrays-or-json.

This question also is relevant as he encountered the same issue as you (see the edit on the accepted answer).

Add a custom attribute to a Laravel / Eloquent model on load?

Community
  • 1
  • 1
SamV
  • 7,548
  • 4
  • 39
  • 50