1

I have a User, Role & Page setup, all with many-to-many relationships and the pivot tables setup in the usual fashion (role_user, page_role), along with the eloquent methods to attach a model to the pivot tables.

My idea is to allow a user to have many roles, and a page can be accessed by many roles.

However now I'd like to return a collection where I have my users details and then the pages they're allowed to access.

The closest I have got is:

return User::find( Auth::user()->id )->with('roles.pages')->first()->roles;

Now this returns each role the user has, and each page that the role can access. Which is correct, however I have duplication on the pages part.

How would I go about getting only a list of pages the user is able to access with no duplication?

Cheers

Alias
  • 2,983
  • 7
  • 39
  • 62

1 Answers1

2

Read that answer to get you on the track: HasManyThrough with one-to-many relationship

Only for your setup you need to adjust the query - join 2 pivot tables (and make sure they represent real data, ie no rows referencing non-existing models):

// User model

// accessor so you can access it like any relation: $user->pages;
public function getPagesAttribute()
{
    if ( ! array_key_exists('pages', $this->relations)) $this->loadPages();

    return $this->getRelation('pages');
}

// here you load the pages and set the collection as a relation
protected function loadPages()
{
    $pages = Page::join('page_role as pr', 'pr.page_id', '=', 'pages.id')
           ->join('role_user as ru', 'ru.role_id', '=', 'pr.role_id')
           ->where('ru.user_id', $this->id)
           ->distinct()
           ->get(['pages.*', 'user_id']);

    $hasMany = new Illuminate\Database\Eloquent\Relations\HasMany(Page::query(), $this, 'user_id', 'id');

    $hasMany->matchMany(array($this), $pages, 'pages');

    return $this;
}

One more thing: I hardcoded tables and columns names for sake of simplicity, but in real life I suggest you rely on the relationships and their getters, like: $relation->getTable(), $relation->getForeignKey() etc.


Now suggestion about your code:

return User::find( // 2. query to get the same user
     Auth::user()->id  // 1. query to get the user and his id
   )->with('roles.pages')
     ->first() // 3. query to get ANOTHER user (or the same, luckily..)
     ->roles;
  1. Use Auth::id() instead of Auth::user()->id (for Laravel ver 4.1.25+) to avoid redundant query
  2. find() and first() are methods that execute the query, so you just returned the user with id = Auth::user()->id and moment later you fetch another one, who comes first() from the users table..
  3. You don't need to use User::whatever for authenticated user, use Auth::user() instead.

So the code with suggested solution would look like this:

Auth::user()->pages; // collection of Page models with unique entries
Community
  • 1
  • 1
Jarek Tkaczyk
  • 78,987
  • 25
  • 159
  • 157
  • Awesome cheers for the heads up, works great :) EDIT: Is it correct to load the `Page` model from inside the `User` model directly? – Alias Jun 27 '14 at 22:44
  • What do you mean by correct? Isn't it what you needed :) It is just adjusting what framework offers to match your needs. – Jarek Tkaczyk Jun 28 '14 at 06:29
  • Would it be possible to show me the above how it's properly supposed to be implemented? Also if the user isn't logged in I need to check if there is any pages which have the role "guest". Logic is working in this paste, but doesn't seem the "best" way of doing it... http://laravel.io/bin/QqlnD – Alias Jun 28 '14 at 18:26
  • Check the edit, you have everything concerning `User` model there. And here's the paste for you http://laravel.io/bin/mlqzq – Jarek Tkaczyk Jun 28 '14 at 22:20
  • Ahhh I see! Another thing though, surely "User" will never have the relation with pages, so it will always execute the "loadPages()" method? – Alias Jun 29 '14 at 09:22
  • Not at all. Check the accessor, last action there: `matchMany` is going to set the relation on `$this`. This is how the relations are attached to the model. You can always play with this and see what queries are run etc. with `artisan tinker` in cli. It is great source to learn how Eloquent works. – Jarek Tkaczyk Jun 29 '14 at 10:57
  • Hey @deczo, any idea how i'd go about getting specific coulmns from the pages table. For example just the name and ID rather than everything (including the timestamps etc) – Alias Jul 09 '14 at 17:50
  • I suppose you want to output only certain fields to the user, so check model's `hidden`/`visible` properties. Otherwise simply use `select` on the query – Jarek Tkaczyk Jul 09 '14 at 20:41