Trying to get a one-to-many relationship working in Laravel 5.4. I've read through the documentation and other resources online and in each instance can't get it working, and various ways I've tried results in different errors.
I have the following three tables:
accounts
contacts
account_contact (pivot table) has the fields: account_id (int/Foreign key), contact_id (int/Foreign key), primary (int), billing (int)
I'm trying to make it so the account can (but not necessarily) have one or more contacts.
My Models are as follows:
accounts
public function contacts()
{
return $this->hasMany(Contact::class);
}
contact
public function account()
{
return $this->belongsTo(Account::class)->withPivot('primary', 'billing');
}
Then say in a controller I attempt:
$account = Account::find($id);
if($account->isEmpty())
return response()->json(['Account not found.'], 404);
$contact = $account->contacts()->exists();
I get the following error:
(1/1) BadMethodCallException
Method contacts does not exist.
Obviously what I'm trying to do is make it so when a contact is made, it can be attached to an Account through the Pivot table.
And when getting the Account, I can then, through the Pivot table get the extra Pivot table fields, and the contact(s) that belong to that Account.
Just to clarify a little further, I am trying to use eloquent, using pivots to do the following query, without having to write it out in every instance.
$contacts = DB::table('account_contact')
->select('contact_id', 'primary', 'billing')
->where('account_id', $id)
->get();
$accountContacts = [];
foreach($contacts as $c){
$accountContact = Contact::find($c->id);
$accountContacts[] = array(
"id" => $accountContact->id,
"sal" => $sal = $accountContact->salutation == null? '' : $accountContact->salutation,
"firstName" => $accountContact->first_name,
"lastName" => $accountContact->last_name,
);
}
I was hoping to just be able to do something like
$accounts->pivot->contacts
and get a name like so:
$accounts->pivot->contacts->first_name
Your relationship is many to many so you need to do this:
accounts
public function contacts()
{
return $this->belongsToMany(Contact::class)->withPivot('primary', 'billing');
}
contact
public function account()
{
return $this->belongsToMany(Account::class)->withPivot('primary', 'billing');
}
Then you should do this via eloquent all the way to avoid the N+1 issue
$account = Account::with("contacts")->where("id", $id)->get();
foreach ($account->contacts as $contact) {
$accountContacts[] = array(
"id" => $accountContact->id,
"sal" => $sal = $accountContact->salutation == null? '' :
$accountContact->salutation,
"firstName" => $accountContact->first_name,
"lastName" => $accountContact->last_name,
);
}
In your account model, make one relationship like this:
public function account_contact()
{
return $this->belongsToMany('App\Account', 'account_contact', 'account_id', 'contact_id');
}
And then fetch using the function you wrote. I hope it would work.
Please try this out and tell me how it goes.
Related
maybe someone know how to insert pivot table in Laravel 8 automatically every i insert counselings table?
I have Model Counseling n to n Problem,
Input form
counselings table
problems table
Counselings Model
Problem Model
Controller
public function create()
{
return view('admin.counseling.create', [
'title' => 'Tambah Bimbingan dan Konseling',
'students' => Student::all(),
'problems' => Problem::all()
]);
}
public function find_nis(Request $request)
{
$student = Student::with('student_class', 'counselings')->findOrFail($request->id);
return response()->json($student);
}
public function store(Request $request)
{ dd($request->all());
$counseling = new Counseling();
$counseling->student_id = $request->student_id;
$counseling->user_id = Auth::user()->id;
$counseling->save();
if ($counseling->save()) {
$problem = new Problem();
$problem->id = $request->has('problem_id');
$problem->save();
}
}
You can insert into a pivot table in a few different ways. I would refer you to the documentation here.
Attaching
You may use the attach method to attach a role to a user by inserting
a record in the relationship's intermediate table:
Example:
$problem->counselings()->attach($counseling->id);
Sync
You may also use the sync method to construct many-to-many
associations. The sync method accepts an array of IDs to place on the
intermediate table. Any IDs that are not in the given array will be
removed from the intermediate table.
Example:
$problem->counselings()->sync($counselingsToSync);
Toggle
The many-to-many relationship also provides a toggle method which
"toggles" the attachment status of the given related model IDs. If the
given ID is currently attached, it will be detached. Likewise, if it
is currently detached, it will be attached:
Example:
$problem->counselings()->toggle($counselingsToToggle);
I would change your store() method to something like this :
public function store(Request $request)
{
$counseling = Counseling::create([
'student_id' => $request->student_id,
'user_id' => Auth::user()->id
]);
if($request->has('problem_id'){
$counseling->problems()->attach($request->problem_id);
//return something if problem id is in request
}
//return something if problem id is not there
}
Laravel 5.7. I have 2 Eloquent models: Owner, Cat.
Owner model:
public function cats()
{
return $this->belongsToMany('App\Cat')->withPivot('borrowed');
}
Cat model:
public function owners()
{
return $this->belongsToMany('App\Owner')->withPivot('borrowed');
}
The cat_owner pivot table has these fields:
id | cat_id | owner_id | borrowed
---------------------------------
1 | 3 | 2 | 1
I want my API to return a list of all cats, and if the logged-in user has borrowed this cat, I want the borrowed field to be set to true. This is what I have so far:
Controller:
public function index()
{
return CatResource::collection(Cat::all());
}
CatResource:
public function toArray()
{
$data = ['id' => $this->id, 'borrowed' => false];
$owner = auth()->user();
$ownerCat = $owner->cats()->where('cat_id', $this->id)->first();
if ($ownerCat) {
$data['borrowed'] = $ownerCat->pivot->borrowed == 1 ? true : false;
}
return $data;
}
This works, but it seems wasteful to request the $owner for every cat, e.g. if there's 5000 cats in the database. Is there a more efficient way to do this? I can think of 2 possible ways:
Pass the $owner to the CatResource (requires overriding existing collection resource).
Get this information in the controller first, before passing to the CatResource.
I prefer the second way, but can't see how to do it.
Try Conditional Relationship.
public function toArray($request)
{
return [
'id' => $this->id,
'borrowed' => false,
'borrowed' => $this->whenPivotLoaded('cat_owner', function () {
return $this->owner_id === auth()->id() && $this->pivot->borrowed == 1 ? true : false;
})
];
}
then call return CatResource::collection(Cat::with('owners')->get());
You are right, this does a lot of extra loading. I think you are running into the limitation that you can't know which record form cat_owner you want until you know both the records you're using from the cat and owner table.
For anyone still interested, my solution would be to make a resource that gives you just the pivot values
Since the following returns a collection you canNOT go to the pivot table on it:
/*
* returns a collection
*/
$data['borrowed'] = $this->owners
/*
* So this doesNOT work. Since you can’t get the pivot
* data on a collection, only on a single record
*/
$data['borrowed'] = $this->owners->pivot
You should receive the collection and then you can read the pivot data in the Resource of the owner Record. If this resource is only for the pivot data I would call it something like attributes.
create a new resourse for the attributes, something like:
class CatOwnerAttributeResource extends JsonResource
{
public function toArray($request)
{
return [
'borrowed' => $this->pivot->borrowed,
];
}
}
Then receive the collection like so:
$data = ['id' => $this->id, 'borrowed' => false];
/*
* Get the collection of attributes and grab the first (and only) record.
* NOTE: the filtering is done in the collection, not in the DBM. If there
* is a possibility that the collection of owners who own this cat gets really
* big this is not the way to go!
*/
if ($attributes =
CatOwnerAttributeResource::collection(
$this->owner
->where(‘id’ = $auth->user()->id)
->first()
) {
$data[‘borrowed’] = $attributes->borrowed == 1 ? true : false;
}
return $data;
Couldn’t run this code so please point errors out if you try it and it gives you any, I will adjust.
This code does not seem to capture the user id from the session so I can use it as a foreign key in the database for the place the user is adding a comment to.
public function store(Request $request, $place_id) {
// find place in the database
$place = Place::find($place_id);
// find user in the database
$user = User::find(\Auth::id());
// create review instance
$review = new Review([
'review' => request('review'),
'rating' => request('rating')
]);
// save review (creating relationship) in the places table as reviews
$place - > reviews() - > save($review);
// save review (creating relationship) in the users table as reviews
$user - > reviews() - > save($review);
$reviewData = Review::find($review - > id);
if (request() - > wantsJson()) {
return $reviewData; // Returns JSON, thanks to Laravel Magic™
}
// return view
return view('place');
}
When creating models for relationships, use create instead of save.
$user->reviews()->create($review);
The create method will associate the relevant relationship id.
I can see that you are saving the $review twice. Once in place_reviews and once in user_reviews. Consider changing your database logic, so reviews belongs to User and Place instead. This would be a lot more sensible structure:
Controller
$review = new Review([
'review' => request('review'),
'rating' => request('rating')
]);
$review->user()->associate($user);
$review->place()->associate($place);
$review->save();
Review model
public function user()
{
return $this->belongsTo(User::class);
}
public function place()
{
return $this->belongsTo(Place::class);
}
Review table
$table->unsignedInteger('user_id');
$table->unsignedInteger('place_id');
User and place model
public function reviews()
{
return $this->hasMany(Review::class);
}
You also have the option to use belongs to many relationships, if one Review could relate to multiple Place.
Another tip:
You may also consider using findOrFail to ensure your user is valid. This way your code will throw an Exception if there is no user id, as opposed to proceeding with a null user, which could be cause for hard-to-find errors.
$userId = \Auth::id();
$user = User::findOrFail($userId);
You should do like this.
$user = \Auth::user();
if(!is_null($user)){
$user_id = $user->id;
}
I have a relationship like this
Requests
=> id
public function proposals(){
return $this->hasMany(Proposal::class)
}
Proposals
=> request_id
=> company_id
public function request(){
return $this->belongsTo(Request::class)
}
public function proposals(){
return $this->belongsTo(Company::class)
}
Companies
=> id
public function proposals(){
return $this->hasMany(Proposal::class)
}
I've tought in something like this:
$request->with('proposals')->whereDoesntHave('company', function($query){
$query->where('company_id', '<>', 1);
})->get()
But it didn't work out.
In this scenario how can I retrieve all the requests that one company has not sent a proposal?
EDIT
A short version of my DB Schema
A User can have many Companies, a User can make Requests, a Company respond to this Request through a Proposal
What I am seeing here from your schema is a pivot table relationship.
A Request can belong to many Companies, and a Company can have many Requests. The relationship between the two is the Proposal, which is your pivot table.
So firstly, you want add the correct relationships to your models
class Request
{
public function companies()
{
// You will need to probably set the foreign and local keys as extra parameters here
return $this->belongsToMany(Company::class, 'proposals')->using(Proposal::class);
}
}
and
class Company
{
public function requests()
{
// You will need to probably set the foreign and local keys as extra parameters here
return $this->belongsToMany(Request::class, 'proposals')->using(Proposal::class);
}
}
Finally, you can get the proposal using the pivot which will be a Proposal object.
To address your original query, you would just do:
$requests = Request::with(['companies' => function($q) {
$q->where('companyid', '!=', 1);
}]);
I'm trying to create a Friendship system with Laravel (I'm starting with it) but I'm blocked with relationships. Here's the thing : there is one table Users and one table Friends which contains the following columns :
friends: id, user_id, friend_id, accepted.
It looks like a Many to Many so here's what I set on User class :
class User extends Eloquent {
function friends()
{
return $this->belongsToMany('User');
}
}
But when I try a :
$friends = User::find($id)->friends()->get()
I have this error :
Base table or view not found: 1146 Table 'base.user_user' doesn't exist
I would like to get a list of the Friends of a user, no matters if the user sent the invitation or received it. So the user can ba on user_id or on friend_id and then I retrieve the data of the other user depending of that column.
Any idea? Thank's!
EDIT : Here's the code I use :
$usersWithFriends = User::with('friendsOfMine', 'friendOf')->get();
$user = User::find(Auth::id())->friends;
foreach($user as $item) {
echo $item->first()->pivot->accepted;
}
tldr; you need 2 inverted relationships to make it work, check SETUP and USAGE below
First off the error - this is how your relation should look like:
function friends()
{
return $this->belongsToMany('User', 'friends', 'user_id', 'friend_id')
// if you want to rely on accepted field, then add this:
->wherePivot('accepted', '=', 1);
}
Then it will work without errors:
$user->friends; // collection of User models, returns the same as:
$user->friends()->get();
SETUP
However you would like the relation to work in both ways. Eloquent doesn't provide a relation of that kind, so you can instead use 2 inverted relationships and merge the results:
// friendship that I started
function friendsOfMine()
{
return $this->belongsToMany('User', 'friends', 'user_id', 'friend_id')
->wherePivot('accepted', '=', 1) // to filter only accepted
->withPivot('accepted'); // or to fetch accepted value
}
// friendship that I was invited to
function friendOf()
{
return $this->belongsToMany('User', 'friends', 'friend_id', 'user_id')
->wherePivot('accepted', '=', 1)
->withPivot('accepted');
}
// accessor allowing you call $user->friends
public function getFriendsAttribute()
{
if ( ! array_key_exists('friends', $this->relations)) $this->loadFriends();
return $this->getRelation('friends');
}
protected function loadFriends()
{
if ( ! array_key_exists('friends', $this->relations))
{
$friends = $this->mergeFriends();
$this->setRelation('friends', $friends);
}
}
protected function mergeFriends()
{
return $this->friendsOfMine->merge($this->friendOf);
}
USAGE
With such setup you can do this:
// access all friends
$user->friends; // collection of unique User model instances
// access friends a user invited
$user->friendsOfMine; // collection
// access friends that a user was invited by
$user->friendOf; // collection
// and eager load all friends with 2 queries
$usersWithFriends = User::with('friendsOfMine', 'friendOf')->get();
// then
$users->first()->friends; // collection
// Check the accepted value:
$user->friends->first()->pivot->accepted;
It's oviously a problem in your DB and also definition of the relation. Many-to-Many relation type expects you to use and intermediate table. Here's what you have to do :
Create a user_friend (id, user_id, friend_id) table in your schema.
Remove unnecessary fields from user and friend tables.
Create proper foreign keys . user.id-> user_friend.user_id , friend.id -> user_friend.friend_id
Better define full relation on the User and Friend models,
for example :
class User extends Eloquent {
function friends()
{
return $this->belongsToMany('User', 'user_friend', 'user_id', 'friend_id');
}
}
You can read much more in Laravel docs, HERE