Eager Loading with filters (where clauses)? - laravel

Is it possible to add where clause to an eager loading method on a model? For example:
class User extends Model {
public function sources()
{
return $this->hasManyThrough(
Source::class, // The related model
UserNetwork::class, // The intermediate table that connects this model with the related model.
'user_id', // The intermediate table's column that connects to this model.
'network_id', // The related table's column that connects to the secondLocalKey.
'id', // This model's column that connects to the firstKey.
'network_id' // The intermediate table's column that connects the related model.
)
->where('xyz_id', $this->xyz_id);
}
}
Doing this User::with('sources')->get() returns a collection of users with the sources relation but the sources collection is empty. When I run User::first()->sources()->get() it returns just the collection of sources with the actual data.
Isn't eager loading supposed to 'eager load' all the records instead of having to specify the the first() record?

Related

Eloquent custom "belongs to" relationship on multiple tables

I have the following DB structure:
Table Vehicles: id, car_id, plane_id
Table Cars: id, model...
Table Planes: id, model...
When a new record added to the table Vehicles, if it is a Car, the car_id will be set, while the plane_id will be left empty, and vice-versa, I know it's a bad structure, but it is legacy and I can't change it.
So I want to define a relationship in the Vehicle model where it can retrieve the Car object or the Plane object according to which key is empty, the car_id or the plane_id. And btw, I've already defined two relationships that will retrieve the Car object and the Plane object separately.
public function carVehicle()
{
return $this->belongsTo(Car::class, 'car_id');
}
public function planeVehicle()
{
return $this->belongsTo(Plane::class, 'plane_id');
}
This would be best handled by a Polymorphic relationship (see https://laravel.com/docs/5.8/eloquent-relationships#polymorphic-relationship for details), but there's ways around this if your current model doesn't match the structure and you can't change it.
You could have a third method that adds both to a Collection and returns the first() one (since you say one of car_id or plane_id will always be null):
Vehicle.php:
public function getChildVehicleAttribute(){
return collect([$this->carVehicle, $this->planeVehicle])
->filter(function($record) {
return $record != null;
})->first();
}
Then, you'd access via the following query:
$vehicle = Vehicle::with(['carVehicle', 'planeVehicle'])->first()->child_vehicle;
// OR
$vehicles = Vehicle::with(['carVehicle', 'planeVehicle'])->get();
foreach($vehicles AS $vehicle){
$childVehicle = $vehicle->child_vehicle;
// dd($childVehicle, etc.)
}
The with() clause would eagerload both relationships so $this->carVehicle and $this->planeVehicle don't trigger additional database calls, and calling child_vehicle on any Vehicle instance would return either a Car or a Plane (or null if neither is defined)

Laravel save to database with multiple tables having many relations to a table

I have a transaction table in database. The relation for the transaction table is, customer can send many transactions. A branch can have many transactions and a transaction handler can serve many transactions. I have defined models and relations. But don't know how to save the relations as save one first will make other fk null. Is there any method to save all at once or my database relation is wrong?
My schema is
And my controller code to save the relation is
//Save models related to transactions
$transaction = new Transaction($request->input('partCTransaction'));
//Customer can have many transactions
$customer->transactions()->save($transaction);
//branch has many reporting entities and a branch handles many transactions
$branch->transactions()->save($transaction);
//A transaction handler handles different transactions
$transactionHandler->transactions()->save($transaction);
Any idea on how to resolve this issue.
My error is
General error: 1364 Field 'branch_id' doesn't have a default value (SQL: insert into `transactions` (`date`, `ref_number`, `customer_id`, `updated_at`, `created_at`) values (, , 9, 2018-06-25 23:36:11, 2018-06-25 23:36:11))
Your Transaction model should have customer, branch, and transactionHandler belongsTo relationships:
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function branch()
{
return $this->belongsTo(Branch::class);
}
public function transactionHandler()
{
return $this->belongsTo(TransactionHandler::class);
}
The belongsTo relationship has an associate() method that sets the relationship field, but does not save the record. You can use these relationships to setup all the appropriate relations, and then call save() when you're done.
$transaction = new Transaction($request->input('partCTransaction'));
$transaction->customer()->associate($customer);
$transaction->branch()->associate($branch);
$transaction->transactionHandler()->associate($transactionHandler);
$transaction->save();
Just a side note, associate() returns the child model, so you can chain all these together if you prefer that look:
$transaction = new Transaction($request->input('partCTransaction'));
$transaction
->customer()->associate($customer)
->branch()->associate($branch)
->transactionHandler()->associate($transactionHandler)
->save();

Correct relationship in Laravel

I have four tables in database: groups, specialties, lessons, group_lesson. It's structures:
groups
id
specialty_id
name
specialties
id
name
lessons
id
specialty_id
group_lesson (UNIQUE INDEX lesson_id, group_id)
lesson_id
group_id
date
My models look like that for now:
class Group extends Eloquent {
public function specialty() {
return $this->belongsTo('Specialty');
}
}
class Lesson extends Eloquent {
public function specialty() {
return $this->belongsTo('Specialty');
}
}
class Specialty extends Eloquent {
public function lessons() {
return $this->hasMany('Lesson');
}
public function groups() {
return $this->hasMany('Group');
}
}
I need get additional fields in Group model look like that
Group - Eloquent model
name - string
lessons - collection of Lesson models of Group Specialty
date - date from group_lesson table
I've tried different relationships and combinations, but it's doesn't work. Please help me to write correct relationships.
You can use eager-loading to access relational data through relationships, and can even chain relationships further. As a rule of thumb, if you can draw a path to from 1 model to another through a relationship, you can eagerload all the relevant and relational data for that with chained eager-loads.
Laravel Eager Loading
As an example
$speciality_group = Speciality::with('group','lessons')->find($id);
Even though you are only getting a single instance of the speciality model, the related data is hasMany, meaning multiple records. You need to loop through these records using a foreach loop to access the relevant data for them, or alternitavely add additional closures in your initial query to load only a single related model.
foreach($speciality_group->group as $group)
{
echo $group->name;
}
You will need to do this for both instances where you want to display related information.

Laravel / Eloquent hasMany relationship with no foreign key

I have a model (Client) with a hasMany relationship to another (Client_option).
The two tables are in different databases (so there is a list of clients, and then each client has their own database with an options table within).
In my Client class I want my options() method to return the entire contents of the options table (it knows which client db to look for). As it is I get an error because the column client_id does not exist in the options table. I can of course create that column and populate every row with the client's id, but I'd only be doing it to keep Eloquent happy so would rather avoid that little messiness.
Thanks in advance for any input!
Geoff
This will allow you to work with it as a relation, call it as dynamic property $user->options, bulk save with push method and so on:
public function options()
{
// it will use the same connection as user model
$options = ClientOption::on($this->getConnectionName())->get();
// if options model has its own, then simply
// $options = ClientOption::get();
$this->setRelation('options', $options);
return $options;
}
public function getOptionsAttribute()
{
return (array_key_exists('options', $this->relations))
// get options from the relation, if already loaded
? $this->getRelation('options')
// otherwise call the method and load the options
: $this->options();
}

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