Question

In Laravel 4 I use Eager Loading for a ManyToMany relationship:

public function categories()
{

    return $this->belongsToMany('Category');

}

It returns categories like this:

    "categories": [
        {
            "id": 1,
            "priority": 1,
            "title": "My category 1",
            "created_at": "2013-08-10 18:45:08",
            "updated_at": "2013-08-10 18:45:08"
        },
        {
            "id": 2,
            "priority": 2,
            "title": "My category 2",
            "created_at": "2013-08-10 18:45:08",
            "updated_at": "2013-08-10 18:45:08"
        }
    ],

But all I need is this:

    "categories": [1,2] // References category id's only

The Query Builder has a method called "lists" which should do the trick. But it's not working in case of an Eager Load???

public function categories()
{

    return $this->belongsToMany('Category')->lists('category_id');

}
Was it helpful?

Solution 2

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');

OTHER TIPS

The reason it doesn't work is because when eager-loading it, using the with method, Laravel expects a relationship method to return a Illuminate\Database\Eloquent\Relations\Relation object, so that it can call get on it. When you call lists, the query is already ran and what is returned instead is an array.

What you could do, to reduce the data transferring, is use the select method on the query, and then run a lists on the categories Collection. Example:

Model.php

function categories() {
    return $this->belongsToMany('Category')->select('id');
}

Whatever.php

$posts = Post::with('Category')->get();
$categories = $posts->categories;

// List only the ids
$categoriesIds = $categories->lists('id');

If this is the case for all requests to category model, you can set the visible array

like protected $visible = array('category_id');

now every request to category model will retrieve category_id only

In your case-

Class Category extends Eloquent{

    protected $visible=array('category_id');
    ...
}

Note- It will return collection of category_id's as an object but if you need an array you have to use toArray() method of query builder to get an array of category_id

And to get exactly what you need, you can try this

$cat_id=Category::all()->toArray();
$arrid=array();
array_walk_recursive($cat_id,function($value,$key) use (&$arrid){
  array_push($arrid,$value);
})
//$arrid will contain only category_id's like
//$arrid=[1,2,3];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top