Selecting a value from an n:n relationship - mysql

I currently have three tables: users, roles, and a user_to_role “pivot” table defining a many-to-many relationship between users and roles:
users
protected $fillable = [
'name', 'email', 'password',
];
user_to_role
protected $fillable = [
'id', 'user_id', 'role_id'
];
roles
protected $fillable = [
'id', 'role_name',
];
The role_name values are admin and client.
When a user logs in, I want to show a view for the specific role that the user is assigned. I don't really know how to do that in the controller, however. I have something like the following, but I know it won’t work:
public function index()
{
if (Auth::user()->role_id==1) {
// and something here which I don't know
return view('homeadmin');
}
}
I know I have to take the id from the roles table, make the connection with the user_to_role pivot, and then join that with the users table, but I don't really know how.

You need to define a relationship between User model and Role model.
# User.php
public function roles()
{
return $this->belongsToMany(Role::class, 'user_to_role');
}
Optionally, define the relationship on Role model as well.
# Role.php
public function users()
{
return $this->belongsToMany(User::class, 'user_to_role');
}
Then, you can access the relationship and use collection methods on it.
public function index()
{
// or Auth::user()->roles->contains('role_name', 'admin') if you want to be specific
if (Auth::user()->roles->contains('id', 1)) {
return view('homeadmin');
}
return view('homeuser');
}
Optionally, you could make a method in the User model to check if an user is admin or client.
# User.php
public function isAdmin()
{
return $this->roles->contains('id', 1); // or contains('role_name', 'admin')
}
public function isClient()
{
return $this->roles->contains('id', 2); // or contains('role_name', 'client')
}
public function index()
{
if (Auth::user()->isAdmin()) {
return view('homeadmin');
}
return view('homeclient');
}
Eloquent Relationships - Many to Many
Collections - contains() method

First of all, if you have User and Role Model mapping to your users and roles table, the convention is to name your pivot table role_user. But you can get along with your current table naming as well.
I would agree the answer of IGP and add a few more suggestions.
If you just need to implement role and user and don't have to build it yourself, there are plenty of existing packages that can help you handle role-permission. You don't really needs to build from scratch. For example, depends on the Laravel version you use, you may choose;
spatie/laravel-permission
Bouncer
Zizaco/Entrust
If you would like to implement role management yourself, when you define your relationship, you need to think about if a user would have multiple roles in the future. Based on what you show us right now, there are only client and admin role. Looks like a user would only be either client or admin but not both. And if you are sure those are the only two roles and a user would be either one, you don't need to have roles table at all. You can just add a boolean column such as is_admin in users table to flag the role.
But let's say you will have more roles, and a user can have multiple roles at the same time. Then you DO need to define a many to many relationship. Other answers already provide example on that pretty well. I would also suggest to define a universal role-handling model function to check all roles. In your User.php model,
public function hasRole($role)
{
// check if user have any of the specified roles
if (is_array($role)) {
foreach($role as $r) {
if ($this->roles->contains('role_name', $r)) {
return true;
}
}
return false;
} else {
return $this->roles->contains('role_name', $role);
}
}
That way, in anywhere in your App, you can check your user role by calling
Auth::user()->hasRole('admin');
or check if user contains any role in a list by calling
Auth::user()->hasRole(['admin', 'client']);

Related

Optimization of Laravel pivot table relationship

I have a pivot table called invite_riskarea which is designed as follows:
This table handles the permissions that have a specific user (through an invite id) to access to specific riskfields. Each riskfield is associated with a riskarea which acts as the main container of specific riskfields.
Within the model Invite I have this relationship:
public function riskareas()
{
return $this->belongsToMany(Riskarea::class)->withPivot('riskfield_id', 'insert', 'edit', 'view');
}
In this way I can return all the riskareas associated with a specific invite, and I should be able to return all the riskfields associated with a specific riskarea in the same invite model.
As you can see from the table invite_riskarea, I have three columns called insert, edit, and delete. These columns manage the types of permissions assigned to a specific user (via an invite id) for a specific riskfield belonging to a riskarea.
I'm trying to retrieve the riskarea permission in the following way:
$invite = Invite::where('id', 58)->first();
$riskarea = $invite->riskareas[0];
$riskfield = $riskareas->riskfields[0];
echo 'view permission => ' . $riskfield->insert;
The problem's that I'm not able to setup a correct relationship in the Invite model that returns me the pivot data of the permissions columns only for the riskfield associated with the riskarea.
So I have manage to handle this situation in this way:
$riskareas = Riskarea::all();
foreach ($riskareas as &$riskarea) {
foreach ($riskarea->riskfields as &$riskfield) {
$result = DB::table('invite_riskarea')
->select('insert', 'edit', 'view')
->where([
'riskarea_id' => $riskarea->id,
'riskfield_id' => $riskfield->id
])
->first();
if ($result) {
$riskfield->insert = $result->insert;
$riskfield->edit = $result->edit;
$riskfield->view = $result->view;
}
}
}
Essentially, I get all the riskareas, and then I iterate over the riskfields associated. For each riskfield, I get the permissions in the invite_riskarea table and then I have the correct structure that I want.
So to summarize:
Is it actually possible create a model relationship that returns the permissions for riskfield and not for riskarea?
Is my table implementation good enough to handle that situation?
I suggest you define back the many-to-many relation for the Riskfield model with the Invite model.
You can also define a direct many-to-many relationship with riskfield in the Invite model. This is how convenient it is for you personally.
And so the inverse many-to-many relationship
public function invites()
{
return $this->belongsToMany(Invite::class)->withPivot('insert', 'edit', 'view');
}
Then get all objects' Riskfields that are associated with the specified invite:
$riskfields = Riskfields::wherehas('invites' . function (Builder $query) use ($invite_id) {
$query->where('invites.id', $invite_id);
})->with('invites')->get();
Then you can access the desired fields of the pivot table in the specified way:
foreach ($riskfields as $riskfield) {
foreach ($riskfield->invites as $invite) {
$insertRiskField = $invite->pivot->insert;
$editRiskField = $invite->pivot->edit;
$viewRiskField = $invite->pivot->view;
}
}
Eager loading executes one query to the database
Yes
Documentation Laravel

Laravel: hasMany relationship + where condition fails

I have a Customer Eloquent model. Customer can have multiple WishLists where he / she can add some products. Typical ecommerce functionality.
The point is that Customer can belong to many Users models.
This was easy:
public function users()
{
return $this->belongsToMany(User::class, 'users_sync_customers', 'customer_uuid', 'user_id')
->withTimestamps()
->orderBy('last_name', 'asc');
}
So I can get all Customers assigned for logged in user by
auth()->user()->customers 🎉
As I mentioned, Customer can have multiple Wishlists:
public function wishLists()
{
return $this
->hasMany(WishList::class, 'customer_uuid', 'uuid')
->where('user_id', '=', auth()->user()->id); // <----- this will fail when I log out
}
but WishList is scoped to both Customer UUID and User ID.
Above relationship works but only when I'm logged in obviously.
As soon as I log out the auth()->user()->is is NULL and I get:
ErrorException {#1483 #message: "Trying to get property 'id' of
non-object"
Question: How can I reference in wishLists() the user_id value?
WishList model has this:
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
So can I use something like $this->user->id?
edit:
Nope, this also doesn't work.
you must check that the user is logged in?
Auth::check() ? Auth::user()->id : null

Using relationships in Laravel Eloquent with bridge table

I have an app that handles user info, and one of the pieces of data we collect is what school(s) they're attending. We have a User object, School object, and a UserSchool object.
This is the user_schools table:
user_id (int),school_id (int)
With the following records for instance:
100, 20
200, 500
200, 10
300, 10
I'm trying to get all schools for the current user (say user 200). This is my UserSchool object:
class UserSchool extends Model
{
var $table = 'user_schools';
function user() {
return $this->belongsTo('User');
}
function school() {
return$this->belongsTo('School');
}
}
In User I have the following relations defined:
public function schools()
{
return $this->hasManyThrough('School', 'UserSchool');
}
public function userSchools()
{
return $this->hasMany('UserSchool');
}
When I var_dump($user->schools) I get no results, even though I know it should be returning multiple Schools. What am I doing wrong? I'm sure this must have been asked before but I don't know what the actual term for this intermediate type of relationship is (bridge table? pivot table?).
Edit: Either way, most of the examples I've looked at haven't worked. I've also tried:
public function schools()
{
return $this->hasManyThrough('School', 'UserSchool', 'school_id');
}
In fact you don't need to have UserSchool object here for this relationship
For User model you can use create the following relationship:
public function schools()
{
return $this->belongsToMany(School::class, 'user_schools');
}
And now you can get schools of user using something like this:
$user = User::find(1);
foreach ($user->schools as $school)
{
echo $school->name;
}
This is standard many to many relationship

MySQL/Eloquent belongsToMany relation - two tables same pivot table allowed?

Is it allowed to have two tables, each with a belongsToMany() relation to each other, using the same pivot table?
Here is my User table:
And here is my Org table:
And here is the pivot table:
Here is the Eloquent User.php model:
<?php
namespace App;
use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model {
protected $table = 'users';
public $timestamps = true;
use Authenticatable;
use SoftDeletes;
protected $dates = ['deleted_at'];
public function orgs()
{
return $this->belongsToMany('App\Org', 'org_user', 'org_id', 'user_id')->withPivot('role_id');
}
}
And here is the Eloquent Org.php model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Org extends Model {
protected $table = 'orgs';
public $timestamps = true;
use SoftDeletes;
protected $dates = ['deleted_at'];
public function users()
{
return $this->belongsToMany('App\User', 'org_user', 'org_id, user_id')->withPivot('role_id');
}
}
Is it OK to do this? (i.e. use a shared pivot table) Does anyone with experience foresee any issues here? Or maybe someone could comment on standard practice/share some insights?
Absolutely you can! This is the beauty of a many to many relationship, in fact the example at http://laravel.com/docs/4.2/eloquent#many-to-many has the User and Role classes both set with belongsToMany. Thought being in that case (and sounds like in yours as well) that a user can have multiple roles and each role can be associated with multiple users.
Regarding your last two questions, the only part that gave me pause is the use of a role_id within the pivot table. The code you provided doesn't go into detail on what purpose that serves. As long as the User would only ever have one role associated with an Org this is fine, but if it could ever be more than one I would recommend another table to hold that User to Org role relationship.

How Eloquent work with Relationship?

I'm new to laravel relationship so many apologizes if it's just dumb question. I'm using a pivot table named users_email on the project to get Emails of users. Pivot table contains the foreign key Uid and Email_id. Uid references users table
primary key and the same as Email_id. I can get the result while joining them using QueryBuilder.
$recent_inbox_email=DB::table('users_email')->
join('email','users_email.email_id','=','email.Id')->
join('users','users_email.Uid','=','users.Id')->
where('users_email.Uid','=',$Uid)->
where('email.draft','<>','true')->
where('email.trash','<>','true')->
where('email.status','=','unread')->count();
here's how I define the relationship in my models
public function getUid()//User Model
{
return $this->hasMany("User_Email",'Uid');
}
public function getEmId()//Email Model
{
return $this->hasMany("User_Email",'email_id');
}
//User_Email Model
public function email()
{
return $this->belongsTo('Email','Id','email_id');
}
public function user()
{
return $this->belongsTo('User','Id','Uid');
}
Now I want to query something like this using Eloquent
$query= select * from users_email inner join
email on users_email.email_id=email.Id
inner join users on users_email.Uid=users.Id
where users.Id=users_email.Uid limit 0,10
foreach($query as $emails)
{
echo $emails->f_name;
echo $emails->Message
}
DB designer Pic
Link to image
Thanks
There are no dumb questions. I'll try to give you an explanation! I'm not a pro, but maybe I can help.
Laravel uses some conventions that are not mandatory, but if you use them, things work like a charm.
For example, as a general recommendation, tables should be named in plural (your table users is ok. Your "email" table should be "emails"). The model, should be named in singular. This is User.php for table users, Email.php for table emails.
"The pivot table is derived from the alphabetical order of the related model names...", in this case "email_user". I repeat, you are not obliged to name them like this, as you can specify the table for the model setting the $table property in the model.
Once you have set up things like this, you only have to add this to your User model:
public function emails()
{
return $this->belongsToMany('Email');
}
And in your Email model:
public function users()
{
return $this->belongsToMany('User');
}
The "User" and "Email" between parentheses is the name of the related model.
And that's it. You can now do this:
$user = User::find(1);
foreach($user->emails as $email) {
echo $email->subject . '<br>';
echo $email->message . '<br>';
}
If you decide not to follow conventions, you can still use Eloquent relationships. You have to set up the relationship like this:
public function nameOfRelation()
{
return $this->belongsToMany('NameOfRelatedModel', 'name_of_table', 'foreign_key', 'other_key');
}
In the case of the User model for example:
public function emails()
{
return $this->belongsToMany('Email', 'users_email', 'Uid', 'email_id');
}
And in the email model, the other way round.
The answer got long! I didn't test the code, but this should give you an idea!
You can always check the official Laravel documentation, it is really helpful!
http://laravel.com/docs/4.2/eloquent
Hope I helped