Laravel / Eloquent - custom relation method - laravel

I have a class Report which has a belongsToMany relation to Metric. Report also additionally has a belongsTo relation to Metric.
Normally, the model returned by the belongsTo relation is the same as one of the models in the belongsToMany relation. When this is true I'd like it to be the case that each of the two relations actually looks at the same object instance (this also saves an extra trip to the db).
So, in basic terms - is there a way to get one relation to check another first, to see if a model has already been loaded, and if so, point to that object rather than creating a new one.
I tried putting some code in the belongsTo relation method for Metric but I can't get round the fact it needs to return an instance of belongsTo, which needs various things passed as constructor arguments (ie. a query object), which aren't relevant in that case that the model has already been loaded in the belongsToMany relation.
I thought of ditching the belongsTo relation and adding data horizontally in the pivot table for the belongsToMany relation, but it isn't a many-to-many relation required so that seems a bit wrong.
Thanks!
Geoff

The idea here is to write a function which would check if a relationship is loaded and return that relationship, otherwise it will return the belongsToMany. This would go in your Report class. This is also for Laravel 5. If you have 4, just remove the namespaces from the model names.
public function metric()
{
return $this->belongsTo('App\Metric');
}
public function metrics()
{
return $this->belongsToMany('App\Metric');
}
public function getMetric()
{
if(array_key_exists('metric', $this->getRelations())) {
return $this->metric;
}
return $this->metrics()->first();
}
If you do decide to just go with a belongsToMany only, I'd suggest putting a unique key on your pivot table for both ID's to keep from getting any duplicates in the pivot table.

Related

Nested eloquent relationship

I just started my first laravel project today and stumbled upon this confusing situation,
I intended to use eloquent relationship instead of manually joining tables on query. So here it goes...
I have 3 tables which are users , tbl_instruments_taught , tbl_instruments
users table does not hold any of the table's ID,
tbl_instruments_taught - holds the id of user and intruments
Note: user can teach multiple instruments
Now, I implemented a hasOne on my other result set but I dont think hasOne works on this one. I read about belongsToMany but as I try implement it, it seems like every minute, it gets confusing.
Any idea?
I think you are looking for pivot table and relation many to many.
You have users table, instruments table (user has many instruments and one instrument could belong to many users) and user_instruments pivot table. Here db model:
And then add belongsToMany relationship in User and Instrument model (with specified pivot table). Like so:
//IN USER MODEL
public function instruments()
{
return $this->belongsToMany('App\Models\Instruments', 'users_instruments', 'user_id', 'instrument_id');
}
//IN INSTRUMENT MODEL
public function users()
{
return $this->belongsToMany('App\Models\Users', 'users_instruments', 'insrument_id', 'user_id');
}
Now you can reach instruments/users data through laravel relations/eager loading.

Laravel Relationship with OR case

Assume I have a User model, and also I have Couple model which forms of 2 users, father_id and mother_id which are essentially user_ids
On User model, I have
public function kids() {
return $this->hasMany('App\Kid', 'father_id');
}
However, I want to check if user_id is either father_id or mother_id, return the related Kid model.
Is there a way to achieve it with a single relationship? What is the proper way of handling this scenario, so I can use $user->kids that would check for both cases?
There is a way, but you wouldn't typically use it to "check" if there are related models.
If you have a field that determines if the model is representing a father or mother, such as is_father, you could do:
public function kids()
{
return ($this->is_father)
? $this->hasMany(Kid::class, 'father_id')
: $this->hasMany(Kid::class, 'mother_id');
}
Essentially, the relationship method MUST return a relationship instance. But you can do logic before you return this.
NOTE: The relationship is cached, so even if the is_father value changes in the same thread run, it will utilize the same relationship that it did before. This can cause unwanted bugs.

Laravel relationships: hasManyThrough a model with on an pivot table

I have 3 models in Laravel:
Item
ModifierGroup
Modifier
An Item can have many ModifierGroups via an intermediate table:
public function modifierGroups()
{
return $this->belongsToMany(
'App\ModifierGroup',
'menu_item_modifiers',
'item_id',
'group_id'
)->using('App\MenuItemModifier')
->orderBy('position', 'ASC')
->withPivot('position');
}
A ModifierGroup has many Modifiers:
public function modifiers()
{
return $this->hasMany(
'App\Modifier',
'group_id',
'id'
)->orderBy('position', 'ASC');
}
My question is whether it's possible to have a function on the Item that gets to the Modifiers, going through the ModifierGroup (and its pivot)? HasManyThrough doesn't seem to fit with an pivot table involved or does it?
It's possible with a BelongsToMany relationship by "skipping" the ModifierGroup table:
public function modifiers() {
return $this->belongsToMany(
'App\Modifier',
'menu_item_modifiers',
'item_id',
'group_id',
null,
'group_id'
);
}
This problem can be solved using pivot tables, but not in the way that you are using them. As I assume you have three tables - each associated with your models (Item, ModifierGroup, Modifier), you haven't set up pivot functionality as you don't actually have pivot tables. To do this you would require one table to link Item to ModifierGroups and another to link ModifierGroups to Modifier.
To start, lets create the tables using Artisan:
php artisan generate:pivot Item ModifierGroup
now, we have a table called item_modifier_group (I believe?). This table should have two columns, item_id and modifier_group_id. These are the keys Eloquent will use to connect to the items and modifier_groups tables. Now, in our query we can access and item's modifier groups by using the following query:
public function modifierGroups()
{
return $this->belongsToMany('App\ModifierGroup');
}
This means that when you call $item->modifierGroups() you will get a collection of all modifier groups having an id of $item->id.
This process can then be repeated for the ModifierGroups to Modifiers relationship:
php artisan generate:pivot ModifierGroup Modifiers
Now define the modifiers method in the ModifierGroup model:
public function modifiers()
{
return $this->belongsToMany('App\Modifier');
}
Now we have our pivot tables set up, and our relationships defined in one direction (just add similar methods to models to get Items via Modifiers)
The final piece of the pie is Eager Loading (it's amazing). Get item, with modifiers through the appropriate groups like so:
Item::with('modifierGroups.modifiers')->findOrFail('id');
Now there are other ways you could have gone about this problem, and some may even be more valid, however the huge benefit to doing it this way is the flexibility. You now have a way to connect Items, ModifierGroup and Modifiers with pivot tables and with a simple eager loaded query, can get any collection combination you deem necessary. Hope this helped! This is definitely the longest solution I've ever written on here...
For future reading I recommend:
Eager Loading - Nested Eager Loading
Defining Relationships - Many-to-Many

Laravel 5.5 retrieving null by nested relation

I have 3 databases:
Routes:
id
name
Rates:
Id
Route_id
Car_id
Cars:
id
name
My model for routes
public function rates()
{
return $this->hasMany('App\Rate', 'route_id');
}
My model for rates
public function car() {
return $this->belongsTo('App\Car','car_id');
}
Now I need to access the car relation, but when I do
return $this->route->with('from','to','rates.car')->paginate(74);
I get null for the car relation
{"id":1,"from_id":1,"to_id":2,"distance":400,"created_at":null,"updated_at":null,"from":{"id":1,"name":"\u0410\u043a\u043a\u043e","created_at":null,"updated_at":null,"lat":32.93310000000000314912540488876402378082275390625,"long":35.0827000000000026602720026858150959014892578125},"to":{"id":2,"name":"\u0410\u0440\u0430\u0434","created_at":null,"updated_at":null,"lat":31.261399999999998300381776061840355396270751953125,"long":35.21490000000000009094947017729282379150390625},"rates":[{"id":1,"route_id":1,"car_id":1,"rate":1123,"night_rate":1391,"car":null},{"id":5551,"route_id":1,"car_id":2,"rate":1123,"night_rate":1391,"car":null},{"id":11101,"route_id":1,"car_id":3,"rate":1123,"night_rate":1391,"car":null},{"id":16651,"route_id":1,"car_id":4,"rate":1123,"night_rate":1391,"car":null},{"id":22201,"route_id":1,"car_id":5,"rate":1123,"night_rate":1391,"car":null},{"id":27751,"route_id":1,"car_id":6,"rate":1123,"night_rate":1391,"car":null},{"id":33301,"route_id":1,"car_id":7,"rate":1123,"night_rate":1391,"car":null},{"id":38851,"route_id":1,"car_id":8,"rate":1123,"night_rate":1391,"car":null}]},
From my understanding you are trying to access a Car model through a Route model.
A couple of things I noticed that should help you find a solution.
First off I think the inverse relation you are supposed to use the belongToMany() function instead.
public function car() {
return $this->belongsToMany('App\Car','Rates'); // Perhaps call the table something like routes_cars to more clearly define it's a pivot table
}
Next I see you are trying to use model functions within the context of $this(). I assume you are doing this in your model? That logic should be in a controller, that might cause some undesired results but I'm not entirely sure. Also it looks like your parameters are incorrect when using with(). You use the function name that you defined in belongsToMany()
App/Route::with('car')->paginate(74);
With the correct relationships setup you rarely need to worry about the pivot table. If you are going to add extra information in the pivot table there are laravel functions to help you do that in the documentation.

Laravel save / update many to many relationship

Can anyone help me on how to save many to many relationship? I have tasks, user can have many tasks and task can have many users (many to many), What I want to achieve is that in update form admin can assign multiple users to specific task. This is done through html multiple select input
name="taskParticipants[]"
The catch here is that through the same form (input) you can add/remove users, that's why I have to use sync().
Maybe I should start from the beginning but don't know where to start...
This is my User model:
public function tasks()
{
return $this->belongsToMany('Task','user_tasks');
}
Task model
public function taskParticipants()
{
return $this->belongsToMany('User','user_tasks');
}
TaskController
public function update($task_id)
{
if (Input::has('taskParticipants'))
{
foreach(Input::get('taskParticipants') as $worker)
{
$task2 = $task->taskParticipants->toArray();
$task2 = array_add($task2,$task_id,$worker);
$task->taskParticipants()->sync(array($task2));
}
}
}
This is structure of tables
tasks
id|title|deadline
user_tasks
id|task_id|user_id
tldr; Use sync with 2nd param false
Many-to-many relationship is belongsToMany on both models:
// Task model
public function users()
{
return $this->belongsToMany('User', 'user_tasks'); // assuming user_id and task_id as fk
}
// User model
public function tasks()
{
return $this->belongsToMany('Task', 'user_tasks');
}
In order to add new relation use attach or sync.
Difference between the two is:
1 attach will add new row on the pivot table without checking if it's already there. It's good when you have additional data linked to that relation, for example:
User and Exam linked with pivot table attempts: id, user_id, exam_id, score
I suppose this is not what you need in your situation:
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]
$user->tasks()->attach([5,6,7]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,5,6,7]
2 sync on the other hand, will either remove all relations and set them up anew:
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]
$user->tasks()->sync([1,2,3]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3]
or it will setup new relations without detaching previous AND without adding duplicates:
$user->tasks()->sync([5,6,7,8], false); // 2nd param = detach
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,7,8]
Here's my notes on how to save and update on all the Eloquent relationships.
in One to One:
You have to use HasOne on the first model and BelongsTo on the second model
to add record on the first model (HasOne) use the save function
example:    $post->comments()->save($comment);
to add record on the second model (BelongsTo) use the associate function
example:    $user->account()->associate($account);    $user->save();
in One to Many:
You have to use HasMany on the first model and BelongsTo on the second model
to add record on the first table (HasMany) use the save or saveMany functions
example:    $post->comments()->saveMany($comments);
to add record on the second model (BelongsTo) use the associate function
example:    $user->account()->associate($account);    $user->save();
in Many to Many:
You have to use BelongsToMany on the first model and BelongsToMany on the second model
to add records on the pivot table use attach or sync functions
both functions accepts single ID or array of ID’s 
the difference is attach checks if the record already exist on the pivot table while sync don’t
example: $user->roles()->attach($roleId);
in Polymorphic One to Many:
You have to use MorphMany on the main model and MorphTo on all the (***able) models
to add records on all the other models use the save
example:    $course->tags()->save($tag);
the pivot table should have the following columns:
. main model ID
. (***able) ID
. (***able) Type
in Polymorphic Many to Many:
You have to use MorphByMany on the main model and MorphToMany on all the (***able) models
to add records on all the other models use the save or saveMany
example:    $course->tags()->save($tag);
example:    $course->tags()->saveMany([$tag_1, $tag_2, $tag_3]);
the pivot table should have the following columns:
. main model ID
. (***able) ID
. (***able) Type
in Has Many Through (shortcut):
You have to use HasManyThrough on the first table and have the normal relations on the other 2 tables
this doesn’t work for ManyToMany relationships (where there’s a pivot table)
however there’s a nice and easy solution just for that.
Here's an article I wrote, inspired by this answer. Important to check it: https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
syncWithoutDetaching([$id_one, $id_two, $id_three]); is what you are looking for. Actually it does the exact thing [sync with 2nd param false] does!
Solved: Use the updateOrInsert(array $attributes, array $values = [])
DB::table('your_pivot_table')->updateOrInsert([
'col' => $someValue
],[
'otherColumn' => $otherVlaue,
]);
}
The sync function obliterates the exiting relationships and makes your array the entire list of relations. You want attach instead to add relations without removing others.
for those who are searching for adding pivot attributes (the middle table attributes), you can use syncWithPivotValues and it also has the second parameter like this
$user->tasks()->syncWithPivotValues($tasksIDs,['day_number' => $day],false);

Resources