Using relationships in Laravel Eloquent with bridge table - mysql

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

Related

Selecting a value from an n:n relationship

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

Query multiple table relationships using Laravel Eloquent Models

I'm trying to query multiple tables using Laravel Eloquent Models with one to one, one to many and many to many relationships.
I have a forms table, a brands table a users table and a brand_groups pivot table.
Each form has one brand and one user:
forms
ID
user_id
brand_id
Brands do not have any foreign keys:
brands
ID
Users do not have any foreign keys:
users
ID
And there is a pivot table to create a many to many relationship for creating brand groups that have many users like brand members:
brand_groups
brand_id
user_id
I'm trying to get all the forms that belong to a user either by a direct ownership (forms.user_id) or by brand membership, all the forms from all the brands that the user is a member through brand_groups many to many pivot table.
For example, we have 2 brands, 2 users and 1 user is a member of 1 brand:
brand(ID: 1)
brand(ID: 2)
user(ID: 1)
user(ID: 2)
brand_group(brand_id: 1, user_id: 1)
form(ID: 1, user_id: 1, brand_id: null)
form(ID: 2, user_id: null, brand_id: 1)
form(ID: 3, user_id: 2, brand_id: 1)
form(ID: 4, user_id: 1, brand_id: 2)
Using Laravel Eloquent Models (not direct DB facade calls), I'd like to retrieve all the forms that belong to a user. For the user(ID:1) there are 3 forms:
form(ID:1) direct user ownership
form(ID:2) user is a member of brand(ID:1) group which is the brand of form(ID:2)
form(ID:3) user is a member of brand(ID:1) group which is the brand of form(ID:3)
I gave it a shot using Eloquent: Relationships - Has Many Through:
Has Many Through
The "has-many-through" relationship provides a convenient way to access distant relations via an intermediate relation.
I have tried it like this:
class User extends Model
{
public function forms()
{
return Forms::hasManyThrough(
Form::class,
BrandGroups::class,
'brand_id',
'brand_id',
'id',
'form_id',
)->where('id', $this->id);
}
}
But I get errors like:
BadMethodCallException with message 'Call to undefined method App\Models\Form::brand_groups()'
EDIT
After some digging, I have managed to come up with the working MySQL code that will return all the forms for a user:
SELECT * FROM `forms`
WHERE EXISTS (
SELECT `brand_id`, `user_id`
FROM `brand_groups`
WHERE `forms`.`brand_id` = `brand_groups`.`brand_id`
AND `brand_groups`.`user_id` = 1
) OR `forms`.`user_id` = 1
Now I just need to convert that query to an eloquent model relation.
Eloquent Models
User.php
class User extends Authenticatable implements MustVerifyEmail
{
public function brands()
{
return $this
->belongsToMany(Brand::class, 'brand_groups')
->using(BrandGroups::class)
->as('member');
}
public function forms()
{
return $this->hasMany(Form::class, 'user_id');
}
}
Brand.php
class Brand extends Model
{
protected $table = 'brands';
public function forms()
{
return $this->hasMany(Form::class);
}
public function members()
{
return $this
->belongsToMany(User::class, 'brand_groups')
->using(BrandGroups::class)
->as('member');
}
}
Form.php
class Form extends Model
{
protected $table = 'forms';
public function owner()
{
return $this->belongsTo(User::class);
}
public function brand()
{
return $this->belongsTo(Brand::class);
}
}
UPDATE
I manage to find a query to get all forms related to a user like this:
class User extends Authenticatable implements MustVerifyEmail
{
...
public function allForms()
{
return Form::where(function ($q) {
$q->whereExists(function ($q) {
$q->from('brand_groups')
->where('forms.brand_id', DB::raw('brand_groups.brand_id'))
->where('brand_groups.user_id', $this->id);
})->orWhere('owner_id', $this->id);
});
}
}
How this can be converted to a direct User model eloquent relationship?
Have you tried to Eager Load the User model relationships?
Edit
Firstly: the pivot table name should be the singular -snake_case- name of both tables and should be in alphabetical order (brand_user)
Next, try the following:
return User::where(‘id’, $this->id)->with([‘forms’,‘brands.forms’)->get();
This should return the Forms with direct ownership plus the user Brands and their associated Forms

Laravel: Select from Database

in my Laravel App I have two tables:
Projects
- id
- user_id
- name
- etc...
Images
- id
- project_id
- url
How can I achieve it, to show all the Projects each user has and all the connected images (each project can have up to 20 images (stored in FTP) and Link in Field URL - the project_id from table "Projects" will be saved in field images.project_id)?
I learned, that I can show the projects like this:
$projects = DB::table('projects')->where('user_id','=',$user->id)->get();
and I tried with
$images = DB::table('images')->where('project_id','=',$projects->id)->get();
But I get an error message:
Property [id] does not exist on this collection instance.
What I am missing? Thank you for pointing me into the correct direction :-)
Kind Regards,
Stefan
For your question i suggest to use eloquent way like set up your models
class Project extends Model
{
public function images()
{
return $this->hasMany(\App\Models\Image::class, 'project_id');
}
public function user()
{
return $this->belongsTo(\App\Models\User::class, 'user_id');
}
}
class Image extends Model
{
public function project()
{
return $this->belongsTo(\App\Models\Project::class, 'project_id');
}
}
Now to find projects with their images you can query as
$projects = Project::with('images')->get();
Each object in $projects will have collection of their associated images.
To add filter for user you can use whereHas on relations
$projects = Project::with('images')
->whereHas('user', function ($query) use ($user) {
$query->where('id', '=', $user->id);
})->get();

Joining Two Tables to a Reference Table Laravel

I Have three tables
#1 Table timeline which is my reference table with an Auto incremented ID which is stored in column id
#2 timeline_videos
#3 timeline_else
What happens is on post if a video is uploaded with the post
it will go into Table #2 ,anything else goes into Table #3.
#2-3 have the Auto Increment Id from the Table timeline stored in a column pid
On query of The Timeline I need to join both tables data using id=pid
so I can use the rest of the Relational Data with the post.
I have done a bit of research and can't seem to find a method for doing so.
So far the code I have
Controller
$groupposts = timeline::where([
['owner','=',$owner],['id','<',$lastid],
])
->join('timeline_videos','timeline.id','=','timeline_videos.pid')
//->join('timeline_else','timeline.id','=','timeline_else.pid')
->orderBy('id','desc')
->limit(5)
->get();
This works with no errors with the second Join commented out but I need to also grab the timeline_else data .
Update --
I have now decided to use Eloquent Relationships to join the tables,
my question now is what type of relationship do I have between the
tables?
I realize it basically needs to be able to switch between two tables to
grab data based on the fact that timeline_videos and timeline_else will not be "JOIN" but separated by type .
The tables need to Join with table #1 timeline based on a column I now have named type for clarifying where to look and matching/joining using the id = pid
You can use relationships.
it sounds like timelines has many videos and has many video failures
https://laravel.com/docs/5.5/eloquent-relationships#one-to-many
you would have a model for each table and set up the relationships
timelines model:
public function videos()
{
return $this-> hasMany('App\Videos');
}
public function videoFailures()
{
return $this-> hasMany('App\videoFailures');
}
videos model:
public function timeline()
{
return $this->belongsTo('App\Timelines');
}
videos failures model:
public function timeline()
{
return $this->belongsTo('App\Timelines');
}
You can then go:
$timeLine = Timmeline::find($id);
to find videos of the time lines you would do:
$videos = $timeLine->videos();
to find else:
$videoElse = $timeLine-> videoFailures();
By using some of what Parker Dell supplied and a bit more trial and error
My Models Looks like
timeline
class timeline extends Model
{
protected $table ='timeline';
public $timestamps = false;
public function videos()
{
return $this->hasMany('App\timeline_videos','pid','id');
}
public function else()
{
return $this->hasMany('App\timeline_ect','pid','id');
}
}
timeline_ect.php ,I changed the name of the table
class timeline_ect extends Model
{
protected $table='timeline_ect';
public $timestamps = false;
public function timeline()
{
return $this->belongsTo('App\Models\timeline','pid','id');
}
}
timeline_videos
class timeline_videos extends Model
{
protected $table='timeline_videos';
public $timestamps = false;
public function timeline()
{
return $this->belongsTo('App\timeline','id','pid');
}
}
Then Lastly my Controller
$timeline = timeline::with('videos','else')
->orderBy('id','desc')
->limit(5)
->get();
So far no Problem query is correct.

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